If there's one thing I've done right at Thinking Bottle so far, it's hire a great development team. If there's one thing said development team has done right so far, it's Variable Manager. Designed almost entirely by one our developers, Ennio, the system allows us to register arbitrary named variables to... well, anything! We use it for current HP, we use it for cooldowns, we use it for all the numbers in abilities. We've created a lot of great systems in the Spark engine, but this one has immediately become the backbone of all internal modding.
What can a variable do?
Each variable is registered with a string name and an entity to attach to, which can be anything from a unit, an ability, a modifier, or even a projectile. After registering the variable you can set or get the value of it at any point from lua scripts. This is great for values which do not change on their own, such as current gold or the level of an ability.
There is a second type of variable available in the system called dependent variables. These variables rely on bound lua handlers to implement any number of their functions. The most common use-case for this are values that are derived from other values. Some examples of values based entirely on other values include armor, max HP, current level, or regen.
Here is an example implementation of max HP:
local hp_max = class(Variable); function hp_max:GetValue() return 210 + self.owner:GetValue("strength") * 16; end
You can also use dependent variables as convenient interfaces for other variables. In the Spark source code we store current mana as a percentage, but we often want to get or set the mana based on the total value. So we created a dependent variable which acts on both get and set to interface with the percentage value; a sort of "view" of the mana percentage.
function mana:OnSet(current, proposed) local max = self.owner:GetValue("mana_max"); proposed = math.max(0, math.min(proposed, max)); self.owner:SetValue("mana_percentage", proposed / max ); return proposed; end function mana:GetValue() return self.owner:GetValue("mana_percentage") * self.owner:GetValue("mana_max"); end
... but what about items or abilities changing values at runtime?
Both variable types support a common feature, variable modifiers, designed to make it much easier to change an unrelated value. Using our API, you can attach one of these variable modifiers to any variable on any entity and apply a bonus (potentially negative) which will remain until you remove the variable modifier. Since variable modifiers are living functions, you can have the bonus be dynamic based on other logic or other variables from any entity.
Here is an example from a unit buff which increases the speed of an unrelated ability using a variable modifier:
function modifier_hook_speed:OnCreated () local ability = self:GetParent():GetAbilityByName("hook") self.hookModifier = self:ModifyAbility(ability, "speed", function () return self:GetAbility():GetSpecialValue("bonus_speed") end) end
This functionality makes it effortless to change the values of units, items, or abilities without modifying their implementation code. In fact, all our base stats, such as Strength, are entirely implemented in lua using the variable manager system. This observable API will be very useful both internally for code organization and also externally for modding.... Want to add new talents? A new base stat? Reimplement what agility does? Use your own custom armor formula?... No problem.
If you have any questions about Spark or the modding engine we're building, be sure to join the discord here: https://discord.gg/eDyGKQY