-
Notifications
You must be signed in to change notification settings - Fork 22
Templates for tpModifier Spell Conditions
Usually you import
from templeplus.pymod import PythonModifier
from toee import *
import tpdp
from utilities import *
To get a feedback in the log and console add:
print "Registering sp-Spell Name"
Next are the functions
def myNewFunction(attachee, args, evt_obj): #arguments should always be the same
*some code*
return 0
The next part defines when the function should be called. This is done by defining a PythonModifier:
spellNameModifier = PythonModifier("sp-Spell Name", 2) # spell_id, duration
("sp-Spell Name") is the name of the condition. If you want to call from a different script, usually the Spellxxxx - Spell Name.py you do:
spellTarget.obj.condition_add_with_args('sp-Spell Name', spell.id, spell.duration)
the number behind the name is the number of arguments the modifier takes. If you need more than two you have to increase the number. Please be aware that if you want to read an argument, the first argument is actually not 1 but 0 (args.get_arg(0)). I personally add the arguments as a comment here, but you can also do this in the beginning of the script (after the import stuff). Something like this is also fine:
# Arg0 = spell.id
# Arg 1= duration
Just be sure to add it somewhere.
The following lines are the event hooks, defining when the above functions are actually called
spellNameModifier.AddHook(Event_Trigger, Event_Key, myNewFunction, ())
All triggers and keys can be found here
I will list the function first and afterwards its correspondending hook.
Mouseover description:
def focusingChantSpellTooltip(attachee, args, evt_obj):
if args.get_arg(1) == 1:
evt_obj.append("Focusing Chant ({} round)".format(args.get_arg(1)))
else:
evt_obj.append("Focusing Chant ({} rounds)".format(args.get_arg(1)))
return 0
focusingChantSpell.AddHook(ET_OnGetTooltip, EK_NONE, focusingChantSpellTooltip, ())
Buff Symbol:
def focusingChantSpellEffectTooltip(attachee, args, evt_obj):
if args.get_arg(1) == 1:
evt_obj.append(tpdp.hash("FOCUSING_CHANT"), -2, " ({} round)".format(args.get_arg(1)))
else:
evt_obj.append(tpdp.hash("FOCUSING_CHANT"), -2, " ({} rounds)".format(args.get_arg(1)))
return 0
focusingChantSpell.AddHook(ET_OnGetEffectTooltip, EK_NONE, focusingChantSpellEffectTooltip, ())
def focusingChantSpellHasSpellActive(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
if evt_obj.data1 == spellPacket.spell_enum:
evt_obj.return_val = 1
return 0
focusingChantSpell.AddHook(ET_OnD20Query, EK_Q_Critter_Has_Spell_Active, focusingChantSpellHasSpellActive, ())
def focusingChantSpellKilled(attachee, args, evt_obj):
args.remove_spell()
args.remove_spell_mod()
return 0
focusingChantSpell.AddHook(ET_OnD20Signal, EK_S_Killed, focusingChantSpellKilled, ())
def focusingChantSpellSpellEnd(attachee, args, evt_obj):
print "Focusing Chant SpellEnd"
return 0
focusingChantSpell.AddHook(ET_OnD20Signal, EK_S_Spell_End, focusingChantSpellSpellEnd, ())
focusingChantSpell.AddSpellDispelCheckStandard()
focusingChantSpell.AddSpellTeleportPrepareStandard()
focusingChantSpell.AddSpellTeleportReconnectStandard()
focusingChantSpell.AddSpellCountdownStandardHook()
needs to be added
Add concentration to a spell condition:
def harmonicChorusSpellAddConcentration(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
spellPacket.caster.condition_add_with_args('sp-Concentrating', args.get_arg(0))
return 0
def harmonicChorusSpellConcentrationBroken(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
if spellPacket.spell_enum == 0:
return 0
args.remove_spell()
args.remove_spell_mod()
return 0
harmonicChorusSpell.AddHook(ET_OnConditionAdd, EK_NONE, harmonicChorusSpellAddConcentration,())
harmonicChorusSpell.AddHook(ET_OnD20Signal, EK_S_Concentration_Broken, harmonicChorusSpellConcentrationBroken, ())
Add Concentration to an AoE spell:
def fugueSpellConcentrationBroken(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
if spellPacket.spell_enum == 0:
return 0
attachee.d20_send_signal(S_Spell_End, args.get_arg(0))
return 0
fugueSpell.AddHook(ET_OnD20Signal, EK_S_Concentration_Broken, fugueSpellConcentrationBroken, ())
If the spell adds another condition you need to add there as well:
fugueCondition.AddHook(ET_OnD20Signal, EK_S_Concentration_Broken, fugueConditionEnd, ())
Some spells enchant a specific item but in my experience you want to add the condition to caster and tie simply check if the caster is actually wearing the item and then apply the effect. I originially thought I simply can add the target to the arguments, but noticed I can't as they are limited to integers. I found a workaround by adding the item as target to the spell registry:
def sonicWeaponSpellChainToWeapon(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
mainhandWeapon = attachee.item_worn_at(item_wear_weapon_primary)
mainhandWeapon.d20_status_init()
spellPacket.add_target(mainhandWeapon, 0)
spellPacket.update_registry()
return 0
sonicWeaponSpell.AddHook(ET_OnConditionAdd, EK_NONE, sonicWeaponSpellChainToWeapon,())
then simply add a check in the function that requires the worn item:
if spellPacket.get_target(1) == evt_obj.attack_packet.get_weapon_used():
When the spell ends, you have to manually remove the item from the spell registry, or the spell will not end properly
def sonicWeaponSpellConditionRemove(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
removeWeaponFromSpellRegistry = spellPacket.get_target(0)
spellPacket.remove_target(removeWeaponFromSpellRegistry)
spellPacket.update_registry()
args.remove_spell()
return 0
sonicWeaponSpell.AddHook(ET_OnConditionRemove, EK_NONE, sonicWeaponSpellConditionRemove, ())
Please note, that the target number changes as the original spell target is already removed, which means that our added target has moved up a slot. I have used this method in a few spells, including an AoE effect that is only turned on, when the item is equipped and it works without troubles. The AoE spell in question would be Chechmate's Light.
Top level menu
#Add the top level menu
radialParent = tpdp.RadialMenuEntryParent("Improvisation ({})".format(args.get_arg(3)))
improvisationRadialId = radialParent.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class)
Child Menu Toggle Box
#Add checkboxes to activate or deactivate the bonus for different options
checkboxAbilityBonus = tpdp.RadialMenuEntryToggle("+{} to next Ability Check".format(args.get_arg(2)), "TAG_SPELLS_IMPROVISATION")
checkboxAbilityBonus.link_to_args(args, 4)
checkboxAbilityBonus.add_as_child(attachee, improvisationRadialId)
return 0
To be done: other radial menu options
My policy is: if I can use an existing mesfile entry, use the mesfile entry, if I would need to create an entry, use freeform if possible. If I use a mesfile entry I always add a comment directly behind it, so I can track it easily in the future, if I can replace it with a freefrom then.
In general, you can link in every string to the help by using the internal help file call:
~Focusing Chant~[TAG_SPELLS_FOCUSING_CHANT]
The string between the ~~ is the displayed string and the call to the help file is the help file tag in the [] bracets. Please leave no space between the two parts or the link will fail.
bonus_list is the most common function you will define. It adds are bonus or penalty. ** Please be sure to use the correct bonus type or you will create stacking issues.** Bonus types can be found here
def focusingChantSpellBonus(attachee, args, evt_obj):
evt_obj.bonus_list.add(1,21,"~Focusing Chant~[TAG_SPELLS_FOCUSING_CHANT] ~Circumstance~[TAG_MODIFIER_CIRCUMSTANCE] Bonus") #Focusing Chant adds a +1 Circumstance Bonus to Attack Rolls, Skill and Ability Checks
return 0
if you want to add it to the damage packet, you need to call if slightly different
evt_obj.damage_packet.bonus_list.add(4, 13, "~War Cry~[TAG_SPELLS_WAR_CRY] ~Morale~[TAG_MODIFIER_MORALE] Bonus")
The list can be called by a lot of Event Triggers. A few examples:
focusingChantSpell.AddHook(ET_OnToHitBonus2, EK_NONE, focusingChantSpellBonus,())
focusingChantSpell.AddHook(ET_OnGetSkillLevel, EK_NONE, focusingChantSpellBonus,())
focusingChantSpell.AddHook(ET_OnGetAbilityCheckModifier, EK_NONE, focusingChantSpellBonus,())
sirinesGraceSpell.AddHook(ET_OnGetSkillLevel, EK_SKILL_PERFORM, sirinesGraceSpellPerformBonus,())
sirinesGraceSpell.AddHook(ET_OnAbilityScoreLevel, EK_STAT_CHARISMA, sirinesGraceSpellAbilityBonus,())
sirinesGraceSpell.AddHook(ET_OnAbilityScoreLevel, EK_STAT_DEXTERITY, sirinesGraceSpellAbilityBonus,())
In the second example, the event will only trigger if the event key is matching. This means the bonus is only added to the Perform Skill and to the Charisma and Dex stats. Also note as the bonus differs, I call two seperate functions.
def sonicWeaponSpellBonusToDamage(attachee, args, evt_obj):
spellPacket = tpdp.SpellPacket(args.get_arg(0))
if spellPacket.get_target(1) == evt_obj.attack_packet.get_weapon_used():
bonusDice = dice_new('1d6') #Sonic Weapon Bonus Damage
evt_obj.damage_packet.add_dice(bonusDice, D20DT_SONIC, 3001) #ID3001 added in damage.mes
return 0
sonicWeaponSpell.AddHook(ET_OnDealingDamage, EK_NONE, sonicWeaponSpellBonusToDamage,())
def harmonicChorusSpellBonusToCasterLevel(attachee, args, evt_obj):
evt_obj.return_val += 2
return 0
harmonicChorusSpell.AddHook(ET_OnGetCasterLevelMod, EK_NONE, harmonicChorusSpellBonusToCasterLevel,())
Add concealment (like blur)
def veilOfShadowSpellConcealment(attachee, args, evt_obj):
evt_obj.bonus_list.add(20,0,"~Veil of Shadow~[TAG_SPELLS_VEIL_OF_SHADOW] Concealment Bonus") #Veil of Shadow grants 20% Concealment
return 0
veilOfShadowSpell.AddHook(ET_OnGetDefenderConcealmentMissChance, EK_NONE, veilOfShadowSpellConcealment,())
def phantomFoeSpellAttackMissChance(attachee, args, evt_obj):
evt_obj.bonus_list.add(50,0,"~Phantom Foe~[TAG_SPELLS_PHANTOM_FOE] Concealment Penalty") #Attacked Opponent gets 50% concealment due to Phantom Foe
return 0
phantomFoeSpell.AddHook(ET_OnToHitBonusFromDefenderCondition, EK_NONE, phantomFoeSpellSetFlankedCondition,())
def angelskinSpellEvilDr(attachee, args, evt_obj):
evt_obj.damage_packet.add_physical_damage_res(5, D20DAP_UNHOLY, 126) #Angelskin grants DR 5/evil; ID126 in damage.mes is DR
return 0
angelskinSpell.AddHook(ET_OnTakingDamage , EK_NONE, angelskinSpellEvilDr,())
This is not doable by using a flag I tried using both D20STD_F_POISON and D20STF_POISON and it did not work.
def irongutsSpellBonusToPoisonSaves(attachee, args, evt_obj):
if evt_obj.flags & 0x8:
evt_obj.bonus_list.add(5, 151,"~Ironguts~[TAG_SPELLS_IRONGUTS] ~Alchemical~[TAG_MODIFIER_ALCHEMICAL] Bonus") #151 = Alchemical; Ironguts adds a +5 Alchemical Bonus to Fortitude Saves vs. poison
return 0
irongutsSpell.AddHook(ET_OnSaveThrowLevel, EK_SAVE_FORTITUDE, irongutsSpellBonusToPoisonSaves,())
This is the table you have to use in place of the descriptor flag:
D20STF_NONE = 0,
D20STF_REROLL = 0x1,
D20STF_CHARM = 0x2,
D20STF_TRAP = 0x4,
D20STF_POISON = 0x8,
D20STF_SPELL_LIKE_EFFECT = 0x10,
D20STF_SPELL_SCHOOL_ABJURATION = 0x20,
D20STF_SPELL_SCHOOL_CONJURATION = 0x40,
D20STF_SPELL_SCHOOL_DIVINATION = 0x80,
D20STF_SPELL_SCHOOL_ENCHANTMENT = 0x100,
D20STF_SPELL_SCHOOL_EVOCATION = 0x200,
D20STF_SPELL_SCHOOL_ILLUSION = 0x400,
D20STF_SPELL_SCHOOL_NECROMANCY =0x800,
D20STF_SPELL_SCHOOL_TRANSMUTATION = 0x1000,
D20STF_SPELL_DESCRIPTOR_ACID = 0x2000,
D20STF_SPELL_DESCRIPTOR_CHAOTIC = 0x4000,
D20STF_SPELL_DESCRIPTOR_COLD = 0x8000,
D20STF_SPELL_DESCRIPTOR_DARKNESS = 0x10000,
D20STF_SPELL_DESCRIPTOR_DEATH = 0x20000,
D20STF_SPELL_DESCRIPTOR_ELECTRICITY = 0x40000,
D20STF_SPELL_DESCRIPTOR_EVIL = 0x80000,
D20STF_SPELL_DESCRIPTOR_FEAR = 0x100000,
D20STF_SPELL_DESCRIPTOR_FIRE = 0x200000,
D20STF_SPELL_DESCRIPTOR_FORCE =0x400000,
D20STF_SPELL_DESCRIPTOR_GOOD = 0x800000,
D20STF_SPELL_DESCRIPTOR_LANGUAGE_DEPENDENT = 0x1000000,
D20STF_SPELL_DESCRIPTOR_LAWFUL = 0x2000000,
D20STF_SPELL_DESCRIPTOR_LIGHT = 0x4000000,
D20STF_SPELL_DESCRIPTOR_MIND_AFFECTING = 0x8000000,
D20STF_SPELL_DESCRIPTOR_SONIC = 0x10000000, // might be an offset here
D20STF_SPELL_DESCRIPTOR_TELEPORTATION = 0x20000000,
D20STF_SPELL_DESCRIPTOR_AIR = 0x40000000,
D20STF_SPELL_DESCRIPTOR_EARTH = 0x80000000,
D20STF_SPELL_DESCRIPTOR_WATER = 33, // <- This one might not even work anymore...
D20STF_DISABLE_SLIPPERY_MIND = 34
DoT effects are handled at the beginning of the round of the affected target.
def bonefiddleSpellBeginRound(attachee, args, evt_obj):
if args.get_arg(1) >= 0:
spellDamageDice = dice_new('3d6')
spellPacket = tpdp.SpellPacket(args.get_arg(0))
game.create_history_freeform(attachee.description + " saves versus ~Bonefiddle~[TAG_SPELLS_BONEFIDDLE]\n\n")
if attachee.saving_throw_spell(args.get_arg(2), D20_Save_Fortitude, D20STD_F_NONE, spellPacket.caster, args.get_arg(0)): #save for no damage and to end spell immediately
attachee.float_text_line("Bonefiddle saved")
args.set_arg(1, -1)
else:
attachee.float_text_line("Bonefiddle damage", tf_red)
attachee.spell_damage(spellPacket.caster, D20DT_SONIC, spellDamageDice, D20DAP_MAGIC, D20A_CAST_SPELL, args.get_arg(0)) #is there a way to change unknown to spell name in the history window?
return 0
The args.set_arg(1, -1) line sets the duration to -1 and thus triggers the normal spell end trigger as the duration is below 0. I found this works better than ending the spell manually.
bonefiddleSpell.AddHook(ET_OnBeginRound, EK_NONE, bonefiddleSpellBeginRound, ())
This one was a tricky one, crit range increasing effects do not stack anymore in D&D 3.5 (which differs from 3.0) I found a solution that does not stack with any other effect.
def criticalStrikeSpellModifyThreatRange(attachee, args, evt_obj):
if evt_obj.attack_packet.get_weapon_used().obj_get_int(obj_f_type) == obj_t_weapon: #Keen requires weapon
getWeaponKeenRange = evt_obj.attack_packet.get_weapon_used().obj_get_int(obj_f_weapon_crit_range)
else:
return 0
appliedKeenRange = evt_obj.bonus_list.get_sum()
if appliedKeenRange == getWeaponKeenRange:
evt_obj.bonus_list.add(getWeaponKeenRange, 0 , "~Critical Strike~[TAG_SPELLS_CRITICAL_STRIKE] Bonus")
return 0
criticalStrikeSpell.AddHook(ET_OnGetCriticalHitRange, EK_NONE, criticalStrikeSpellModifyThreatRange,())
attachee.condition_add_with_args('Temp_Ability_Loss', stat_strength, 2)
attachee.condition_add_with_args('Temp_Ability_Loss', stat_dexterity, 2)
game.create_history_freeform("{} Abilities damaged\n\n".format(attachee.description))
failDice = dice_new('1d100')
distortDiceResult = failDice.roll()
if distortDiceResult < 51: #Distort Speech is a 50% Chance to fail spells and activate items
evt_obj.return_val = 100
attachee.float_text_line("Distort Speech Failure", tf_red)
game.create_history_freeform("~Distort Speech~[TAG_SPELLS_DISTORT_SPEECH] check: {} rolls a {}. Failure!\n\n".format(attachee.description, distortDiceResult))
game.particles('Fizzle', attachee)
distortSpeechSpell.AddHook(ET_OnD20Query, EK_Q_SpellInterrupted, distortSpeechSpellDistortCheck,())
def lightfootSpellCancelAoO(attachee, args, evt_obj):
attachee.float_text_line("Lightfooted")
evt_obj.return_val = 0
return 0
lightfootSpell.AddHook(ET_OnD20Query, EK_Q_AOOIncurs, lightfootSpellCancelAoO,())
def nauseatedConditionAoOPossible(attachee, args, evt_obj): #Can't AoO under Nauseated condition
evt_obj.return_val = 0
return 0
nauseatedCondition.AddHook(ET_OnD20Query, EK_Q_AOOPossible, nauseatedConditionAoOPossible, ())
def phantomFoeSpellSetFlankedCondition(attachee, args, evt_obj):
if evt_obj.attack_packet.get_flags() & D20CAF_FLANKED:
return 0
flags = evt_obj.attack_packet.get_flags()
flags |= D20CAF_FLANKED
evt_obj.attack_packet.set_flags(flags)
Reduce targets actions options
def rayOfDizzinessSpellTurnBasedStatusInit(attachee, args, evt_obj):
if evt_obj.tb_status.hourglass_state > 2:
attachee.float_text_line("Dizzy", tf_red)
evt_obj.tb_status.hourglass_state = 2 # Limited to a Standard or Move Action only
return 0
2 reduces to either move or standard action, 1 reduces to only move action
rayOfDizzinessSpell.AddHook(ET_OnTurnBasedStatusInit, EK_NONE, rayOfDizzinessSpellTurnBasedStatusInit, ())