diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp index 3c9e664d1..aa1c0ae16 100644 --- a/TemplePlus/condition.cpp +++ b/TemplePlus/condition.cpp @@ -5187,10 +5187,13 @@ int ClassAbilityCallbacks::BardMusicRadial(DispatcherCallbackArgs args){ if (!bardLvl || perfSkill < 3) return 0; + //Ask python for the maximum number of uses of bardic music + int nMaxBardicMusic = d20Sys.D20QueryPython(args.objHndCaller, "Max Bardic Music"); + RadialMenuEntryParent bmusic(5039); bmusic.flags |= 0x6; bmusic.minArg = args.GetCondArg(0); - bmusic.maxArg = bardLvl; + bmusic.maxArg = nMaxBardicMusic; auto bmusicId = bmusic.AddChildToStandard(args.objHndCaller, RadialMenuStandardNode::Class); RadialMenuEntryAction insCourage(5040, D20A_BARDIC_MUSIC, BM_INSPIRE_COURAGE, "TAG_CLASS_FEATURES_BARD_INSPIRE_COURAGE"); diff --git a/TemplePlus/python/python_object.cpp b/TemplePlus/python/python_object.cpp index 662e1f3ed..6a2280932 100644 --- a/TemplePlus/python/python_object.cpp +++ b/TemplePlus/python/python_object.cpp @@ -1434,6 +1434,136 @@ static PyObject* PyObjHandle_ConditionAddWithArgs(PyObject* obj, PyObject* args) return PyInt_FromLong(result); } +static PyObject* PyObjHandle_FavoredEnemy(PyObject* obj, PyObject* args) { + auto self = GetSelf(obj); + if (!self->handle) + return PyInt_FromLong(0); + + objHndl critter; + if (!PyArg_ParseTuple(args, "O&:objhndl.is_favored_enemy", &ConvertObjHndl, &critter)) { + return 0; + } + + if (!critter) + return PyInt_FromLong(0); + + auto category = critterSys.GetCategory(critter); + int result = 0; + + switch (category) { + case mc_type_aberration: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_ABERRATION); + break; + case mc_type_animal: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_ANIMAL); + break; + case mc_type_beast: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_BEAST); + break; + case mc_type_construct: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_CONSTRUCT); + break; + case mc_type_elemental: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_ELEMENTAL); + break; + case mc_type_giant: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_GIANT); + break; + case mc_type_humanoid: + { + //Check the subtype (be careful since it could fall into more than one category, if so return the highest feat count) + int flags = critterSys.GetSubcategoryFlags(critter); + if (flags & mc_subtype_goblinoid) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_GOBLINOID); + if (count > result) result = count; + } + if (flags & mc_subtype_reptilian) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_REPTILIAN); + if (count > result) result = count; + } + if (flags & mc_subtype_dwarf) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_DWARF); + if (count > result) result = count; + } + if (flags & mc_subtype_elf) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_ELF); + if (count > result) result = count; + } + if (flags & mc_subtype_gnoll) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_GNOLL); + if (count > result) result = count; + } + if (flags & mc_subtype_gnome) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_GNOME); + if (count > result) result = count; + } + if (flags & mc_subtype_halfling) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_HALFLING); + if (count > result) result = count; + } + if (flags & mc_subtype_orc) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_ORC); + if (count > result) result = count; + } + if (flags & mc_subtype_human) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_HUMANOID_HUMAN); + if (count > result) result = count; + } + } + break; + case mc_type_ooze: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_OOZE); + break; + case mc_type_plant: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_PLANT); + break; + case mc_type_shapechanger: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_SHAPECHANGER); + break; + case mc_type_vermin: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_VERMIN); + break; + case mc_type_dragon: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_DRAGON); + break; + case mc_type_magical_beast: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_BEAST); + break; + case mc_type_monstrous_humanoid: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_MONSTROUS_HUMANOID); + break; + case mc_type_outsider: + { + //Check the subtype (since it could fall into more than one category be careful to return the highest count) + int flags = critterSys.GetSubcategoryFlags(critter); + if (flags & mc_subtype_evil) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_OUTSIDER_EVIL); + if (count > result) result = count; + } + if (flags & mc_subtype_good) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_OUTSIDER_GOOD); + if (count > result) result = count; + } + if (flags & mc_subtype_lawful) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_OUTSIDER_LAWFUL); + if (count > result) result = count; + } + if (flags & mc_subtype_chaotic) { + int count = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_OUTSIDER_CHAOTIC); + if (count > result) result = count; + } + } + break; + case mc_type_fey: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_FEY); + break; + case mc_type_undead: + result = feats.HasFeatCountByClass(self->handle, FEAT_FAVORED_ENEMY_UNDEAD); + break; + } + + return PyInt_FromLong(result); +} static PyObject* PyObjHandle_IsFlankedBy(PyObject* obj, PyObject* args) { auto self = GetSelf(obj); @@ -3269,6 +3399,7 @@ static PyMethodDef PyObjHandleMethods[] = { { "is_active_combatant", PyObjHandle_IsActiveCombatant, METH_VARARGS, NULL }, { "is_category_type", PyObjHandle_IsCategoryType, METH_VARARGS, NULL }, { "is_category_subtype", PyObjHandle_IsCategorySubtype, METH_VARARGS, NULL }, + { "is_favored_enemy", PyObjHandle_FavoredEnemy, METH_VARARGS, NULL }, { "is_flanked_by", PyObjHandle_IsFlankedBy, METH_VARARGS, NULL }, { "is_friendly", PyObjHandle_IsFriendly, METH_VARARGS, NULL }, { "is_unconscious", PyObjHandle_IsUnconscious, METH_VARARGS, NULL }, diff --git a/tpdata/tpgamefiles.dat b/tpdata/tpgamefiles.dat index bc955d017..7cc88e7a9 100644 Binary files a/tpdata/tpgamefiles.dat and b/tpdata/tpgamefiles.dat differ diff --git a/tpdatasrc/tpgamefiles/rules/feats/improved favored enemy.txt b/tpdatasrc/tpgamefiles/rules/feats/improved favored enemy.txt new file mode 100644 index 000000000..ad9fa0d66 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/improved favored enemy.txt @@ -0,0 +1,5 @@ +name: Improved Favored Enemy +flags: 12582912 +prereqs: +description: You deal an extra 3 points of damage to your favored enemies. This benefit stacks with any existing favored enemy bonus gained from another class. +prereq descr: Base attack bonus +5 and favored enemy ability, \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/feats/feat - Improved Favored Enemy.py b/tpdatasrc/tpgamefiles/scr/feats/feat - Improved Favored Enemy.py new file mode 100644 index 000000000..b975ad7b5 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/feats/feat - Improved Favored Enemy.py @@ -0,0 +1,15 @@ +from toee import * + + +def CheckPrereq(attachee, classLevelled, abilityScoreRaised): + + #Return zero if base attack bonus is too low + if attachee.get_base_attack_bonus() < 5: + return 0 + + #Check for any favored enemy feat + for i in range (feat_favored_enemy_aberration , feat_favored_enemy_humanoid_human): + if attachee.has_feat(i): + return 1 + + return 0 diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/extra_music.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/extra_music.py index 3944a8f56..a83bbf628 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/extra_music.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/extra_music.py @@ -10,14 +10,20 @@ def EMNewDay(attachee, args, evt_obj): BardicMusicCount = attachee.has_feat("Extra Music") - #Extra Music grants 4 additional uses of Bardic Music each time the feat is taken - #Max music per day remains 3, can't find a way to alter it. Works properly nonetheless. args.set_arg(0, args.get_arg(0) + BardicMusicCount * 4) return 0 + +def QueryMaxBardicMusic(attachee, args, evt_obj): + #Total uses = bard level + extra music count * 4 + MaxMusicCount = attachee.has_feat("Extra Music") * 4 + MaxMusicCount += attachee.stat_level_get(stat_level_bard) + evt_obj.return_val = MaxMusicCount + return 0 eSF = PythonModifier() eSF.ExtendExisting("Bardic Music") eSF.AddHook(ET_OnNewDay, EK_NEWDAY_REST, EMNewDay, ()) +eSF.AddHook(ET_OnD20PythonQuery, "Max Bardic Music", QueryMaxBardicMusic, ()) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/improved_favored_enemy.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/improved_favored_enemy.py new file mode 100644 index 000000000..493688ad0 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/improved_favored_enemy.py @@ -0,0 +1,23 @@ +#Improved Favored Enemy: Complete Warrior, p. 101 + +from templeplus.pymod import PythonModifier +from toee import * +import tpdp + +print "Registering Improved Favored Enemy" + +#Feat adds 3 extra damage +bon_val = 3 + +def impFavoredEnemyDamageBonus(attachee, args, evt_obj): + + target = evt_obj.attack_packet.target + favored_enemy = attachee.is_favored_enemy(target) + + if favored_enemy: + evt_obj.damage_packet.bonus_list.add_from_feat(bon_val, 0, 114, "Improved Favored Enemy") + return 0 + +impFavoredEnemy = PythonModifier("Improved Favored Enemy", 2) # args are just-in-case placeholders +impFavoredEnemy.MapToFeat("Improved Favored Enemy") +impFavoredEnemy.AddHook(ET_OnDealingDamage, EK_NONE, impFavoredEnemyDamageBonus, ())