Average Gain Module

Introduction

This module calculates the average gain of any stat or currency in your game, there aren’t a lot of resources out there about this, so I decided to create a simple and easy to use module.


Showcase


Documentation

CONSTRUCTOR

new(seconds: number, updateInterval: number, allowNegative: boolean, roundAverage: boolean)
new(seconds: number)
"update interval is 0 by default"

METHODS

getAverage(): number
"returns the average gain"
update(amount: number, callback)
"updates the average gain"
destroy() ONLY WHEN updateInterval > 0
"destroys the update loop"

Example

How can this be useful? Let’s say you have a tycoon, and you want to show the player how much he is getting per second, this module for you!

--// Services
local ReplicatedStorage = game:GetService("ReplicatedStorage")

--// Modules
local AverageGain = require(ReplicatedStorage.AverageGain) 

--// Variables
local label = script.Parent.TextLabel

local cash = 0
local cashAverage = AverageGain.new(1, .5) -- average per second, updates every .5 seconds
--[[
Ex:
 AverageGain.new(60) -- average per minute, updates when update() is called
 AverageGain.new(3600, 0) -- average per hour, updates when update() is called
 ...
]]

--// Functions
local function updateText(value, average)
	label.Text = `Cash: {value} ({average}/s)`
end

--// Init
updateText(cash, cashAverage:getAverage())
task.spawn(function()
	while true do
		task.wait(.1)
		cash += 1
		cashAverage:update(cash, function(average) -- updates the average and updates the ui
			updateText(cash, average)
		end)
	end
end)

Source Code

That’s it, feel free to change and use it for your projects! :wink:

local averageGain = {}
averageGain.__index = averageGain

--// Private Functions
local function roundDecimals(num, numDecimalPlaces)
	local mult = 10 ^ (numDecimalPlaces or 0)
	return math.floor(num * mult + 0.5) / mult
end

--// Public Functions
function averageGain.new(seconds: number, updateInterval: number, allowNegative: boolean, roundAverage: boolean)
	local self = setmetatable({}, averageGain)
	self.previous = nil
	self.lastAverage = 0
	self.average = 0
	
	self._seconds = seconds
	self._updateInterval = updateInterval or 0
	self._allowNegative = allowNegative
	self._roundAverage = roundAverage
	self:_init()
	return self
end

function averageGain:getAverage(): number
	local average = (self._updateInterval > 0) and self.lastAverage or self.average
	return self._roundAverage and math.floor(average) or average
end

function averageGain:update(amount: number, callback)
	if not self.callback then self.callback = callback end
	if not self.previous then self.previous = amount end

	local difference = (amount - self.previous) * self._seconds
	self.previous = amount
	
	if not self._allowNegative and (self.previous <= 0 or difference <= 0) then 
		self.callback( self:getAverage() )
		return
	end
	
	self.average = roundDecimals(self.average + difference, 2)
	self.callback( self:getAverage() )

	task.delay(1, function()
		self.average = roundDecimals(self.average - difference, 2)
		self.callback( self:getAverage() )
	end)
end

function averageGain:_init()
	if self._updateInterval <= 0 then return end
	self._thread = task.spawn(function()
		while true do
			if not self.callback then task.wait() continue end
			task.wait(self._updateInterval)

			self.lastAverage = self.average
			self.callback(self.lastAverage)
		end
	end)
end

function averageGain:destroy()
	if self._thread and coroutine.status(self._thread) == "suspended" then
		task.cancel(self._thread)
	end
end

return averageGain
10 Likes

Update 1: Bug Fixes & New Features

  • Fixed a bug where floating-point numbers caused negative average;
  • Improved code structure for better efficiency;
  • Added an option to allow negative values;
  • Added an option to round the average.