-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Patches
Patches are special objects that can change the member values of objects. Everything that is considered an upgrade in real-time strategy games is done through patching in nyan.
PatchName<TargetObject>():
# This is a comment
int_member -= 5
text_member += "yes"
set_member &= {Archer, Knight, Spearman}
object_member = MoveAbility
...
The above example shows the declaration of a patch. Patches always have exactly one target denoted in angled brackets (<>
). The target indicates the type of object that can be changed by the patch. Target objects may be abstract.
Patches themselves are only allowed to change existing members of an object, not to introduce new ones. However, they can add new parents to an object which leads to the parent members being added to the object. This is discussed in section Adding inheritance.
Apart from having to specify a target and the lacking ability to introduce new members directly, patches have the same characteristics as normal objects. This includes the ability to inherit from other patches. When a parent of a patch already specified a target, the derived patch is not allowed to change it. All children of a patch will implicitly affect the same patch target.
Patches are applied at runtime during a game. When, where and why a patch is applied is decided by the openage engine functions and is only limited by the type of the target object. The number of applications of a patch is not limited and independent from its declaration. Depending on the game, it could be applied once, infinite times or never.
It is also important to mention that a patch does not change the declaration of an object and has no influence on the actual nyan files. Patches operate on instances of objects in the nyan database of the engine. Only these instances are altered.
Patches can be applied to all instances, a set of instances or one specific instance of an object. This is an important difference to the system used in Age of Empires II where upgrades usually affect all instances of an object. In nyan, every object that has the type of the target object has the potential of being patched, but does not have to be patched necessarily.
The example below shows how an instance is altered by a patch.
# This is a normal object
Archer(RangedUnit):
hp = 30
attack = 4
speed = 0.96
# This is a patch
Fletching<RangedUnit>(Tech):
attack += 1
Here we see two objects: Archer
and Fletching
. Fletching
is also a patch because it has specified RangedUnit
as a target. It increases the value of attack
by 1
each time it is applied. Because Archer
inherits from RangedUnit
, it is of the type RangedUnit
and the patch can potentially be applied to its instances.
Let's have a look at an instance of archer before the application of the patch.
(Archer instance)
hp = 30
attack = 4
speed = 0.96
This instance still has the default values of the object declaration. We now apply Fletching
, e.g. by an engine function for researching technologies.
(Archer instance)
hp = 30
attack = 5
speed = 0.96
attack
has increased by 1, as we expected. We now apply the patch a second time.
(Archer instance)
hp = 30
attack = 6
speed = 0.96
Again, the attack
value increased by 1.
Because patches are just a special kind of object, they can be patched too. This is especially useful for mods, which want to change the effects of existing technologies or unit upgrades.
Fletching<RangedUnit>(Tech):
attack += 1
BetterFletching<Fletching>():
attack +=2
In the above example we see two patches: Fletching
and BetterFletching
. BetterFletching
patches Fletching
by increasing the value of the member attack
by 2.
There are peculiarities to consider as we will see in the next example.
DebuffAttack<Unit>():
attack -= 1
DebuffPatch<DebuffAttack>(Tech):
attack += 2
In a naive approach, some might expect the member of DebuffAttack
to be attack += 1
after applying DebuffPatch
. However, this would be wrong. The patch only changes the value, it does not touch the operator. Therefore, the member will be patched to attack -= 3
by DebuffPatch
. Changing the operator requires an additional @
symbol. Using @
overwrites both the operator and the value.
DebuffAttack<Unit>():
attack -= 1
OverwriteOperator<DebuffAttack>():
# overwrite operator and value of 'attack'
attack @+= 2
Applying OverwriteOperator
will alter the member attack to attack += 2
. The @
modifier can also be stacked to overwrite the overwrite operators.
DebuffAttack<Unit>():
attack -= 1
OverwriteOperator<DebuffAttack>():
# overwrite operator and value of 'attack'
attack @+= 2
FixOperator<OverwriteOperator>():
# this overwrites the operator of 'OverwriteOperator'
# with '*='
attack @*= 1
FixOperatorFix<OverwriteOperator>():
# this overwrites the operator of 'OverwriteOperator'
# with '@*='
attack @@*= 1
To show how @
can be stacked, we have introduced two patches for OverwriteOperator
: FixOperator
and FixOperatorFix
. They both overwrite the operator of OverwriteOperator
, but have a slightly different outcome.
FixOperator
overwrites the operator of OverwriteOperator
with *=
. That means, after applying FixOperator
the instance of OverwriteOperator will look like this:
(OverwriteOperator instance)
# no more '@' because it was overwritten
attack *= 1
The application of FixOperator
also overwrote the @
modifier. Therefore, the application of OverwriteOperator
will no longer overwrite the operator of DebuffAttack
.
On the other hand, FixOperatorFix
overwrites the operator of OverwriteOperator
with @*=
, or more specifically with everything after the first occurence of @
.
(OverwriteOperator instance)
# '@' is still present
attack @*= 1
When OverwriteOperator
is applied now, it will still overwrite the operator of DebuffAttack
, but with the operator it got from the application of FixOperatorFix
.
A special feature of patches is their ability to change the inheritance parents of a nyan object. This is introduced to allow the injection of a parent in between an existing parent-child relationship. This is denoted by the [NewParent+, +OtherNewParent, ...]
syntax. The position of the +
specifies if the object is added at the front (Name+) or the end of the current parent list (+Name).
RootObject():
magic_value : int = 9001
ChildObject(RootObject):
magic_value -= 1
InjectedObject(RootObject):
magic_value -= 7661
InjectIt<ChildObject>[InjectedObject+]():
magic_value += 2
When the InjectIt
patch is applied, InjectedObject
will be the new direct parent of ChildObject
.