-
Notifications
You must be signed in to change notification settings - Fork 22
Python Modifiers
Python Modifiers are python objects used in Temple+ to expose ToEE's Dispatcher System and to create new Modifiers (a.k.a Conditions).
Those familiar with ToEE scripting know the use of Modifiers/Conditions from scripts - e.g.:
zuggtmoy.condition_add_with_args( "Invisible", 0, 0 )
Likewise, the Property fields in protos.tab, which include item properties such as UseableItem
, also refer to Modifiers.
Python Modifiers allow you to create brand new effects for either of the above.
In addition, Feats and Classes also make use of Modifiers for many of their actual effects, such as Save/BAB values, special Class / Feat bonuses, and even defining the Radial Menu entries which in turn trigger game actions.
Before getting started with Python Modifiers, please thoroughly review the ToEE Dispatcher System since the concepts are identical.
You can find a list of the vanilla ToEE Modifiers here.
The Python Modifier files distributed with Temple+ are located inside the file tpdata\tpgamefiles.dat.
After extracting it, you may find the modifiers in scr\tpModifiers.
For adding custom or edited Python Modifier files, you can place them in the ToEE overrides folder - i.e.
<ToEE folder>\overrides\scr\tpModifiers
and they will be automatically registered when the game is launched.
You define a Python Modifier by instantiating a PythonModifier object.
Example usage:
from templeplus.pymod import PythonModifier
import tpdp
pmEx = PythonModifier("PyMod Example", 0) # creates and registers a Modifier with 0 args; prevents duplicates by default!
The PythonModifier Constructor generally takes three arguments:
- The first argument is the Modifier Name - this is used as a key in ToEE's hashtable of Modifiers.
- The second argument is the number of Arguments the Modifier has.
- A third optional argument is whether to add the PreventDuplicate hook, which prevents more than one instance of the Modifier on any given game object.
The default value is set to true, and most of the time you won't even see it specified.
Whenever you instantiate a PythonModifier, it automatically registers it in ToEE's table of Modifiers. It will replace any previously existing Modifiers, so you can overwrite vanilla modifiers if you wish.
To add a callback hook to a Python Modifier, use the .AddHook
class method.
It takes 4 arguments:
- EventType - The Event Type to which it'll respond.
- EventKey - the subtype of event. If EK_NONE is used, it will respond to any type of Key!
- Callback Function - A Python function that handles the event and the Event Object.
- Parameter Tuple - To be used like the Data1/Data2 fields in the Conditions. Useful for recycling generic parametric Callbacks.
Example usage:
def OnDamage2(attachee, args, evt_obj): # this callback makes your target fall down after taking damage (i.e. on a successful hit, in the damage post-processing stage)
target = evt_obj.attack_packet.target
if (target != OBJ_HANDLE_NULL):
if (target.d20_query(Q_Prone) == 0 and attachee.trip_check(target)):
target.fall_down()
target.condition_add("Prone")
target.float_mesfile_line( 'mes\\combat.mes', 104, 1 ) # Tripped!
tripBite = PythonModifier("Tripping Bite", 0)
tripBite.AddHook(ET_OnDealingDamage2, EK_NONE, OnDamage2, ())
Event Types (ET_xxx and EK_xxx) are integers defined in constants.py. You can technically specify any integer if you want to create new ones, but it is not recommended for the sake of compatibility with other modifications.
Event Keys can also be specified as strings, and the engine will convert them to a hashcode.
It's also possible to request new Event Types and to hook them into the engine - this is done in the C++ layer of the code.
As you can see, the PythonModifier callbacks have three inputs:
- attachee - this is the game object (PyObjHandle object) to which the Modifier is applied. It's the same as what you're used to from the ToEE san_ scripts and such.
Note that for item effects, this actually refers to the item Wielder. - args - an object that allows you to access and modify the Condition Node arguments.
The relevant methods areargs.get_arg(n)
andargs.set_arg(n, value)
- evt_obj - The Event Object.
It's highly recommend to just extract tpgamefiles.dat and learn by example :)
Start with pymod_example.py, and check out the Modifier definitions for the various Classes.
Temple+ introduces a new built-in python module called tpdp (short for Temple+ Dispatcher).
This module exposes all the Event Objects are exposed to the Python layer, as well as many useful ToEE data types (Classes) such as:
- BonusList
- AttackPacket
- DamagePacket
- D20Action
- RadialMenuEntry (and its subclasses, RadialMenuEntryAction, RadialMenuEntryToggle and RadialMenuEntryParent)
- and many more...
These data types appear as fields in the various Event Objects. See tpdp for more details.
You can instantiate new objects via tpdp.<Class Name>()
.
E.g. for Radial Menu entries, you'll often see code of the sort:
radial_entry = tpdp.RadialMenuEntryAction("text", D20A_xxx, x, "TAG_xxxx") # instantiates a new RadialMenuEntryAction object
radial_entry.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) # copies it to attachee's radial menu
Temple+ allows you to define new Query and Signal Events via a string key.
For example, suppose you want to define a callback for a query of the form:
obj.d20_query("Sneak Attack Dice")
This is handled by adding the following hook to the modifier:
classSpecObj.AddHook(ET_OnD20PythonQuery, "Sneak Attack Dice", AssassinSneakAttackDice, ())
The above example also happens to show how classes can now stack Sneak Attack dice on top of what they already get from the Rogue class.
Queries are the most commonplace method of obtaining status information on an object. This lets you expand the type of queries you can ask of the engine regarding the new Modifiers you create.
Signals are defined similarly. E.g. if you want to send a signal
obj.d20_send_signal("Increase Max Psi", 1)
The corresponding handler callback would be
def IncreaseMaxPsi(attachee, args, evt_obj):
max_psi = args.get_arg(1)
new_max = max_psi + evt_obj.data1
if new_max < 0: # in case new max is less than 0
new_max = 0
args.set_arg(1, new_max) # adjust max points by the amount specified in the event object
return 0
psiPoints.AddHook(ET_OnD20PythonSignal, "Increase Max Psi", IncreaseMaxPsi, ()) # hook IncreaseMaxPsi to event of python signal
However, it's possible that you'll need to add query handlers to existing Modifiers. Say, if you want to add a query for "Is Psi Resistant" to "Monster Undead" Modifier (this is a hardcoded ToEE modifier that gets automatically applied to undead creatures). For that you'll need to extend an existing modifier, explained in the next section.
You can also extending existing modifiers, such as the vanilla Classes. The relevant class method is .ExtendExisting("<name>")
. For example:
classSpecExtender = PythonModifier()
classSpecExtender.ExtendExisting("Cleric")
classSpecExtender.AddHook(...)
This should be done with care however, since sometimes ToEE references its hardcoded modifiers by address, which can cause a conflict with the above method. Consult the Temple+ crew if in doubt :)