Main.SideBar (edit) |
Main /
UpgradesIntroductionEvery UnitType and every Unit has certain properties, such as health, armor, shields, costs (minerals/oil) and so on. These properties can (currently) be modified through some kind of "TechCenter" using upgrades or technologies. Most properties that are relevant to upgrades are numeric values (int, float/bofixed, ...). There are some exceptions (e.g. the list of unittypes a factory can produce), but I hope they are not so difficult to support, too. For now we consider numeric value only. Every property has a minimum value, a maximum value and a current value. The minimum/maximum value is what most of this document is about.
Note that in my notation "current value" means the value of a property in a Unit object. It does not mean the "value of the property after upgrades".
RequirementsEvery property has a base value. This is stored in UnitProperties which reads it from the corresponding index.unit file. This base value should be modifyable (i.e. upgradeable) because of:
-> same as technologies, but applies to a single unit only
-> same as technologies, but applies to a single unit only
-> is always temporary only!
-> same as technologies, but applies to a single unit only
-> is temporary only!
-> same as technologies, but applies to a single unit only
-> is temporary only! (only as long as the other unit/unittype exists)
-> same as technologies, but applies to a single unit only
-> is temporary only! (only as long as the other unit/unittype is in range)
-> same as technologies, but can apply to all/some existing units and/or new units.
-> can be temporary
essentially the same as "Special item"
As you can see most are very similar and we can group them together:
ImplementationFrom the above list items you may see that the current implementation is not able to support all possible upgrade types: Current implementationCurrently we store the "base value" and the "max value" of an upgradeable property in UnitProperties. Note that "min value" is not stored currently and is usually context dependent (such as "min health is 0"). An upgrade always modifies the "max value" of the property, whereas the base value is the value as loaded from index.unit.
are not possible. (note that "apply to some exising units is possible, but only as "one-shot" upgrade, i.e. that changes the current value only, not the maximum value)
are not possible either. Better implementationIn order to support the required features we have to change the implementation. One trivial and pretty obvious way (I use health as example, but other properties are analogous):
int Unit::health()
{
float healthFactor = currentHealthFactor();
int baseHealth = unitProperties()->baseHealth();
int health = baseHealth;
for (UpgradeIterator it = firstUpgrade; it != endUpgrade; ++it) {
(*it)->applyUpgrade(&health);
}
return (int)(health * healthFactor);
}
As you can see, here all upgrades are stored in Unit (alternatively one could have two kinds of upgrades: one stored in Unit and one stored in UnitProperties. the example does not change from this).
note that "apply to existing units" can mean both: an actual upgrade object in the Unit class that modifies the max value (as shown in the example), or a change of the current value of the properties (in this case: the healthFactor) when the upgrade is gained.
There is however a disadvantage (as usual with trivial implementations):
This is because every time health() is being called (this can be very often) we have to go through the whole loop. An easy improvement: implement a cache:
int Unit::health()
{
if (upgradesDirty) {
mHealthCache = calculateHealthAsDescribedAbove();
}
return mHealthCache;
}
The main disadvantage here is that health() cannot be const, as mHealthCache needs to be modified. However by making mHealthCache mutable, this should be fixable, too. Technical implementation problemsThis section discusses (possible) problems with the approach discussed above. Unit creationUnit creation is rather simple: a Unit object is created as usual, with all upgradeable properties being "dirty", i.e. they are calculated as soon as they are accessed the very first time. IDsWe require some way to identify an upgrade. At runtime this is no problem, as we can simply use pointers to the upgrade objects. However when saving the game, we need a way to load it again.
I have not found a satisfying solution to this problem. Apply UpgradesWhen the player researches a technology, the corresponding Player object is notified. It then stores the technology (that is the UpgradeProperties pointer) internally. With the new design we require that such a technology is added to the upgrades list of all units that it applies to. Doing so should be rather simple, as the Player has access to all relevant units. The same must happen with non-technology upgrades, but often is easier. For example an experience upgrade applies to a single unit only anyway, so adding it to that unit only (not to the Player object) is all natural and straight forward. Apply Upgrades to current values of existing unitsThis one is tricky. Let us use "health" as example. Image a unit has 50 out of 100 health and an upgrade "health += 20" is added to that unit.
The first solution is the easiest, as nothing needs to be applied at all. However I think the other two solutions are preferred. I like the 3rd one more (although 2 is quite fine as well). Also, it might depend on what you upgrade. E.g. for shields and for weapon reloading times, I would use the first option - RL
This might be implementable using code like this:
void Unit::addUpgrade(upgrade)
{
mUpgrades.append(upgrade);
mUpgradesCacheDirty = true; // causes "MaxValue" and "MinValue" properties to be recalculated when they are accessed
applyUpgradeToCurrentValues(upgrade);
}
applyUpgradeToCurrentValues() does not use a cache in the way MaxValue and /MinValue do, as current values are always "one shot" applies, the "current value" is never changed permanently (otherwise e.g. when changing health, the unit would be invincible). The tricky part is "downgrading" a unit. This feature is required for temporary upgrades: imagine the unit collects an item "health += 100 for 2 minutes", then after two minutes the health must be reduced again. Why is this tricky? Imagine this
So the question is which of the following options do we choose now?
I think the 2nd solution is correct here. After collecting the special item, the unit got damaged by 150 points, if it did not have the special item, it would already be destroyed now. So it is logical that it'll be destroyed ony it loses that item. I'm not sure about this, it might be confusing when your unit suddenly blows up when the battle is over. I think I'd rather go for the 3rd option - RL
AB: agreed.
Update:Currently we do not apply any values manuelly when an upgrade is gained. Instead we use a "factor" for the "currentvalue", i.e. currentValue = factor * maxValue, where maxValue is the value that gets upgraded. Status(Note: In the article above the term "current implementation" refers to the old implementation before 2005/07/07) The architecture described in this article has been implemented. Today (2005/07/10) we store a "base" value of every upgradeable property in the classes UnitProperties or PluginProperties. Together with all upgrades, such a base value makes up the real "current" value, which is calculated on the fly and recalculated whenever the upgrades change (the "base" value is read once on startup from the config files only and does not change afterwards). A list of upgrades is maintained by every object of the classes
Note that all classes automatically (without any additional API requirements) temporary upgrades. |