From 54ec3043878d468bee88a20115611c164f3d587b Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Thu, 16 Sep 2021 11:11:34 +0200 Subject: [PATCH 01/32] Dragon Disciple WIP --- .../mes/help/dragon_discipile_help.tab | 6 + .../char_class/class023_dragon_disciple.py | 112 ++++++++++-------- .../feats/dragon disciple blindsense.txt | 7 ++ .../feats/dragon disciple breath weapon.txt | 22 ++++ .../feats/dragon disciple claws and bite.txt | 5 + .../dragon disciple dragon apotheosis.txt | 5 + .../feats/dragon disciple heritage black .txt | 6 + .../feats/dragon disciple heritage blue.txt | 6 + .../rules/feats/dragon disciple heritage.txt | 5 + .../scr/tpModifiers/dragon_disciple.py | 60 ++++++---- 10 files changed, 166 insertions(+), 68 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple breath weapon.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple claws and bite.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple dragon apotheosis.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab new file mode 100644 index 000000000..0fefbc135 --- /dev/null +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab @@ -0,0 +1,6 @@ +TAG_DRAGON_DISCIPILE TAG_PRESTIGE_CLASSES Dragon Discipile Dragon Discipile Short Desdcription - TBD!. Most Dragon Discipiles are barbarian, fighters, or rangers who have dabbed as bards or sorcerers. Occasionally, a serious spellcaster explores the path to further a goal of finding out more about his draconic heritage. Hit Die: d12 Requirements: To qualify to become a Dragon Discipile, a character must fulfill all the following criteria. Race: Any non dragon (cannot be already a half-dragon). Skills: Knowledge(Arcana) 8 ranks (not implemented). Languages: Draconic (not implemented). Spellcasting: Ability to cast arcane spells without preparation. Special: The player chooses a dragon variety when taking the first level in this prestige class. Base Attack and Base Save Bonuses: see ~table~[TAG_CLASS_FEATURES_DRAGON_DISCIPILES_TABLES] Class Skills: ~Concentration(Con)~[TAG_CONCENTRATION], Craft(Int), ~Diplomacy(Cha)~[TAG_DIPLOMACY], Escape Artist(Dex), ~Gather Information(Cha)~[TAG_GATHER_INFORMATION], Knowledge(all skills), ~Listen(Wis)~[TAG_LISTEN], Profession(Wis), ~Search(Int)~[TAG_SEARCH], Speak Language(None), ~Spellcraft(Int)~[TAG_SPELLCRAFT], and ~Spot(Wis)~[TAG_SPOT]. Skill Points at Each Level: 2 + Int modifier Class Features: [CMD_CHILDREN] +TAG_CLASS_FEATURES_DRAGON_DISCIPILES_TABLES TAG_DRAGON_DISCIPILE Dragon Discipile Base Attack & Base Save Bonuses - TBD! Level ~BAB~[TAG_LEVEL_BONUSES] ~Fort.~[TAG_FORTITUDE] ~Reflex~[TAG_REFLEX] ~Will~[TAG_WILL] 1 +0 +2 +0 +2 2 +1 +3 +0 +3 3 +2 +3 +1 +3 4 +3 +3 +1 +3 5 +3 +4 +1 +4 6 +4 +5 +2 +5 7 +5 +5 +2 +5 8 +6 +6 +2 +6 9 +6 +6 +3 +6 10 +7 +7 +3 +7 +TAG_CLASS_FEATURES_DRAGON_DISCIPILES_SPELLS_PER_DAY TAG_DRAGON_DISCIPILE Dragon Discipile Bonus Spells - TBD! At each stormlord level, the character gains new spells per day (and spells known, if applicable) as if he had also gained a level in a divine spellcasting class to which he belonged before adding the prestige class level. He does not, however, gain any other benefit a character of that class would have gained (improved chance of turning or destroying undead, additional favored enemies, and so on). If the character had more than one divine spellcasting class before becoming a stormlord, the player must decide to which class to add each stormlord level for the purpose of determining spells per day and spells known. +TAG_CLASS_FEATURES_DRAGON_DISCIPILES_GRANTED_PROFICIENCIES TAG_DRAGON_DISCIPILE Dragon Discipile Weapon and Armor Proficiency Dragon Discipiles gain no weapon or armor proficiencies. +TAG_CLASS_FEATURES_DRAGON_DISCIPILES_HERITAGE TAG_DRAGON_DISCIPILE Dragon Discipile Heritage The player chooses a dragon variety when taking the first level in this prestige class. TBD! +TAG_CLASS_FEATURES_DRAGON_DISCIPILES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPILE Dragon Discipile Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 1ec2d9b82..99ae0657c 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -1,22 +1,23 @@ from toee import * import char_class_utils +import char_editor ################################################### def GetConditionName(): - return "Dragon Disciple" + return "Dragon Disciple" def GetSpellCasterConditionName(): - return "Dragon Disciple Spellcasting" + return "Dragon Disciple Spellcasting" def GetCategory(): - return "Core 3.5 Ed Prestige Classes" + return "Core 3.5 Ed Prestige Classes" def GetClassDefinitionFlags(): - return CDF_CoreClass + return CDF_CoreClass def GetClassHelpTopic(): - return "TAG_DRAGON_DISCIPLES" + return "TAG_DRAGON_DISCIPLES" classEnum = stat_level_dragon_disciple @@ -24,75 +25,90 @@ def GetClassHelpTopic(): class_feats = { +1: ("Dragon Disciple Natural Armor",), +2: ("Dragon Disciple Claws and Bite",), +3: ("Dragon Disciple Breath Weapon",), +5: ("Dragon Disciple Blindsense",), +10: ("Dragon Disciple Dragon Apotheosis",) } +bonus_feats = ["Dragon Disciple Heritage"] + class_skills = (skill_alchemy, skill_concentration, skill_craft, skill_diplomacy, skill_escape_artist, skill_gather_information, skill_knowledge_all, skill_listen, skill_profession, skill_search, skill_spellcraft, skill_spot) def IsEnabled(): - return 1 + return 1 def GetHitDieType(): - return 12 + return 12 def GetSkillPtsPerLevel(): - return 2 - + return 2 + def GetBabProgression(): - return base_attack_bonus_type_martial + return base_attack_bonus_type_martial def IsFortSaveFavored(): - return 1 + return 1 def IsRefSaveFavored(): - return 0 + return 0 def IsWillSaveFavored(): - return 1 + return 1 def GetSpellListType(): - return spell_list_type_none # dragon disciples only advance bonus spells + return spell_list_type_none # dragon disciples only advance bonus spells def IsClassSkill(skillEnum): - return char_class_utils.IsClassSkill(class_skills, skillEnum) + return char_class_utils.IsClassSkill(class_skills, skillEnum) def IsClassFeat(featEnum): - return char_class_utils.IsClassFeat(class_feats, featEnum) + return char_class_utils.IsClassFeat(class_feats, featEnum) def GetClassFeats(): - return class_feats + return class_feats def IsAlignmentCompatible( alignment): - return 1 + return 1 def CanCastInnateArcane(obj): #todo: generalize - if obj.stat_level_get(stat_level_bard) > 0: - return 1 - if obj.stat_level_get(stat_level_sorcerer) > 0: - return 1 - return 0 - -def SpeaksDraconic( obj ): - return 1 # languages not implemented in ToEE + if obj.stat_level_get(stat_level_bard) > 0: + return 1 + if obj.stat_level_get(stat_level_sorcerer) > 0: + return 1 + return 0 + +def SpeaksDraconic(obj): + return 1 # languages not implemented in ToEE def KnowledgeArcanaCheck( obj ): - # if (obj.skill_ranks_get(skill_knowledge_arcana) < 8): #knowledge skill not implemented in ToEE - # return 0 - if obj.stat_level_get(stat_level) < 5: # a replacement for checking knowledge arcana - return 0 - return 1 - -def ObjMeetsPrereqs( obj ): - return 0 # WIP - - if not KnowledgeArcanaCheck(obj): - return 0 - if (not obj.has_feat(feat_cleave) ): - return 0 - if (not obj.has_feat(feat_power_attack) ): - return 0 - if (not CanCastInnateArcane(obj)): - return 0 - # if (not obj.has_feat(feat_improved_sunder) ): # sunder not yet implemented - # return 0 - #if (not obj.d20_query('EVIL_OUTSIDER_CONTACTED')): - # return 0 - return 1 \ No newline at end of file + # if (obj.skill_ranks_get(skill_knowledge_arcana) < 8): #knowledge skill not implemented in ToEE + # return 0 + if obj.stat_level_get(stat_level) < 5: # a replacement for checking knowledge arcana + return 0 + return 1 + +def ObjMeetsPrereqs(obj): + if not KnowledgeArcanaCheck(obj): + return 0 + elif not CanCastInnateArcane(obj): + return 0 + elif not SpeaksDraconic(obj): + return 0 + return 1 + + +# Levelup +def IsSelectingFeatsOnLevelup(obj): + newLvl = obj.stat_level_get( classEnum ) + 1 + if newLvl == 1: + return 1 + return 0 + +def LevelupGetBonusFeats(obj): +#Lazy copy + paste from fighter; could be cleaned up as it is only one feat + bonFeatInfo = [] + for ft in bonus_feats: + bonFeatInfo.append(char_editor.FeatInfo(ft)) + char_editor.set_bonus_feats(bonFeatInfo) + return diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt new file mode 100644 index 000000000..4064a60db --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt @@ -0,0 +1,7 @@ +name: Dragon Disciple Blindsense +flags: 8 +prereqs: +description: At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. + +Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. +prereq descr: Dragon Disciple level 5. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple breath weapon.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple breath weapon.txt new file mode 100644 index 000000000..0dc16a094 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple breath weapon.txt @@ -0,0 +1,22 @@ +name: Dragon Disciple Breath Weapon +flags: 8 +prereqs: +description: At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. + +At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. + +The DC of the breath weapon is 10 + class level + Con modifier. + +Black Line of acid +Blue Line of lightning +Green Cone of corrosive gas (acid) +Red Cone of fire +White Cone of cold +Brass Line of fire +Bronze Line of lightning +Copper Line of acid +Gold Cone of fire +Silver Cone of cold + +A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. +prereq descr: Dragon Disciple level 3. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple claws and bite.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple claws and bite.txt new file mode 100644 index 000000000..22251ea1e --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple claws and bite.txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Claws and Bite +flags: 8 +prereqs: +description: At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. +prereq descr: Dragon Disciple level 2. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple dragon apotheosis.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple dragon apotheosis.txt new file mode 100644 index 000000000..44dc4aa3c --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple dragon apotheosis.txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Dragon Apotheosis +flags: 8 +prereqs: +description: At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. +prereq descr: Dragon Disciple level 5. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt new file mode 100644 index 000000000..76736a4a8 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt @@ -0,0 +1,6 @@ +name: Dragon Disciple Heritage - Black +flags: 8 +prereqs: 23 1 +description: The player chooses a dragon variety when taking the first level in this prestige class. +prereq descr: Dragon Disciple level 1 +parent: Dragon Disciple Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt new file mode 100644 index 000000000..8a3c60164 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt @@ -0,0 +1,6 @@ +name: Dragon Disciple Heritage - Blue +flags: 8 +prereqs: 23 1 +description: The player chooses a dragon variety when taking the first level in this prestige class. +prereq descr: Dragon Disciple level 1 +parent: Dragon Disciple Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt new file mode 100644 index 000000000..6a8439c93 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Heritage +flags: 524288 +prereqs: 23 1 +description: The player chooses a dragon variety when taking the first level in this prestige class. +prereq descr: Dragon Disciple level 1 \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index f80321d07..93188a171 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -13,7 +13,7 @@ def GetSpellCasterConditionName(): print "Registering " + GetConditionName() -classEnum = stat_level_eldritch_knight +classEnum = stat_level_dragon_disciple classSpecModule = __import__('class023_dragon_disciple') ################################################### @@ -48,19 +48,6 @@ def OnGetSaveThrowWill(attachee, args, evt_obj): classSpecObj.AddHook(ET_OnSaveThrowLevel, EK_SAVE_WILL, OnGetSaveThrowWill, ()) -def OnGetAbilityScore(attachee, args, evt_obj): - #statType = args.get_param(0) - lvl = attachee.stat_level_get(classEnum) - statMod = args.get_param(1) - - newValue = statMod + evt_obj.bonus_list.get_sum() - if (newValue < 3): # ensure minimum stat of 3 - statMod = 3-newValue - evt_obj.bonus_list.add(statMod, 0, 139) - return 0 - -classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) - #region Spell casting # configure the spell casting condition to hold the highest Arcane classs @@ -132,15 +119,48 @@ def OnLevelupSpellsFinalize(attachee, args, evt_obj): #endregion +##### Dragon Disciple Class Features ##### + +### AC Bonus def NaturalArmorACBonus(attachee, args, evt_obj): - type = args.get_arg(0) - if type != 3: - return 0 - bonus = args.get_arg(1) - evt_obj.bonus_list.add(bonus , 0, 137) + classLevel = attachee.stat_level_get(classEnum) + if classLevel == 1: + bonusValue = 1 + elif classLevel < 7: + bonusValue = 2 + elif classLevel < 10: + bonusValue = 3 + else: + bonusValue = 4 + bonusType = 0 #ID 0 = Stacking; Wrong Type as Touch Attacks should nullify this bonus + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Natural Armor~[TAG_CLASS_FEATURES_DRAGON_DISCIPILES_NATURAL_ARMOR_INCREASE]") return 0 -naturalArmorInc = PythonModifier("Dragon Disciple Natural Armor", 3) +naturalArmorInc = PythonModifier("Dragon Disciple Natural Armor", 0) naturalArmorInc.MapToFeat("Dragon Disciple Natural Armor") naturalArmorInc.AddHook(ET_OnGetAC, EK_NONE, NaturalArmorACBonus, ()) +### Strength Bonus +def OnGetAbilityScore(attachee, args, evt_obj): + #statType = args.get_param(0) + lvl = attachee.stat_level_get(classEnum) + statMod = args.get_param(1) + + newValue = statMod + evt_obj.bonus_list.get_sum() + if (newValue < 3): # ensure minimum stat of 3 + statMod = 3-newValue + evt_obj.bonus_list.add(statMod, 0, 139) + return 0 + +classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) + +### Breath Weapon + + +### Blindsense + + +#### Dragon Apotheosis +# At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), +# and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, +#60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. From 03126730533a0e19c4a1168bfda7968b7549e204 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Fri, 17 Sep 2021 11:28:13 +0200 Subject: [PATCH 02/32] Updated Dragon Disciple --- tpdata/templeplus/lib/templeplus/constants.py | 2 + .../mes/help/dragon_discipile_help.tab | 6 - .../mes/help/dragon_disciple_help.tab | 11 ++ .../dragon_disciple_breath_weapons.mes | 1 + .../char_class/class023_dragon_disciple.py | 34 +++--- ...on02301_dragon_disciple_select_heritage.py | 18 +++ ...tion02302_dragon_disciple_breath_attack.py | 29 +++++ .../feats/dragon disciple heritage black .txt | 6 - .../feats/dragon disciple heritage blue.txt | 6 - .../rules/feats/dragon disciple heritage.txt | 4 +- .../rules/partsys/dragon_disciple_partsys.tab | 12 ++ .../3231 - Dragon Disciple Cone Breath.txt | 17 +++ ...Spell3231 - Dragon Disciple Cone Breath.py | 61 ++++++++++ .../scr/tpModifiers/dragon_disciple.py | 105 +++++++++++++++++- 14 files changed, 278 insertions(+), 34 deletions(-) delete mode 100644 tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab create mode 100644 tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab create mode 100644 tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes create mode 100644 tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py create mode 100644 tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt create mode 100644 tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab create mode 100644 tpdatasrc/tpgamefiles/rules/spells/3231 - Dragon Disciple Cone Breath.txt create mode 100644 tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py diff --git a/tpdata/templeplus/lib/templeplus/constants.py b/tpdata/templeplus/lib/templeplus/constants.py index 439daafb8..448dc6118 100644 --- a/tpdata/templeplus/lib/templeplus/constants.py +++ b/tpdata/templeplus/lib/templeplus/constants.py @@ -3357,6 +3357,8 @@ spell_new_slot_lvl_8 = 1613 spell_new_slot_lvl_9 = 1614 +spell_dragon_disciple_cone_breath = 3231 +spell_dragon_disciple_line_breath = 3232 stat_strength = 0 stat_dexterity = 1 diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab deleted file mode 100644 index 0fefbc135..000000000 --- a/tpdatasrc/tpgamefiles/mes/help/dragon_discipile_help.tab +++ /dev/null @@ -1,6 +0,0 @@ -TAG_DRAGON_DISCIPILE TAG_PRESTIGE_CLASSES Dragon Discipile Dragon Discipile Short Desdcription - TBD!. Most Dragon Discipiles are barbarian, fighters, or rangers who have dabbed as bards or sorcerers. Occasionally, a serious spellcaster explores the path to further a goal of finding out more about his draconic heritage. Hit Die: d12 Requirements: To qualify to become a Dragon Discipile, a character must fulfill all the following criteria. Race: Any non dragon (cannot be already a half-dragon). Skills: Knowledge(Arcana) 8 ranks (not implemented). Languages: Draconic (not implemented). Spellcasting: Ability to cast arcane spells without preparation. Special: The player chooses a dragon variety when taking the first level in this prestige class. Base Attack and Base Save Bonuses: see ~table~[TAG_CLASS_FEATURES_DRAGON_DISCIPILES_TABLES] Class Skills: ~Concentration(Con)~[TAG_CONCENTRATION], Craft(Int), ~Diplomacy(Cha)~[TAG_DIPLOMACY], Escape Artist(Dex), ~Gather Information(Cha)~[TAG_GATHER_INFORMATION], Knowledge(all skills), ~Listen(Wis)~[TAG_LISTEN], Profession(Wis), ~Search(Int)~[TAG_SEARCH], Speak Language(None), ~Spellcraft(Int)~[TAG_SPELLCRAFT], and ~Spot(Wis)~[TAG_SPOT]. Skill Points at Each Level: 2 + Int modifier Class Features: [CMD_CHILDREN] -TAG_CLASS_FEATURES_DRAGON_DISCIPILES_TABLES TAG_DRAGON_DISCIPILE Dragon Discipile Base Attack & Base Save Bonuses - TBD! Level ~BAB~[TAG_LEVEL_BONUSES] ~Fort.~[TAG_FORTITUDE] ~Reflex~[TAG_REFLEX] ~Will~[TAG_WILL] 1 +0 +2 +0 +2 2 +1 +3 +0 +3 3 +2 +3 +1 +3 4 +3 +3 +1 +3 5 +3 +4 +1 +4 6 +4 +5 +2 +5 7 +5 +5 +2 +5 8 +6 +6 +2 +6 9 +6 +6 +3 +6 10 +7 +7 +3 +7 -TAG_CLASS_FEATURES_DRAGON_DISCIPILES_SPELLS_PER_DAY TAG_DRAGON_DISCIPILE Dragon Discipile Bonus Spells - TBD! At each stormlord level, the character gains new spells per day (and spells known, if applicable) as if he had also gained a level in a divine spellcasting class to which he belonged before adding the prestige class level. He does not, however, gain any other benefit a character of that class would have gained (improved chance of turning or destroying undead, additional favored enemies, and so on). If the character had more than one divine spellcasting class before becoming a stormlord, the player must decide to which class to add each stormlord level for the purpose of determining spells per day and spells known. -TAG_CLASS_FEATURES_DRAGON_DISCIPILES_GRANTED_PROFICIENCIES TAG_DRAGON_DISCIPILE Dragon Discipile Weapon and Armor Proficiency Dragon Discipiles gain no weapon or armor proficiencies. -TAG_CLASS_FEATURES_DRAGON_DISCIPILES_HERITAGE TAG_DRAGON_DISCIPILE Dragon Discipile Heritage The player chooses a dragon variety when taking the first level in this prestige class. TBD! -TAG_CLASS_FEATURES_DRAGON_DISCIPILES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPILE Dragon Discipile Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab new file mode 100644 index 000000000..993f5f8bb --- /dev/null +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab @@ -0,0 +1,11 @@ +TAG_DRAGON_DISCIPLE TAG_PRESTIGE_CLASSES Dragon Disciple Dragon Disciple Short Description - TBD!. Most Dragon Disciples are barbarian, fighters, or rangers who have dabbed as bards or sorcerers. Occasionally, a serious spellcaster explores the path to further a goal of finding out more about his draconic heritage. Hit Die: d12 Requirements: To qualify to become a Dragon Disciple, a character must fulfill all the following criteria. Race: Any non dragon (cannot be already a half-dragon). Skills: Knowledge(Arcana) 8 ranks (not implemented). Languages: Draconic (not implemented). Spellcasting: Ability to cast arcane spells without preparation. Special: The player chooses a dragon variety when taking the first level in this prestige class. Base Attack and Base Save Bonuses: see ~table~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES] Class Skills: ~Concentration(Con)~[TAG_CONCENTRATION], Craft(Int), ~Diplomacy(Cha)~[TAG_DIPLOMACY], Escape Artist(Dex), ~Gather Information(Cha)~[TAG_GATHER_INFORMATION], Knowledge(all skills), ~Listen(Wis)~[TAG_LISTEN], Profession(Wis), ~Search(Int)~[TAG_SEARCH], Speak Language(None), ~Spellcraft(Int)~[TAG_SPELLCRAFT], and ~Spot(Wis)~[TAG_SPOT]. Skill Points at Each Level: 2 + Int modifier Class Features: [CMD_CHILDREN] +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES TAG_DRAGON_DISCIPLE Dragon Disciple Base Attack & Base Save Bonuses - TBD! Level ~BAB~[TAG_LEVEL_BONUSES] ~Fort.~[TAG_FORTITUDE] ~Reflex~[TAG_REFLEX] ~Will~[TAG_WILL] 1 +0 +2 +0 +2 2 +1 +3 +0 +3 3 +2 +3 +1 +3 4 +3 +3 +1 +3 5 +3 +4 +1 +4 6 +4 +5 +2 +5 7 +5 +5 +2 +5 8 +6 +6 +2 +6 9 +6 +6 +3 +6 10 +7 +7 +3 +7 +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_SPELLS_PER_DAY TAG_DRAGON_DISCIPLE Dragon Disciple Bonus Spells - TBD! At each stormlord level, the character gains new spells per day (and spells known, if applicable) as if he had also gained a level in a divine spellcasting class to which he belonged before adding the prestige class level. He does not, however, gain any other benefit a character of that class would have gained (improved chance of turning or destroying undead, additional favored enemies, and so on). If the character had more than one divine spellcasting class before becoming a stormlord, the player must decide to which class to add each stormlord level for the purpose of determining spells per day and spells known. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_GRANTED_PROFICIENCIES TAG_DRAGON_DISCIPLE Dragon Disciple Weapon and Armor Proficiency Dragon Disciples gain no weapon or armor proficiencies. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE TAG_DRAGON_DISCIPLE Dragon Disciple Heritage The player chooses a dragon variety when taking the first level in this prestige class. TBD! +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPLE Dragon Disciple Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_CLAWS_AND_BITE TAG_DRAGON_DISCIPLE Dragon Disciple Claws and Bite At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. Use the values below or the disciple’s base claw and bite damage values, whichever are greater. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. Size Bite Damage Claw Damage Small 1d4 1d3 Medium 1d6 1d4 Large 1d8 1d6 +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS TAG_DRAGON_DISCIPLE Dragon Disciple Wings At 9th level, a dragon disciple grows a set of draconic wings. He may now fly at a speed equal to his normal land speed, with average maneuverability. Not implemented in ToEE as there is flight in ToEE! +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. diff --git a/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes b/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes new file mode 100644 index 000000000..994e549be --- /dev/null +++ b/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes @@ -0,0 +1 @@ +{3231} {Dragon Disciple Cone Breath} diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 99ae0657c..8491252cd 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -25,14 +25,14 @@ def GetClassHelpTopic(): class_feats = { -1: ("Dragon Disciple Natural Armor",), +1: ("Dragon Disciple Heritage", "Dragon Disciple Natural Armor",), 2: ("Dragon Disciple Claws and Bite",), 3: ("Dragon Disciple Breath Weapon",), 5: ("Dragon Disciple Blindsense",), 10: ("Dragon Disciple Dragon Apotheosis",) } -bonus_feats = ["Dragon Disciple Heritage"] +#bonus_feats = ["Dragon Disciple Heritage"] class_skills = (skill_alchemy, skill_concentration, skill_craft, skill_diplomacy, skill_escape_artist, skill_gather_information, skill_knowledge_all, skill_listen, skill_profession, skill_search, skill_spellcraft, skill_spot) @@ -88,6 +88,12 @@ def KnowledgeArcanaCheck( obj ): return 0 return 1 +def DragonRaceCheck(obj): + #Can't check if character is half-dragon; it does not exist in ToEE + if obj.is_category_type(mc_type_dragon): + return 1 + return 0 + def ObjMeetsPrereqs(obj): if not KnowledgeArcanaCheck(obj): return 0 @@ -95,20 +101,22 @@ def ObjMeetsPrereqs(obj): return 0 elif not SpeaksDraconic(obj): return 0 + #elif DragonRaceCheck: + # return 0 return 1 # Levelup -def IsSelectingFeatsOnLevelup(obj): - newLvl = obj.stat_level_get( classEnum ) + 1 - if newLvl == 1: - return 1 - return 0 +#def IsSelectingFeatsOnLevelup(obj): +# newLvl = obj.stat_level_get( classEnum ) + 1 +# if newLvl == 1: +# return 1 +# return 0 -def LevelupGetBonusFeats(obj): +#def LevelupGetBonusFeats(obj): #Lazy copy + paste from fighter; could be cleaned up as it is only one feat - bonFeatInfo = [] - for ft in bonus_feats: - bonFeatInfo.append(char_editor.FeatInfo(ft)) - char_editor.set_bonus_feats(bonFeatInfo) - return +# bonFeatInfo = [] +# for ft in bonus_feats: +# bonFeatInfo.append(char_editor.FeatInfo(ft)) +# char_editor.set_bonus_feats(bonFeatInfo) +# return diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py new file mode 100644 index 000000000..08ad3c207 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py @@ -0,0 +1,18 @@ +from toee import * +import tpactions + +def GetActionName(): + return "Dragon Disciple Select Heritage" + +def GetActionDefinitionFlags(): + return D20ADF_None + +def GetTargetingClassification(): + return D20TC_Target0 + +def GetActionCostType(): + return D20ACT_NULL + +def AddToSequence(d20action, action_seq, tb_status): + action_seq.add_action(d20action) + return AEC_OK \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py new file mode 100644 index 000000000..2e1f69de1 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py @@ -0,0 +1,29 @@ +from toee import * +import tpactions +import tpdp + + +def GetActionName(): + return "Dragon Disciple Breath Attack" + + +def GetActionDefinitionFlags(): + return D20ADF_MagicEffectTargeting | D20ADF_QueryForAoO + + +def GetTargetingClassification(): + return D20TC_CastSpell + + +def GetActionCostType(): + return D20ACT_Standard_Action + + +def AddToSequence(d20action, action_seq, tb_status): + print "Dragon Disciple Breath Weapon Add to Sequence" + if d20action.performer.d20_query(Q_Prone): + d20aGetup = d20action + d20aGetup.action_type = tpdp.D20ActionType.StandUp + action_seq.add_action(d20aGetup) + action_seq.add_action(d20action) + return AEC_OK diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt deleted file mode 100644 index 76736a4a8..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage black .txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Dragon Disciple Heritage - Black -flags: 8 -prereqs: 23 1 -description: The player chooses a dragon variety when taking the first level in this prestige class. -prereq descr: Dragon Disciple level 1 -parent: Dragon Disciple Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt deleted file mode 100644 index 8a3c60164..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage blue.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Dragon Disciple Heritage - Blue -flags: 8 -prereqs: 23 1 -description: The player chooses a dragon variety when taking the first level in this prestige class. -prereq descr: Dragon Disciple level 1 -parent: Dragon Disciple Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt index 6a8439c93..9a5fef308 100644 --- a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple heritage.txt @@ -1,5 +1,5 @@ name: Dragon Disciple Heritage -flags: 524288 -prereqs: 23 1 +flags: 8 +prereqs: description: The player chooses a dragon variety when taking the first level in this prestige class. prereq descr: Dragon Disciple level 1 \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab new file mode 100644 index 000000000..6df3d8f8c --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab @@ -0,0 +1,12 @@ +sp-Dragon Disciple Cone Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Cone Breath Acid 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Acid 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Cone Breath Cold 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Cold 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Electricity smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Cone Breath Electricity 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Electricity 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Cone Breath Fire 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Fire 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 diff --git a/tpdatasrc/tpgamefiles/rules/spells/3231 - Dragon Disciple Cone Breath.txt b/tpdatasrc/tpgamefiles/rules/spells/3231 - Dragon Disciple Cone Breath.txt new file mode 100644 index 000000000..07c9d910a --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/spells/3231 - Dragon Disciple Cone Breath.txt @@ -0,0 +1,17 @@ +School: None +Casting Time: 1 action +Range: Specified 30 +Saving Throw: Reflex +Spell Resistance: No +Projectile: No +flags_Target: Range +flags_Target: Degrees +flags_Target: Fixed-radius +inc_flags_Target: Other +exc_flags_Target: Self +exc_flags_Target: Dead +exc_flags_Target: Non-critter +mode_Target: Cone +radius_Target: 60 +degrees_Target: 90 +ai_type: ai_action_offensive \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py new file mode 100644 index 000000000..fa62e4562 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py @@ -0,0 +1,61 @@ +from toee import * + +def OnBeginSpellCast(spell): + print "Dragon Disciple Cone Breath OnBeginSpellCast" + print "spell.target_list=", spell.target_list + print "spell.caster=", spell.caster, " caster.level= ", spell.caster_level + +def OnSpellEffect(spell): + print "Dragon Disciple Cone Breath OnSpellEffect" + + targetsToRemove = [] + spell.duration = 0 + spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)-10)/2) + print "Dragon Disciple Breath Weapon DC: {}".format(spell.dc) + + spellDamageDice = dice_new('1d8') + if spell.caster_level < 7: + spellDamageDice.number = 2 + elif spell.caster_level < 10: + spellDamageDice = 4 + else: + spellDamageDice = 6 + saveType = D20_Save_Reduction_Half + damageType = spell.caster.d20_query("PQ_Dragon_Disciple_Element_Type") + print "Dragon Disciple Breath Weapon Element: {}".format(damageType) + #If different Breath Weapon Types get added (e.g. Sonic) add them here + if damageType == D20DT_ACID: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ACID + particleEffect = "sp-Dragon Disciple Cone Breath Acid" + elif damageType == D20DT_COLD: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_COLD + particleEffect = "sp-Dragon Disciple Cone Breath Cold" + elif damageType == D20DT_ELECTRICITY: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ELECTRICITY + particleEffect = "sp-Dragon Disciple Cone Breath Electricity" + elif damageType == D20DT_FIRE: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_FIRE + particleEffect = "sp-Dragon Disciple Cone Breath Fire" + else: #Fallback + saveDescriptor = D20STD_F_NONE + particleEffect = "sp-Dragon Disciple Cone Breath Fire" + print "Dragon Disciple Breath Weapon Descriptor: {}".format(saveDescriptor) + + game.particles(particleEffect, spell.caster) + + for spellTarget in spell.target_list: + #Save for half damage: + if spellTarget.obj.reflex_save_and_damage(spell.caster, spell.dc, saveType, saveDescriptor, spellDamageDice, damageType, D20DAP_UNSPECIFIED, D20A_CAST_SPELL, spell.id): #success + spellTarget.obj.float_mesfile_line('mes\\spell.mes', 30001) + else: + spellTarget.obj.float_mesfile_line('mes\\spell.mes', 30002) + targetsToRemove.append(spellTarget.obj) + + spell.target_list.remove_list(targetsToRemove) + spell.spell_end(spell.id) + +def OnBeginRound(spell): + print "Dragon Disciple Cone Breath OnBeginRound" + +def OnEndSpellCast(spell): + print "Dragon Disciple Cone Breath OnEndSpellCast" \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 93188a171..b547624a9 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -1,6 +1,7 @@ from templeplus.pymod import PythonModifier from toee import * import tpdp +import tpactions import char_class_utils ################################################### @@ -15,6 +16,25 @@ def GetSpellCasterConditionName(): classEnum = stat_level_dragon_disciple classSpecModule = __import__('class023_dragon_disciple') + +selectHeritageId = 2301 +breathWeaponEnum = 2302 + +#Dict to handle Dragon Disciple Heritage +#Can easily be expanded by adding a new type of heritage to the dict +#[Colour, ElementType, Breath Weapon Shape (1 = Cone, 2 = Line)] +dictDragonHeritage = { +1: ["Black", D20DT_ACID, 2], +2: ["Blue", D20DT_ELECTRICITY, 2], +3: ["Green", D20DT_ACID, 1], +4: ["Red", D20DT_FIRE, 1], +5: ["White", D20DT_COLD, 1], +6: ["Brass", D20DT_FIRE, 2], +7: ["Bronze", D20DT_ELECTRICITY, 2], +8: ["Copper", D20DT_ACID, 2], +9: ["Gold", D20DT_FIRE, 1], +10: ["Silver", D20DT_COLD, 1] +} ################################################### @@ -121,6 +141,50 @@ def OnLevelupSpellsFinalize(attachee, args, evt_obj): ##### Dragon Disciple Class Features ##### +###Handle Heritage +def selectHeritageRadial(attachee, args, evt_obj): + if not args.get_arg(0): + radialSelectHeritageParent = tpdp.RadialMenuEntryParent("Select Dragon Disciple Heritage") + radialSelectHeritageParentId = radialSelectHeritageParent.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) + for key in dictDragonHeritage.keys(): + dragonColour = dictDragonHeritage[key][0] + radialHeritageColourId = tpdp.RadialMenuEntryPythonAction("{} Dragon Heritage".format(dragonColour), D20A_PYTHON_ACTION, selectHeritageId, key, "TAG_CLASS_FEATURES_MARSHAL_MINOR_AURAS") + radialHeritageColourId.add_as_child(attachee, radialSelectHeritageParentId) + return 0 + +def setHeritage(attachee, args, evt_obj): + chosenHeritage = evt_obj.d20a.data1 + args.set_arg(0, chosenHeritage) + return 0 + +def querySelectedHeritage(attachee, args, evt_obj): + evt_obj.return_val = args.get_arg(0) + return 0 + +def queryHeritageElementType(attachee, args, evt_obj): + heritage = args.get_arg(0) + evt_obj.return_val = dictDragonHeritage[heritage][1] + return 0 + +def queryHeritageBreathWeaponType(attachee, args, evt_obj): + heritage = args.get_arg(0) + evt_obj.return_val = dictDragonHeritage[heritage][2] + return 0 + +def initialHeritageValue(attachee, args, evt_obj): + args.set_arg(0, 0) + return 0 + +dragonHeritage = PythonModifier("Dragon Disciple Heritage", 3) #arg0 = heritage +dragonHeritage.MapToFeat("Dragon Disciple Heritage") +dragonHeritage.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, selectHeritageRadial, ()) +dragonHeritage.AddHook(ET_OnD20PythonActionPerform, selectHeritageId, setHeritage, ()) +dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Selected_Heritage", queryHeritageElementType, ()) +dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Element_Type", queryHeritageElementType, ()) +dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Breath_Weapon_Type", queryHeritageBreathWeaponType, ()) +dragonHeritage.AddHook(ET_OnConditionAdd, EK_NONE, initialHeritageValue, ()) + + ### AC Bonus def NaturalArmorACBonus(attachee, args, evt_obj): classLevel = attachee.stat_level_get(classEnum) @@ -133,7 +197,7 @@ def NaturalArmorACBonus(attachee, args, evt_obj): else: bonusValue = 4 bonusType = 0 #ID 0 = Stacking; Wrong Type as Touch Attacks should nullify this bonus - evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Natural Armor~[TAG_CLASS_FEATURES_DRAGON_DISCIPILES_NATURAL_ARMOR_INCREASE]") + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Natural Armor~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE]") return 0 naturalArmorInc = PythonModifier("Dragon Disciple Natural Armor", 0) @@ -155,6 +219,45 @@ def OnGetAbilityScore(attachee, args, evt_obj): classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) ### Breath Weapon +def breathWeaponRadial(attachee, args, evt_obj): + if args.get_arg(0): + return 0 + #I display the heritage colour in the Breath Weapon Radial + #So the player gets a visual feedback, which colour he did choose + heritage = attachee.d20_query("PQ_Dragon_Disciple_Selected_Heritage") + dragonColour = dictDragonHeritage[heritage][0] + breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") + spellId = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath + + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_INTERFACE_HELP") + spellData = tpdp.D20SpellData(spellId) + casterLevel = attachee.stat_level_get(classEnum) + spellData.set_spell_class(classEnum) + spellData.set_spell_level(casterLevel) + breathWeaponId.set_spell_data(spellData) + breathWeaponId.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) + return 0 + +def performBreathWeapon(attachee, args, evt_obj): + currentSequence = tpactions.get_cur_seq() + spellPacket = currentSequence.spell_packet + newSpellId = tpactions.get_new_spell_id() + spellPacket.caster_level = attachee.stat_level_get(classEnum) + tpactions.register_spell_cast(spellPacket, newSpellId) + tpactions.trigger_spell_effect(evt_obj.d20a.spell_id) + return 0 + + +def resetBreathWeapon(attachee, args, evt_obj): + args.set_arg(0, 0) + return 0 + +dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #arg0 = used this day +dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") +dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) +#dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) ### Blindsense From b9d12def5ba8a6f46679e923b01ac3ff48385d0f Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Fri, 17 Sep 2021 12:08:29 +0200 Subject: [PATCH 03/32] Update dragon_disciple.py --- tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index b547624a9..8e8ce6139 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -23,6 +23,8 @@ def GetSpellCasterConditionName(): #Dict to handle Dragon Disciple Heritage #Can easily be expanded by adding a new type of heritage to the dict #[Colour, ElementType, Breath Weapon Shape (1 = Cone, 2 = Line)] +#If a new element type would be added e.g. sonic or negative, +#this also would be needed to add in both spells and in the partsys dictDragonHeritage = { 1: ["Black", D20DT_ACID, 2], 2: ["Blue", D20DT_ELECTRICITY, 2], @@ -154,6 +156,7 @@ def selectHeritageRadial(attachee, args, evt_obj): def setHeritage(attachee, args, evt_obj): chosenHeritage = evt_obj.d20a.data1 + print "Selected Heritage: {}".format(chosenHeritage) args.set_arg(0, chosenHeritage) return 0 @@ -227,10 +230,10 @@ def breathWeaponRadial(attachee, args, evt_obj): heritage = attachee.d20_query("PQ_Dragon_Disciple_Selected_Heritage") dragonColour = dictDragonHeritage[heritage][0] breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") - spellId = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath + spellEnum = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_INTERFACE_HELP") - spellData = tpdp.D20SpellData(spellId) + spellData = tpdp.D20SpellData(spellEnum) casterLevel = attachee.stat_level_get(classEnum) spellData.set_spell_class(classEnum) spellData.set_spell_level(casterLevel) @@ -255,7 +258,7 @@ def resetBreathWeapon(attachee, args, evt_obj): dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #arg0 = used this day dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) -#dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) From 783606e1e10dba5a9570316bcb212028b49074af Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Mon, 20 Sep 2021 12:14:34 +0200 Subject: [PATCH 04/32] Added Ability Boost --- .../mes/help/dragon_disciple_help.tab | 1 + .../scr/tpModifiers/dragon_disciple.py | 99 ++++++++++++++++--- 2 files changed, 86 insertions(+), 14 deletions(-) diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab index 993f5f8bb..1f99cc3b1 100644 --- a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab @@ -4,6 +4,7 @@ TAG_CLASS_FEATURES_DRAGON_DISCIPLES_SPELLS_PER_DAY TAG_DRAGON_DISCIPLE Dragon TAG_CLASS_FEATURES_DRAGON_DISCIPLES_GRANTED_PROFICIENCIES TAG_DRAGON_DISCIPLE Dragon Disciple Weapon and Armor Proficiency Dragon Disciples gain no weapon or armor proficiencies. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE TAG_DRAGON_DISCIPLE Dragon Disciple Heritage The player chooses a dragon variety when taking the first level in this prestige class. TBD! TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPLE Dragon Disciple Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST TAG_DRAGON_DISCIPLE Dragon Disciple Ability Boost As a dragon disciple gains levels in this prestige class, his ability scores increase. Strength +2 on Level 2 and 4. Constitution +2 on Level 6. Intelligence +2 on Level 8. These increases stack and are gained as if through level advancement. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_CLAWS_AND_BITE TAG_DRAGON_DISCIPLE Dragon Disciple Claws and Bite At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. Use the values below or the disciple’s base claw and bite damage values, whichever are greater. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. Size Bite Damage Claw Damage Small 1d4 1d3 Medium 1d6 1d4 Large 1d8 1d6 TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 8e8ce6139..835702c08 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -207,19 +207,65 @@ def NaturalArmorACBonus(attachee, args, evt_obj): naturalArmorInc.MapToFeat("Dragon Disciple Natural Armor") naturalArmorInc.AddHook(ET_OnGetAC, EK_NONE, NaturalArmorACBonus, ()) -### Strength Bonus -def OnGetAbilityScore(attachee, args, evt_obj): +### Ability Bonus +#def OnGetAbilityScore(attachee, args, evt_obj): #statType = args.get_param(0) - lvl = attachee.stat_level_get(classEnum) - statMod = args.get_param(1) - - newValue = statMod + evt_obj.bonus_list.get_sum() - if (newValue < 3): # ensure minimum stat of 3 - statMod = 3-newValue - evt_obj.bonus_list.add(statMod, 0, 139) +# lvl = attachee.stat_level_get(classEnum) +# statMod = args.get_param(1) +# +# newValue = statMod + evt_obj.bonus_list.get_sum() +# if (newValue < 3): # ensure minimum stat of 3 +# statMod = 3-newValue +# evt_obj.bonus_list.add(statMod, 0, 139) +# return 0 + +#classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) + + +def onGetAbilityScoreStr(attachee, args, evt_obj): + level = attachee.stat_level_get(classEnum) + if level < 2: + return 0 + elif level < 4: + bonusValue = 2 + elif level < 10: + bonusValue = 4 + else: + bonusValue = 8 + bonusType = 0 #ID 0 = Untyped(stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Ability Boost~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST]") return 0 -classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) +def onGetAbilityScoreCon(attachee, args, evt_obj): + level = attachee.stat_level_get(classEnum) + if level >= 6: + bonusValue = 2 + bonusType = 0 #ID = 0 Untyped(stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Ability Boost~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST]") + return 0 + +def onGetAbilityScoreInt(attachee, args, evt_obj): + level = attachee.stat_level_get(classEnum) + if level >= 8: + bonusValue = 2 + bonusType = 0 #ID = 0 Untyped(stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Ability Boost~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST]") + return 0 + +def onGetAbilityScoreCha(attachee, args, evt_obj): + level = attachee.stat_level_get(classEnum) + if level >= 10: + bonusValue = 2 + bonusType = 0 #ID = 0 Untyped(stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "~Dragon Disciple Ability Boost~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST]") + return 0 + +classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, onGetAbilityScoreStr, ()) +classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_CONSTITUTION, onGetAbilityScoreCon, ()) +classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_INTELLIGENCE, onGetAbilityScoreInt, ()) +classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_CHARISMA, onGetAbilityScoreCha, ()) + +### Claws and Bite ### Breath Weapon def breathWeaponRadial(attachee, args, evt_obj): @@ -264,9 +310,34 @@ def resetBreathWeapon(attachee, args, evt_obj): ### Blindsense - +#TBD #### Dragon Apotheosis -# At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), -# and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, -#60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. +#Not implemented: +#Half-dragon template +#Low-light vision +#60-foot darkvision + +def sleepParalyzeImmunity(attachee, args, evt_obj): + if evt_obj.is_modifier("sp-Sleep"): + evt_obj.return_val = 0 + combatMesLine = 5059 #ID 5059: "Sleep Immunity" + historyMesLine = 31 #ID 31: {[ACTOR] is immune to ~sleep~[TAG_SPELLS_SLEEP].} + attachee.float_mesfile_line('mes\\combat.mes', combatMesLine, tf_red) + game.create_history_from_pattern(historyMesLine, attachee, OBJ_HANDLE_NULL) + elif evt_obj.is_modifier("Paralyzed"): + evt_obj.return_val = 0 + attachee.float_text_line("Paralyze Immunity", tf_red) + game.create_history_freeform("{} is immune to ~paralyze~[TAG_PARALYZED] effects\n\n".format(attachee)) + return 0 + +def elementImmunity(attachee, args, evt_obj): + elementType = attachee.d20_query("PQ_Dragon_Disciple_Element_Type") + damageMesLine = 132 #ID 132 in damage.mes is Immunity + evt_obj.damage_packet.add_mod_factor(0.0, elementType, damageMesLine) + return 0 + +dragonDiscipleApotheosis = PythonModifier("Dragon Disciple Dragon Apotheosis", 3) +dragonDiscipleApotheosis.MapToFeat("Dragon Disciple Dragon Apotheosis") +dragonDiscipleApotheosis.AddHook(ET_OnConditionAddPre, EK_NONE, sleepParalyzeImmunity, ()) +dragonDiscipleApotheosis.AddHook(ET_OnTakingDamage2, EK_NONE, elementImmunity, ()) From 0cb6e919622766fa3c65124ab2dd23ab5c35d674 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Mon, 20 Sep 2021 22:47:59 +0200 Subject: [PATCH 05/32] Fixed Heritage bug --- .../scr/tpModifiers/dragon_disciple.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 835702c08..9b4b5aed8 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -150,7 +150,7 @@ def selectHeritageRadial(attachee, args, evt_obj): radialSelectHeritageParentId = radialSelectHeritageParent.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) for key in dictDragonHeritage.keys(): dragonColour = dictDragonHeritage[key][0] - radialHeritageColourId = tpdp.RadialMenuEntryPythonAction("{} Dragon Heritage".format(dragonColour), D20A_PYTHON_ACTION, selectHeritageId, key, "TAG_CLASS_FEATURES_MARSHAL_MINOR_AURAS") + radialHeritageColourId = tpdp.RadialMenuEntryPythonAction("{} Dragon Heritage".format(dragonColour), D20A_PYTHON_ACTION, selectHeritageId, key, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE") radialHeritageColourId.add_as_child(attachee, radialSelectHeritageParentId) return 0 @@ -161,12 +161,14 @@ def setHeritage(attachee, args, evt_obj): return 0 def querySelectedHeritage(attachee, args, evt_obj): - evt_obj.return_val = args.get_arg(0) + heritage = args.get_arg(0) + evt_obj.return_val = heritage return 0 def queryHeritageElementType(attachee, args, evt_obj): heritage = args.get_arg(0) - evt_obj.return_val = dictDragonHeritage[heritage][1] + elementType = dictDragonHeritage[heritage][1] + evt_obj.return_val = elementType return 0 def queryHeritageBreathWeaponType(attachee, args, evt_obj): @@ -182,14 +184,14 @@ def initialHeritageValue(attachee, args, evt_obj): dragonHeritage.MapToFeat("Dragon Disciple Heritage") dragonHeritage.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, selectHeritageRadial, ()) dragonHeritage.AddHook(ET_OnD20PythonActionPerform, selectHeritageId, setHeritage, ()) -dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Selected_Heritage", queryHeritageElementType, ()) +dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Selected_Heritage", querySelectedHeritage, ()) dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Element_Type", queryHeritageElementType, ()) dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Breath_Weapon_Type", queryHeritageBreathWeaponType, ()) dragonHeritage.AddHook(ET_OnConditionAdd, EK_NONE, initialHeritageValue, ()) ### AC Bonus -def NaturalArmorACBonus(attachee, args, evt_obj): +def naturalArmorACBonus(attachee, args, evt_obj): classLevel = attachee.stat_level_get(classEnum) if classLevel == 1: bonusValue = 1 @@ -205,7 +207,7 @@ def NaturalArmorACBonus(attachee, args, evt_obj): naturalArmorInc = PythonModifier("Dragon Disciple Natural Armor", 0) naturalArmorInc.MapToFeat("Dragon Disciple Natural Armor") -naturalArmorInc.AddHook(ET_OnGetAC, EK_NONE, NaturalArmorACBonus, ()) +naturalArmorInc.AddHook(ET_OnGetAC, EK_NONE, naturalArmorACBonus, ()) ### Ability Bonus #def OnGetAbilityScore(attachee, args, evt_obj): @@ -278,7 +280,7 @@ def breathWeaponRadial(attachee, args, evt_obj): breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") spellEnum = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath - breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_INTERFACE_HELP") + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") spellData = tpdp.D20SpellData(spellEnum) casterLevel = attachee.stat_level_get(classEnum) spellData.set_spell_class(classEnum) @@ -328,7 +330,7 @@ def sleepParalyzeImmunity(attachee, args, evt_obj): elif evt_obj.is_modifier("Paralyzed"): evt_obj.return_val = 0 attachee.float_text_line("Paralyze Immunity", tf_red) - game.create_history_freeform("{} is immune to ~paralyze~[TAG_PARALYZED] effects\n\n".format(attachee)) + game.create_history_freeform("{} is immune to ~paralyze~[TAG_PARALYZED] effects\n\n".format(attachee.description)) return 0 def elementImmunity(attachee, args, evt_obj): From 809df430df67de21a9a004b76092329e3e7b58b2 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Tue, 21 Sep 2021 00:04:23 +0200 Subject: [PATCH 06/32] Fixed Cone Breath Weapon --- tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab | 2 +- .../rules/spell_enums/dragon_disciple_spell_enum.mes | 7 +++++++ .../scr/Spell3231 - Dragon Disciple Cone Breath.py | 7 +++++-- tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py | 6 +++--- 4 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab index 1f99cc3b1..4e379f813 100644 --- a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab @@ -6,7 +6,7 @@ TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE TAG_DRAGON_DISCIPLE Dragon Discip TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPLE Dragon Disciple Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST TAG_DRAGON_DISCIPLE Dragon Disciple Ability Boost As a dragon disciple gains levels in this prestige class, his ability scores increase. Strength +2 on Level 2 and 4. Constitution +2 on Level 6. Intelligence +2 on Level 8. These increases stack and are gained as if through level advancement. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_CLAWS_AND_BITE TAG_DRAGON_DISCIPLE Dragon Disciple Claws and Bite At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. Use the values below or the disciple’s base claw and bite damage values, whichever are greater. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. Size Bite Damage Claw Damage Small 1d4 1d3 Medium 1d6 1d4 Large 1d8 1d6 -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS TAG_DRAGON_DISCIPLE Dragon Disciple Wings At 9th level, a dragon disciple grows a set of draconic wings. He may now fly at a speed equal to his normal land speed, with average maneuverability. Not implemented in ToEE as there is flight in ToEE! TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. diff --git a/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes new file mode 100644 index 000000000..e9f50e02a --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes @@ -0,0 +1,7 @@ +// Dragon Disciple Breath Weapons + +{3231}{Dragon Disciple Cone Breath} + +{8231}{Dragon Disciple Cone Breath} + +{23231}{DRAGON_DISCIPLE_CONE_BREATH} diff --git a/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py index fa62e4562..a3a6707a1 100644 --- a/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py +++ b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py @@ -11,15 +11,18 @@ def OnSpellEffect(spell): targetsToRemove = [] spell.duration = 0 spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)-10)/2) + print "Debug Dragon Disciple Cone Breath" print "Dragon Disciple Breath Weapon DC: {}".format(spell.dc) + print "Dragon Disciple Caster Level: {}".format(spell.caster_level) spellDamageDice = dice_new('1d8') if spell.caster_level < 7: spellDamageDice.number = 2 elif spell.caster_level < 10: - spellDamageDice = 4 + spellDamageDice.number = 4 else: - spellDamageDice = 6 + spellDamageDice.number = 6 + print "spellDamageDice: {}".format(spellDamageDice) saveType = D20_Save_Reduction_Half damageType = spell.caster.d20_query("PQ_Dragon_Disciple_Element_Type") print "Dragon Disciple Breath Weapon Element: {}".format(damageType) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 9b4b5aed8..e1192b60f 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -280,7 +280,7 @@ def breathWeaponRadial(attachee, args, evt_obj): breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") spellEnum = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath - breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") spellData = tpdp.D20SpellData(spellEnum) casterLevel = attachee.stat_level_get(classEnum) spellData.set_spell_class(classEnum) @@ -295,10 +295,10 @@ def performBreathWeapon(attachee, args, evt_obj): newSpellId = tpactions.get_new_spell_id() spellPacket.caster_level = attachee.stat_level_get(classEnum) tpactions.register_spell_cast(spellPacket, newSpellId) - tpactions.trigger_spell_effect(evt_obj.d20a.spell_id) + #tpactions.trigger_spell_effect(evt_obj.d20a.spell_id) + tpactions.trigger_spell_effect(newSpellId) return 0 - def resetBreathWeapon(attachee, args, evt_obj): args.set_arg(0, 0) return 0 From 1ad5cfab9a9ab531fca6ca83c534969751cb02fc Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Tue, 21 Sep 2021 13:11:55 +0200 Subject: [PATCH 07/32] Further progress for the DD --- .../char_class/class023_dragon_disciple.py | 51 +++++++++++----- ...tion02302_dragon_disciple_breath_attack.py | 1 - .../rules/partsys/dragon_disciple_partsys.tab | 23 ++++---- .../3232 - Dragon Disciple Line Breath.txt | 15 +++++ ...Spell3232 - Dragon Disciple Line Breath.py | 59 +++++++++++++++++++ .../scr/tpModifiers/dragon_disciple.py | 26 +++++--- 6 files changed, 141 insertions(+), 34 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/spells/3232 - Dragon Disciple Line Breath.txt create mode 100644 tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 8491252cd..ef88b61b1 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -32,8 +32,6 @@ def GetClassHelpTopic(): 10: ("Dragon Disciple Dragon Apotheosis",) } -#bonus_feats = ["Dragon Disciple Heritage"] - class_skills = (skill_alchemy, skill_concentration, skill_craft, skill_diplomacy, skill_escape_artist, skill_gather_information, skill_knowledge_all, skill_listen, skill_profession, skill_search, skill_spellcraft, skill_spot) def IsEnabled(): @@ -107,16 +105,39 @@ def ObjMeetsPrereqs(obj): # Levelup -#def IsSelectingFeatsOnLevelup(obj): -# newLvl = obj.stat_level_get( classEnum ) + 1 -# if newLvl == 1: -# return 1 -# return 0 - -#def LevelupGetBonusFeats(obj): -#Lazy copy + paste from fighter; could be cleaned up as it is only one feat -# bonFeatInfo = [] -# for ft in bonus_feats: -# bonFeatInfo.append(char_editor.FeatInfo(ft)) -# char_editor.set_bonus_feats(bonFeatInfo) -# return + +def IsSelectingFeatsOnLevelup(obj): + return 0 + +def LevelupGetBonusFeats(obj): + return + +def IsSelectingSpellsOnLevelup( obj , class_extended_1 = 0): + if class_extended_1 <= 0: + class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) + if char_editor.is_selecting_spells(obj, class_extended_1): + return 1 + return 0 + + +def LevelupCheckSpells(obj, class_extended_1 = 0): + if class_extended_1 <= 0: + class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) + if not char_editor.spells_check_complete(obj, class_extended_1): + return 0 + return 1 + +def InitSpellSelection(obj , class_extended_1 = 0): + newLvl = obj.stat_level_get( classEnum ) + 1 + levelsWithoutNewSpells = [3, 7, 10] + if not newLvl in levelsWithoutNewSpells: + if class_extended_1 <= 0: + class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) + char_editor.init_spell_selection(obj, class_extended_1) + return 0 + +def LevelupSpellsFinalize(obj , class_extended_1 = 0): + if class_extended_1 <= 0: + class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) + char_editor.spells_finalize(obj, class_extended_1) + return 0 \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py index 2e1f69de1..f210d80d9 100644 --- a/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py +++ b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py @@ -20,7 +20,6 @@ def GetActionCostType(): def AddToSequence(d20action, action_seq, tb_status): - print "Dragon Disciple Breath Weapon Add to Sequence" if d20action.performer.d20_query(Q_Prone): d20aGetup = d20action d20aGetup.action_type = tpdp.D20ActionType.StandUp diff --git a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab index 6df3d8f8c..582c0e166 100644 --- a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab +++ b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab @@ -1,12 +1,15 @@ -sp-Dragon Disciple Cone Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Cone Breath Acid 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Acid 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?360 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Dragon Disciple Cone Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Dragon Disciple Cone Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 sp-Dragon Disciple Cone Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Cone Breath Cold 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Cold 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 -sp-Dragon Disciple Cone Breath Electricity smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Cone Breath Electricity 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Electricity 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Cone Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 +sp-Dragon Disciple Cone Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Cone Breath Fire 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Fire 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Cone Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 +sp-Dragon Disciple Cone Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 +sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0,5 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0,5 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,5 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 diff --git a/tpdatasrc/tpgamefiles/rules/spells/3232 - Dragon Disciple Line Breath.txt b/tpdatasrc/tpgamefiles/rules/spells/3232 - Dragon Disciple Line Breath.txt new file mode 100644 index 000000000..1073d15d1 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/spells/3232 - Dragon Disciple Line Breath.txt @@ -0,0 +1,15 @@ +School: None +Casting Time: 1 action +Range: Specified 60 +Saving Throw: Reflex +Spell Resistance: No +Projectile: No +flags_Target: Range +flags_Target: Radius +inc_flags_Target: Other +exc_flags_Target: Self +exc_flags_Target: Dead +exc_flags_Target: Non-critter +mode_Target: Ray +radius_Target: 5 +ai_type: ai_action_offensive \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py new file mode 100644 index 000000000..291788edb --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py @@ -0,0 +1,59 @@ +from toee import * + +def OnBeginSpellCast(spell): + print "Dragon Disciple Line Breath OnBeginSpellCast" + print "spell.target_list=", spell.target_list + print "spell.caster=", spell.caster, " caster.level= ", spell.caster_level + +def OnSpellEffect(spell): + print "Dragon Disciple Line Breath OnSpellEffect" + + targetsToRemove = [] + spell.duration = 0 + spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)-10)/2) + + spellDamageDice = dice_new('1d8') + if spell.caster_level < 7: + spellDamageDice.number = 2 + elif spell.caster_level < 10: + spellDamageDice.number = 4 + else: + spellDamageDice.number = 6 + saveType = D20_Save_Reduction_Half + damageType = spell.caster.d20_query("PQ_Dragon_Disciple_Element_Type") + #If different Breath Weapon Types get added (e.g. Sonic) add them here + if damageType == D20DT_ACID: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ACID + particleEffect = "sp-Dragon Disciple Line Breath Acid" + elif damageType == D20DT_COLD: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_COLD + particleEffect = "sp-Dragon Disciple Line Breath Cold" + elif damageType == D20DT_ELECTRICITY: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ELECTRICITY + particleEffect = "sp-Dragon Disciple Line Breath Electricity" + elif damageType == D20DT_FIRE: + saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_FIRE + particleEffect = "sp-Dragon Disciple Line Breath Fire" + else: #Fallback + saveDescriptor = D20STD_F_NONE + particleEffect = "sp-Dragon Disciple Line Breath Fire" + + spell.caster.turn_towards(spell.target_list[0].obj) + game.particles(particleEffect, spell.caster) + + for spellTarget in spell.target_list: + #Save for half damage: + if spellTarget.obj.reflex_save_and_damage(spell.caster, spell.dc, saveType, saveDescriptor, spellDamageDice, damageType, D20DAP_UNSPECIFIED, D20A_CAST_SPELL, spell.id): #success + spellTarget.obj.float_mesfile_line('mes\\spell.mes', 30001) + else: + spellTarget.obj.float_mesfile_line('mes\\spell.mes', 30002) + targetsToRemove.append(spellTarget.obj) + + spell.target_list.remove_list(targetsToRemove) + spell.spell_end(spell.id) + +def OnBeginRound(spell): + print "Dragon Disciple Line Breath OnBeginRound" + +def OnEndSpellCast(spell): + print "Dragon Disciple Line Breath OnEndSpellCast" \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index e1192b60f..18a0fbe22 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -180,7 +180,7 @@ def initialHeritageValue(attachee, args, evt_obj): args.set_arg(0, 0) return 0 -dragonHeritage = PythonModifier("Dragon Disciple Heritage", 3) #arg0 = heritage +dragonHeritage = PythonModifier("Dragon Disciple Heritage", 3) #heritage, empty, empty dragonHeritage.MapToFeat("Dragon Disciple Heritage") dragonHeritage.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, selectHeritageRadial, ()) dragonHeritage.AddHook(ET_OnD20PythonActionPerform, selectHeritageId, setHeritage, ()) @@ -271,8 +271,8 @@ def onGetAbilityScoreCha(attachee, args, evt_obj): ### Breath Weapon def breathWeaponRadial(attachee, args, evt_obj): - if args.get_arg(0): - return 0 + chargesLeft = args.get_arg(0) + maxCharges = args.get_arg(1) #I display the heritage colour in the Breath Weapon Radial #So the player gets a visual feedback, which colour he did choose heritage = attachee.d20_query("PQ_Dragon_Disciple_Selected_Heritage") @@ -280,7 +280,7 @@ def breathWeaponRadial(attachee, args, evt_obj): breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") spellEnum = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath - breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({})".format(dragonColour), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") + breathWeaponId = tpdp.RadialMenuEntryPythonAction("{} Breath Weapon {}/{}".format(dragonColour, chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") spellData = tpdp.D20SpellData(spellEnum) casterLevel = attachee.stat_level_get(classEnum) spellData.set_spell_class(classEnum) @@ -289,23 +289,33 @@ def breathWeaponRadial(attachee, args, evt_obj): breathWeaponId.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) return 0 +def chargesCheckBreathWeapon(attachee, args, evt_obj): + if args.get_arg(0) < 1: + evt_obj.return_val = AEC_OUT_OF_CHARGES + return 0 + def performBreathWeapon(attachee, args, evt_obj): + chargesLeft = args.get_arg(0) currentSequence = tpactions.get_cur_seq() spellPacket = currentSequence.spell_packet newSpellId = tpactions.get_new_spell_id() spellPacket.caster_level = attachee.stat_level_get(classEnum) tpactions.register_spell_cast(spellPacket, newSpellId) - #tpactions.trigger_spell_effect(evt_obj.d20a.spell_id) tpactions.trigger_spell_effect(newSpellId) + chargesLeft -= 1 + args.set_arg(0, chargesLeft) return 0 def resetBreathWeapon(attachee, args, evt_obj): - args.set_arg(0, 0) + maxCharges = 1 + args.set_arg(0, maxCharges) + args.set_arg(1, maxCharges) return 0 -dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #arg0 = used this day +dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #chargesLeft, maxCharges, empty dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, chargesCheckBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) @@ -339,7 +349,7 @@ def elementImmunity(attachee, args, evt_obj): evt_obj.damage_packet.add_mod_factor(0.0, elementType, damageMesLine) return 0 -dragonDiscipleApotheosis = PythonModifier("Dragon Disciple Dragon Apotheosis", 3) +dragonDiscipleApotheosis = PythonModifier("Dragon Disciple Dragon Apotheosis", 3) #empty, empty, empty dragonDiscipleApotheosis.MapToFeat("Dragon Disciple Dragon Apotheosis") dragonDiscipleApotheosis.AddHook(ET_OnConditionAddPre, EK_NONE, sleepParalyzeImmunity, ()) dragonDiscipleApotheosis.AddHook(ET_OnTakingDamage2, EK_NONE, elementImmunity, ()) From 6e4d1e02056c472a1dc23486018bc5663b1fa964 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Wed, 22 Sep 2021 12:16:48 +0200 Subject: [PATCH 08/32] Finished help file for DD --- .../mes/help/dragon_disciple_help.tab | 10 +- .../char_class/class023_dragon_disciple.py | 30 +--- .../rules/feats/dragon disciple wings.txt | 5 + .../rules/partsys/dragon_disciple_partsys.tab | 15 +- ...Spell3232 - Dragon Disciple Line Breath.py | 1 - .../scr/tpModifiers/dragon_disciple.py | 159 ++++++++++-------- 6 files changed, 109 insertions(+), 111 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple wings.txt diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab index 4e379f813..465a4ffdc 100644 --- a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab @@ -1,12 +1,12 @@ -TAG_DRAGON_DISCIPLE TAG_PRESTIGE_CLASSES Dragon Disciple Dragon Disciple Short Description - TBD!. Most Dragon Disciples are barbarian, fighters, or rangers who have dabbed as bards or sorcerers. Occasionally, a serious spellcaster explores the path to further a goal of finding out more about his draconic heritage. Hit Die: d12 Requirements: To qualify to become a Dragon Disciple, a character must fulfill all the following criteria. Race: Any non dragon (cannot be already a half-dragon). Skills: Knowledge(Arcana) 8 ranks (not implemented). Languages: Draconic (not implemented). Spellcasting: Ability to cast arcane spells without preparation. Special: The player chooses a dragon variety when taking the first level in this prestige class. Base Attack and Base Save Bonuses: see ~table~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES] Class Skills: ~Concentration(Con)~[TAG_CONCENTRATION], Craft(Int), ~Diplomacy(Cha)~[TAG_DIPLOMACY], Escape Artist(Dex), ~Gather Information(Cha)~[TAG_GATHER_INFORMATION], Knowledge(all skills), ~Listen(Wis)~[TAG_LISTEN], Profession(Wis), ~Search(Int)~[TAG_SEARCH], Speak Language(None), ~Spellcraft(Int)~[TAG_SPELLCRAFT], and ~Spot(Wis)~[TAG_SPOT]. Skill Points at Each Level: 2 + Int modifier Class Features: [CMD_CHILDREN] -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES TAG_DRAGON_DISCIPLE Dragon Disciple Base Attack & Base Save Bonuses - TBD! Level ~BAB~[TAG_LEVEL_BONUSES] ~Fort.~[TAG_FORTITUDE] ~Reflex~[TAG_REFLEX] ~Will~[TAG_WILL] 1 +0 +2 +0 +2 2 +1 +3 +0 +3 3 +2 +3 +1 +3 4 +3 +3 +1 +3 5 +3 +4 +1 +4 6 +4 +5 +2 +5 7 +5 +5 +2 +5 8 +6 +6 +2 +6 9 +6 +6 +3 +6 10 +7 +7 +3 +7 -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_SPELLS_PER_DAY TAG_DRAGON_DISCIPLE Dragon Disciple Bonus Spells - TBD! At each stormlord level, the character gains new spells per day (and spells known, if applicable) as if he had also gained a level in a divine spellcasting class to which he belonged before adding the prestige class level. He does not, however, gain any other benefit a character of that class would have gained (improved chance of turning or destroying undead, additional favored enemies, and so on). If the character had more than one divine spellcasting class before becoming a stormlord, the player must decide to which class to add each stormlord level for the purpose of determining spells per day and spells known. +TAG_DRAGON_DISCIPLE TAG_PRESTIGE_CLASSES Dragon Disciple The blood of dragons runs through the veins of many races. For some, this heritage manifests as a sorcerous bloodline and a predilection for magic. But a few embrace the dreams, recognizing their allure as a promise. These become dragon disciples, who use their magical power as a catalyst to ignite their dragon blood, realizing its fullest potential. Most Dragon Disciples are barbarian, fighters, or rangers who have dabbed as bards or sorcerers. Occasionally, a serious spellcaster explores the path to further a goal of finding out more about his draconic heritage. Hit Die: d12 Requirements: To qualify to become a Dragon Disciple, a character must fulfill all the following criteria. Race: Any non dragon (cannot be already a half-dragon). Skills: Knowledge(Arcana) 8 ranks (not implemented). Languages: Draconic (not implemented). Spellcasting: Ability to cast arcane spells without preparation. Special: The player chooses a dragon variety when taking the first level in this prestige class. Base Attack and Base Save Bonuses: see ~table~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES] Class Skills: ~Concentration(Con)~[TAG_CONCENTRATION], Craft(Int), ~Diplomacy(Cha)~[TAG_DIPLOMACY], Escape Artist(Dex), ~Gather Information(Cha)~[TAG_GATHER_INFORMATION], Knowledge(all skills), ~Listen(Wis)~[TAG_LISTEN], Profession(Wis), ~Search(Int)~[TAG_SEARCH], Speak Language(None), ~Spellcraft(Int)~[TAG_SPELLCRAFT], and ~Spot(Wis)~[TAG_SPOT]. Skill Points at Each Level: 2 + Int modifier Class Features: [CMD_CHILDREN] +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_TABLES TAG_DRAGON_DISCIPLE Dragon Disciple Base Attack & Base Save Bonuses Level ~BAB~[TAG_LEVEL_BONUSES] ~Fort.~[TAG_FORTITUDE] ~Reflex~[TAG_REFLEX] ~Will~[TAG_WILL] 1 +0 +2 +0 +2 2 +1 +3 +0 +3 3 +2 +3 +1 +3 4 +3 +4 +1 +4 5 +3 +4 +1 +4 6 +4 +5 +2 +5 7 +5 +5 +2 +5 8 +6 +6 +2 +6 9 +6 +6 +3 +6 10 +7 +7 +3 +7 +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_SPELLS_PER_DAY TAG_DRAGON_DISCIPLE Dragon Disciple Bonus Spells Dragon disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score at level 1, 2, 4, 5, 6, 8 and 9. A bonus spell can be added to any level of spells the disciple already has the ability to cast. If a character has more than one spellcasting class, he must decide to which class he adds each bonus spell as it is gained. Once a bonus spell has been applied, it cannot be shifted. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_GRANTED_PROFICIENCIES TAG_DRAGON_DISCIPLE Dragon Disciple Weapon and Armor Proficiency Dragon Disciples gain no weapon or armor proficiencies. -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE TAG_DRAGON_DISCIPLE Dragon Disciple Heritage The player chooses a dragon variety when taking the first level in this prestige class. TBD! +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE TAG_DRAGON_DISCIPLE Dragon Disciple Heritage The player chooses a dragon heritage when taking the first level in this prestige class. This determines the kind of ~Breath Weapon~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON] and ~Element Immunity~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS] the Disciple will get as he progresses in levels. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPLE Dragon Disciple Natural Armor Increase A dragon disciple gains an increase to the character's existing natural armor (if any). At 1st level +1, at 4th level +2, and at 7th level +3. As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST TAG_DRAGON_DISCIPLE Dragon Disciple Ability Boost As a dragon disciple gains levels in this prestige class, his ability scores increase. Strength +2 on Level 2 and 4. Constitution +2 on Level 6. Intelligence +2 on Level 8. These increases stack and are gained as if through level advancement. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_CLAWS_AND_BITE TAG_DRAGON_DISCIPLE Dragon Disciple Claws and Bite At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. Use the values below or the disciple’s base claw and bite damage values, whichever are greater. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. Size Bite Damage Claw Damage Small 1d4 1d3 Medium 1d6 1d4 Large 1d8 1d6 TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS TAG_DRAGON_DISCIPLE Dragon Disciple Wings At 9th level, a dragon disciple grows a set of draconic wings. He may now fly at a speed equal to his normal land speed, with average maneuverability. Not implemented in ToEE as there is flight in ToEE! -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (as noted above), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (6d8), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index ef88b61b1..6456d13bc 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -29,6 +29,7 @@ def GetClassHelpTopic(): 2: ("Dragon Disciple Claws and Bite",), 3: ("Dragon Disciple Breath Weapon",), 5: ("Dragon Disciple Blindsense",), +9: ("Dragon Disciple Wings",), 10: ("Dragon Disciple Dragon Apotheosis",) } @@ -112,32 +113,3 @@ def IsSelectingFeatsOnLevelup(obj): def LevelupGetBonusFeats(obj): return -def IsSelectingSpellsOnLevelup( obj , class_extended_1 = 0): - if class_extended_1 <= 0: - class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) - if char_editor.is_selecting_spells(obj, class_extended_1): - return 1 - return 0 - - -def LevelupCheckSpells(obj, class_extended_1 = 0): - if class_extended_1 <= 0: - class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) - if not char_editor.spells_check_complete(obj, class_extended_1): - return 0 - return 1 - -def InitSpellSelection(obj , class_extended_1 = 0): - newLvl = obj.stat_level_get( classEnum ) + 1 - levelsWithoutNewSpells = [3, 7, 10] - if not newLvl in levelsWithoutNewSpells: - if class_extended_1 <= 0: - class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) - char_editor.init_spell_selection(obj, class_extended_1) - return 0 - -def LevelupSpellsFinalize(obj , class_extended_1 = 0): - if class_extended_1 <= 0: - class_extended_1 = char_class_utils.GetHighestArcaneClass(obj) - char_editor.spells_finalize(obj, class_extended_1) - return 0 \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple wings.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple wings.txt new file mode 100644 index 000000000..5009f2cd5 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple wings.txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Wings +flags: 8 +prereqs: +description: TBD! +prereq descr: Dragon Disciple level 9 \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab index 582c0e166..8279f26f3 100644 --- a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab +++ b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab @@ -10,6 +10,15 @@ sp-Dragon Disciple Cone Breath Electricity water droplets 15 30 30 Object YPR sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 sp-Dragon Disciple Cone Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 sp-Dragon Disciple Cone Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 -sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0,5 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 -sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0,5 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 -sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0,5 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 +sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 +sp-Dragon Disciple Line Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Line Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Line Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Line Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Line Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 +sp-Dragon Disciple Line Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 +sp-Dragon Disciple Line Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Line Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 +sp-Dragon Disciple Line Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 diff --git a/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py index 291788edb..ed892ab77 100644 --- a/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py +++ b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py @@ -38,7 +38,6 @@ def OnSpellEffect(spell): saveDescriptor = D20STD_F_NONE particleEffect = "sp-Dragon Disciple Line Breath Fire" - spell.caster.turn_towards(spell.target_list[0].obj) game.particles(particleEffect, spell.caster) for spellTarget in spell.target_list: diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 18a0fbe22..424ab8470 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -69,78 +69,6 @@ def OnGetSaveThrowWill(attachee, args, evt_obj): classSpecObj.AddHook(ET_OnSaveThrowLevel, EK_SAVE_REFLEX, OnGetSaveThrowReflex, ()) classSpecObj.AddHook(ET_OnSaveThrowLevel, EK_SAVE_WILL, OnGetSaveThrowWill, ()) - -#region Spell casting - -# configure the spell casting condition to hold the highest Arcane classs -def OnAddSpellCasting(attachee, args, evt_obj): - #arg0 holds the arcane class - if (args.get_arg(0) == 0): - args.set_arg(0, char_class_utils.GetHighestArcaneClass(attachee)) - - return 0 - -# Extend caster level for base casting class -def OnGetBaseCasterLevel(attachee, args, evt_obj): - class_extended_1 = args.get_arg(0) - class_code = evt_obj.arg0 - if (class_code != class_extended_1): - if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution - return 0 - classLvl = attachee.stat_level_get(classEnum) - if classLvl > 1: - evt_obj.bonus_list.add(classLvl - 1, 0, 137) - return 0 - -def OnSpellListExtensionGet(attachee, args, evt_obj): - class_extended_1 = args.get_arg(0) - class_code = evt_obj.arg0 - if (class_code != class_extended_1): - if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution - return 0 - classLvl = attachee.stat_level_get(classEnum) - if classLvl > 1: - evt_obj.bonus_list.add(classLvl - 1, 0, 137) - return 0 - -def OnInitLevelupSpellSelection(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - classLvl = attachee.stat_level_get(classEnum) - if (classLvl == 0): - return 0 - class_extended_1 = args.get_arg(0) - classSpecModule.InitSpellSelection(attachee, class_extended_1) - return 0 - -def OnLevelupSpellsCheckComplete(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - class_extended_1 = args.get_arg(0) - if (not classSpecModule.LevelupCheckSpells(attachee, class_extended_1) ): - evt_obj.bonus_list.add(-1, 0, 137) # denotes incomplete spell selection - return 1 - -def OnLevelupSpellsFinalize(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - classLvl = attachee.stat_level_get(classEnum) - if (classLvl == 0): - return 0 - class_extended_1 = args.get_arg(0) - classSpecModule.LevelupSpellsFinalize(attachee, class_extended_1) - return - -spellCasterSpecObj = PythonModifier(GetSpellCasterConditionName(), 8) -spellCasterSpecObj.AddHook(ET_OnConditionAdd, EK_NONE, OnAddSpellCasting, ()) -spellCasterSpecObj.AddHook(ET_OnGetBaseCasterLevel, EK_NONE, OnGetBaseCasterLevel, ()) -spellCasterSpecObj.AddHook(ET_OnSpellListExtensionGet, EK_NONE, OnSpellListExtensionGet, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Activate, OnInitLevelupSpellSelection, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Check_Complete, OnLevelupSpellsCheckComplete, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Finalize, OnLevelupSpellsFinalize, ()) - -#endregion - ##### Dragon Disciple Class Features ##### ###Handle Heritage @@ -158,6 +86,9 @@ def setHeritage(attachee, args, evt_obj): chosenHeritage = evt_obj.d20a.data1 print "Selected Heritage: {}".format(chosenHeritage) args.set_arg(0, chosenHeritage) + #Visual Feedback for selected Heritage + heritageColour = dictDragonHeritage[chosenHeritage][0] + attachee.float_text_line("{} Heritage chosen".format(heritageColour)) return 0 def querySelectedHeritage(attachee, args, evt_obj): @@ -324,12 +255,22 @@ def resetBreathWeapon(attachee, args, evt_obj): ### Blindsense #TBD +###Wings +def addWings(attachee, args, evt_obj): + #meshId = + #evt_obj.append(meshId) + return 0 + +dragonDiscipleWings = PythonModifier("Dragon Disciple Dragon Wings", 3) #empty, empty, empty +dragonDiscipleWings.MapToFeat("Dragon Disciple Dragon Wings") +dragonDiscipleWings.AddHook(ET_OnAddMesh, EK_NONE, addWings, ()) + + #### Dragon Apotheosis #Not implemented: #Half-dragon template #Low-light vision #60-foot darkvision - def sleepParalyzeImmunity(attachee, args, evt_obj): if evt_obj.is_modifier("sp-Sleep"): evt_obj.return_val = 0 @@ -353,3 +294,75 @@ def elementImmunity(attachee, args, evt_obj): dragonDiscipleApotheosis.MapToFeat("Dragon Disciple Dragon Apotheosis") dragonDiscipleApotheosis.AddHook(ET_OnConditionAddPre, EK_NONE, sleepParalyzeImmunity, ()) dragonDiscipleApotheosis.AddHook(ET_OnTakingDamage2, EK_NONE, elementImmunity, ()) + + +#region Spell casting + +# configure the spell casting condition to hold the highest Arcane classs +def OnAddSpellCasting(attachee, args, evt_obj): + #arg0 holds the arcane class + if (args.get_arg(0) == 0): + args.set_arg(0, char_class_utils.GetHighestArcaneClass(attachee)) + + return 0 + +# Extend caster level for base casting class +def OnGetBaseCasterLevel(attachee, args, evt_obj): + class_extended_1 = args.get_arg(0) + class_code = evt_obj.arg0 + if (class_code != class_extended_1): + if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution + return 0 + classLvl = attachee.stat_level_get(classEnum) + if classLvl > 1: + evt_obj.bonus_list.add(classLvl - 1, 0, 137) + return 0 + +def OnSpellListExtensionGet(attachee, args, evt_obj): + class_extended_1 = args.get_arg(0) + class_code = evt_obj.arg0 + if (class_code != class_extended_1): + if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution + return 0 + classLvl = attachee.stat_level_get(classEnum) + if classLvl > 1: + evt_obj.bonus_list.add(classLvl - 1, 0, 137) + return 0 + +def OnInitLevelupSpellSelection(attachee, args, evt_obj): + if (evt_obj.arg0 != classEnum): + return 0 + classLvl = attachee.stat_level_get(classEnum) + if (classLvl == 0): + return 0 + class_extended_1 = args.get_arg(0) + classSpecModule.InitSpellSelection(attachee, class_extended_1) + return 0 + +def OnLevelupSpellsCheckComplete(attachee, args, evt_obj): + if (evt_obj.arg0 != classEnum): + return 0 + class_extended_1 = args.get_arg(0) + if (not classSpecModule.LevelupCheckSpells(attachee, class_extended_1) ): + evt_obj.bonus_list.add(-1, 0, 137) # denotes incomplete spell selection + return 1 + +def OnLevelupSpellsFinalize(attachee, args, evt_obj): + if (evt_obj.arg0 != classEnum): + return 0 + classLvl = attachee.stat_level_get(classEnum) + if (classLvl == 0): + return 0 + class_extended_1 = args.get_arg(0) + classSpecModule.LevelupSpellsFinalize(attachee, class_extended_1) + return + +spellCasterSpecObj = PythonModifier(GetSpellCasterConditionName(), 8) +spellCasterSpecObj.AddHook(ET_OnConditionAdd, EK_NONE, OnAddSpellCasting, ()) +spellCasterSpecObj.AddHook(ET_OnGetBaseCasterLevel, EK_NONE, OnGetBaseCasterLevel, ()) +spellCasterSpecObj.AddHook(ET_OnSpellListExtensionGet, EK_NONE, OnSpellListExtensionGet, ()) +spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Activate, OnInitLevelupSpellSelection, ()) +spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Check_Complete, OnLevelupSpellsCheckComplete, ()) +spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Finalize, OnLevelupSpellsFinalize, ()) + +#endregion \ No newline at end of file From 7fcad2c2abf27d9218b897e0f3aea817769e1d36 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Thu, 23 Sep 2021 10:00:11 +0200 Subject: [PATCH 09/32] DD: Dropped Blindsense, fixed fire smoke --- .../mes/help/dragon_disciple_help.tab | 4 +-- .../dragon_disciple_breath_weapons.mes | 1 + .../char_class/class023_dragon_disciple.py | 1 - ...tion02302_dragon_disciple_breath_attack.py | 2 +- .../feats/dragon disciple blindsense.txt | 7 ----- .../rules/partsys/dragon_disciple_partsys.tab | 26 +++++++++---------- .../dragon_disciple_spell_enum.mes | 3 +++ .../scr/tpModifiers/dragon_disciple.py | 20 +++++++++++--- 8 files changed, 36 insertions(+), 28 deletions(-) delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt diff --git a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab index 465a4ffdc..0a420c2e6 100644 --- a/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/dragon_disciple_help.tab @@ -7,6 +7,6 @@ TAG_CLASS_FEATURES_DRAGON_DISCIPLES_NATURAL_ARMOR_INCREASE TAG_DRAGON_DISCIPLE TAG_CLASS_FEATURES_DRAGON_DISCIPLES_ABILITY_BOOST TAG_DRAGON_DISCIPLE Dragon Disciple Ability Boost As a dragon disciple gains levels in this prestige class, his ability scores increase. Strength +2 on Level 2 and 4. Constitution +2 on Level 6. Intelligence +2 on Level 8. These increases stack and are gained as if through level advancement. TAG_CLASS_FEATURES_DRAGON_DISCIPLES_CLAWS_AND_BITE TAG_DRAGON_DISCIPLE Dragon Disciple Claws and Bite At 2nd level, a dragon disciple gains claw and bite attacks if he does not already have them. Use the values below or the disciple’s base claw and bite damage values, whichever are greater. A dragon disciple is considered proficient with these attacks. When making a full attack, a dragon disciple uses his full base attack bonus with his bite attack but takes a -5 penalty on claw attacks. The Multiattack feat reduces this penalty to only -2. Size Bite Damage Claw Damage Small 1d4 1d3 Medium 1d6 1d4 Large 1d8 1d6 TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON TAG_DRAGON_DISCIPLE Dragon Disciple Breath Weapon At 3rd level, a dragon disciple gains a minor breath weapon. The type and shape depend on the dragon variety whose heritage he enjoys (see below). Regardless of the ancestor, the breath weapon deals 2d8 points of damage of the appropriate energy type. At 7th level, the damage increases to 4d8, and when a disciple attains dragon apotheosis at 10th level it reaches its full power at 6d8. Regardless of its strength, the breath weapon can be used only once per day. Use all the rules for dragon breath weapons except as specified here. Dragon Heritage Breath Weapon Black Line of acid Blue Line of lightning Green Cone of acid Red Cone of fire White Cone of Cold Brass Line of Fire Bronze Line of Lightning Copper Line of acid Gold Cone of Fire Silver Cone of cold The DC of the breath weapon is 10 + class level + Con modifier. A line-shaped breath weapon is 5 feet high, 5 feet wide, and 60 feet long. A cone-shaped breath weapon is 30 feet long. -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BLINDSENSE TAG_DRAGON_DISCIPLE Dragon Disciple Blindsense At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. Not implemented in ToEE, has no real gameplay effect TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS TAG_DRAGON_DISCIPLE Dragon Disciple Wings At 9th level, a dragon disciple grows a set of draconic wings. He may now fly at a speed equal to his normal land speed, with average maneuverability. Not implemented in ToEE as there is flight in ToEE! -TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (6d8), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. +TAG_CLASS_FEATURES_DRAGON_DISCIPLES_DRAGON_APOTHEOSIS TAG_DRAGON_DISCIPLE Dragon Disciple Dragon Apotheosis At 10th level, a dragon disciple takes on the half-dragon template. His breath weapon reaches full strength (6d8), and he gains +4 to Strength and +2 to Charisma. His natural armor bonus increases to +4, and he acquires low-light vision, 60-foot darkvision, immunity to sleep and paralysis effects, and immunity to the energy type used by his breath weapon. Note: Not implemented: low-light vision, darkvision, half-dragon template. diff --git a/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes b/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes index 994e549be..da1c584ea 100644 --- a/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes +++ b/tpdatasrc/tpgamefiles/mes/spell_ext/dragon_disciple_breath_weapons.mes @@ -1 +1,2 @@ {3231} {Dragon Disciple Cone Breath} +{3232} {Dragon Disciple Line Breath} diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 6456d13bc..927d1e561 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -28,7 +28,6 @@ def GetClassHelpTopic(): 1: ("Dragon Disciple Heritage", "Dragon Disciple Natural Armor",), 2: ("Dragon Disciple Claws and Bite",), 3: ("Dragon Disciple Breath Weapon",), -5: ("Dragon Disciple Blindsense",), 9: ("Dragon Disciple Wings",), 10: ("Dragon Disciple Dragon Apotheosis",) } diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py index f210d80d9..5c099a95a 100644 --- a/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py +++ b/tpdatasrc/tpgamefiles/rules/d20_actions/action02302_dragon_disciple_breath_attack.py @@ -8,7 +8,7 @@ def GetActionName(): def GetActionDefinitionFlags(): - return D20ADF_MagicEffectTargeting | D20ADF_QueryForAoO + return D20ADF_MagicEffectTargeting | D20ADF_QueryForAoO | D20ADF_TriggersCombat def GetTargetingClassification(): diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt deleted file mode 100644 index 4064a60db..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple blindsense.txt +++ /dev/null @@ -1,7 +0,0 @@ -name: Dragon Disciple Blindsense -flags: 8 -prereqs: -description: At 5th level, the dragon disciple gains blindsense with a range of 30 feet. Using nonvisual senses the dragon disciple notices things it cannot see. He usually does not need to make Spot or Listen checks to notice and pinpoint the location of creatures within range of his blindsense ability, provided that he has line of effect to that creature. - -Any opponent the dragon disciple cannot see still has total concealment against him, and the dragon disciple still has the normal miss chance when attacking foes that have concealment. Visibility still affects the movement of a creature with blindsense. A creature with blindsense is still denied its Dexterity bonus to Armor Class against attacks from creatures it cannot see. At 10th level, the range of this ability increases to 60 feet. -prereq descr: Dragon Disciple level 5. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab index 8279f26f3..32788c940 100644 --- a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab +++ b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab @@ -7,18 +7,18 @@ sp-Dragon Disciple Cone Breath Cold fire strike 3 sparklies 30 50 Object YPR sp-Dragon Disciple Cone Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 sp-Dragon Disciple Cone Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 sp-Dragon Disciple Cone Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 -sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 sp-Dragon Disciple Cone Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 sp-Dragon Disciple Cone Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 -sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 -sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 -sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 -sp-Dragon Disciple Line Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Line Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Line Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 -sp-Dragon Disciple Line Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Line Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 -sp-Dragon Disciple Line Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 -sp-Dragon Disciple Line Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 0?5 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Line Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 -sp-Dragon Disciple Line Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?5 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 +sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 +sp-Dragon Disciple Line Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Dragon Disciple Line Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Line Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Dragon Disciple Line Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Dragon Disciple Line Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 +sp-Dragon Disciple Line Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 +sp-Dragon Disciple Line Breath Fire smoke emit 10 20 400 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 +sp-Dragon Disciple Line Breath Fire Fire Strike 30 50 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 +sp-Dragon Disciple Line Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 diff --git a/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes index e9f50e02a..0780f3878 100644 --- a/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes +++ b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes @@ -1,7 +1,10 @@ // Dragon Disciple Breath Weapons {3231}{Dragon Disciple Cone Breath} +{3232}{Dragon Disciple Line Breath} {8231}{Dragon Disciple Cone Breath} +{8232}{Dragon Disciple Line Breath} {23231}{DRAGON_DISCIPLE_CONE_BREATH} +{23232}{DRAGON_DISCIPLE_LINE_BREATH} diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 424ab8470..1a16f0c99 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -220,19 +220,30 @@ def breathWeaponRadial(attachee, args, evt_obj): breathWeaponId.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) return 0 -def chargesCheckBreathWeapon(attachee, args, evt_obj): +def checkBreathWeapon(attachee, args, evt_obj): if args.get_arg(0) < 1: evt_obj.return_val = AEC_OUT_OF_CHARGES return 0 def performBreathWeapon(attachee, args, evt_obj): - chargesLeft = args.get_arg(0) + target = evt_obj.d20a.target + #attachee.turn_towards(target) + if attachee.anim_goal_push_attack(target, 0, 0 ,0): + new_anim_id = attachee.anim_goal_get_new_id() + evt_obj.d20a.flags |= D20CAF_NEED_ANIM_COMPLETED + evt_obj.d20a.anim_id = new_anim_id + return 0 + +def frameBreathWeapon(attachee, args, evt_obj): currentSequence = tpactions.get_cur_seq() spellPacket = currentSequence.spell_packet newSpellId = tpactions.get_new_spell_id() spellPacket.caster_level = attachee.stat_level_get(classEnum) tpactions.register_spell_cast(spellPacket, newSpellId) tpactions.trigger_spell_effect(newSpellId) + + #Reduce Breath Weapon Daily uses by 1 + chargesLeft = args.get_arg(0) chargesLeft -= 1 args.set_arg(0, chargesLeft) return 0 @@ -246,14 +257,15 @@ def resetBreathWeapon(attachee, args, evt_obj): dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #chargesLeft, maxCharges, empty dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) -dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, chargesCheckBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, checkBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, frameBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) ### Blindsense -#TBD +#dropped ###Wings def addWings(attachee, args, evt_obj): From d02b5fb86d8fd51a9a8b751aa0511a54cec39be6 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Thu, 30 Sep 2021 12:54:55 +0200 Subject: [PATCH 10/32] Added Flying Toggle (Nonfunctional yet) --- .../action02303_dragon_disciple_toogle_fly.py | 18 +++++++ .../scr/tpModifiers/dragon_disciple.py | 51 +++++++++++++++++-- 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/d20_actions/action02303_dragon_disciple_toogle_fly.py diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02303_dragon_disciple_toogle_fly.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02303_dragon_disciple_toogle_fly.py new file mode 100644 index 000000000..d4821d8fb --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/d20_actions/action02303_dragon_disciple_toogle_fly.py @@ -0,0 +1,18 @@ +from toee import * +import tpactions + +def GetActionName(): + return "Dragon Disciple Toogle Fly" + +def GetActionDefinitionFlags(): + return D20ADF_None + +def GetTargetingClassification(): + return D20TC_Target0 + +def GetActionCostType(): + return D20ACT_NULL + +def AddToSequence(d20action, action_seq, tb_status): + action_seq.add_action(d20action) + return AEC_OK \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 1a16f0c99..18338df30 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -19,6 +19,7 @@ def GetSpellCasterConditionName(): selectHeritageId = 2301 breathWeaponEnum = 2302 +toggleFlyingId = 2303 #Dict to handle Dragon Disciple Heritage #Can easily be expanded by adding a new type of heritage to the dict @@ -223,7 +224,7 @@ def breathWeaponRadial(attachee, args, evt_obj): def checkBreathWeapon(attachee, args, evt_obj): if args.get_arg(0) < 1: evt_obj.return_val = AEC_OUT_OF_CHARGES - return 0 + return 0 def performBreathWeapon(attachee, args, evt_obj): target = evt_obj.d20a.target @@ -259,7 +260,7 @@ def resetBreathWeapon(attachee, args, evt_obj): dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, checkBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) -dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, frameBreathWeapon, ()) +dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionFrame, breathWeaponEnum, frameBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) @@ -273,9 +274,51 @@ def addWings(attachee, args, evt_obj): #evt_obj.append(meshId) return 0 -dragonDiscipleWings = PythonModifier("Dragon Disciple Dragon Wings", 3) #empty, empty, empty -dragonDiscipleWings.MapToFeat("Dragon Disciple Dragon Wings") +def wingsRadial(attachee, args, evt_obj): + print "Debug Dragon Disciple wingsRadial Hook" + actionString = "Land" if args.get_arg(0) else "Fly" + radialWingsId = tpdp.RadialMenuEntryPythonAction("{}".format(actionString), D20A_PYTHON_ACTION, toggleFlyingId, 0, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS") + radialWingsId.add_as_child(attachee, tpdp.RadialMenuStandardNode.Class) + return 0 + +def resetWings(attachee, args, evt_obj): + args.set_arg(0, 0) + return 0 + +def checkWingsIndoors(attachee, args, evt_obj): + if not game.is_outdoor(): + evt_obj.return_val = AEC_ACTION_INVALID + return 0 + +def toggleWings(attachee, args, evt_obj): + isFlying = args.get_arg(0) + if isFlying: + args.set_arg(0, 0) + attachee.float_text_line("No longer Flying") + else: + args.set_arg(0, 1) + attachee.float_text_line("Flying") + return 0 + +def queryIsFlying(attachee, args, evt_obj): + evt_obj.return_val = 1 if args.get_arg(0) else 0 + return 0 + +def tooltipFyling(attachee, args, evt_obj): + evt_obj.append("Flying!") + return 0 + +dragonDiscipleWings = PythonModifier("Dragon Disciple Dragon Wings", 3) #isFlying, empty, empty +dragonDiscipleWings.MapToFeat("Dragon Disciple Wings") dragonDiscipleWings.AddHook(ET_OnAddMesh, EK_NONE, addWings, ()) +dragonDiscipleWings.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, wingsRadial, ()) +dragonDiscipleWings.AddHook(ET_OnD20PythonActionCheck, toggleFlyingId, checkWingsIndoors, ()) +dragonDiscipleWings.AddHook(ET_OnD20PythonActionPerform, toggleFlyingId, toggleWings, ()) +dragonDiscipleWings.AddHook(ET_OnGetTooltip, EK_NONE, tooltipFyling, ()) +dragonDiscipleWings.AddHook(ET_OnConditionAdd, EK_NONE, resetWings, ()) +dragonDiscipleWings.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetWings, ()) +dragonDiscipleWings.AddHook(ET_OnD20Signal, EK_S_Teleport_Reconnect, resetWings, ()) +dragonDiscipleWings.AddHook(ET_OnD20PythonQuery, "PQ_IS_FLYING", queryIsFlying, ()) #### Dragon Apotheosis From e641ab5dc87cc68db49cbc3965e2b3d11504fc07 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Fri, 1 Oct 2021 12:16:00 +0200 Subject: [PATCH 11/32] Moved Flying to seperate condtion --- .../scr/tpModifiers/dragon_disciple.py | 26 +++--------- .../scr/tpModifiers/flying_condition.py | 41 +++++++++++++++++++ 2 files changed, 47 insertions(+), 20 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 18338df30..abcef92f6 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -275,14 +275,13 @@ def addWings(attachee, args, evt_obj): return 0 def wingsRadial(attachee, args, evt_obj): - print "Debug Dragon Disciple wingsRadial Hook" - actionString = "Land" if args.get_arg(0) else "Fly" + actionString = "Stop Flying" if attachee.d20_query("PQ_Is_Flying") else "Start Flying" radialWingsId = tpdp.RadialMenuEntryPythonAction("{}".format(actionString), D20A_PYTHON_ACTION, toggleFlyingId, 0, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_WINGS") radialWingsId.add_as_child(attachee, tpdp.RadialMenuStandardNode.Class) return 0 def resetWings(attachee, args, evt_obj): - args.set_arg(0, 0) + attachee.d20_signal("PS_Flying_End") return 0 def checkWingsIndoors(attachee, args, evt_obj): @@ -291,34 +290,21 @@ def checkWingsIndoors(attachee, args, evt_obj): return 0 def toggleWings(attachee, args, evt_obj): - isFlying = args.get_arg(0) + isFlying = attachee.d20_query("PQ_Is_Flying") if isFlying: - args.set_arg(0, 0) - attachee.float_text_line("No longer Flying") + attachee.d20_send_signal("PS_Flying_End") else: - args.set_arg(0, 1) - attachee.float_text_line("Flying") - return 0 - -def queryIsFlying(attachee, args, evt_obj): - evt_obj.return_val = 1 if args.get_arg(0) else 0 - return 0 - -def tooltipFyling(attachee, args, evt_obj): - evt_obj.append("Flying!") + attachee.condition_add_with_args("Flying Condition", 0, 0, 0) return 0 -dragonDiscipleWings = PythonModifier("Dragon Disciple Dragon Wings", 3) #isFlying, empty, empty +dragonDiscipleWings = PythonModifier("Dragon Disciple Dragon Wings", 3) #empty, empty, empty dragonDiscipleWings.MapToFeat("Dragon Disciple Wings") dragonDiscipleWings.AddHook(ET_OnAddMesh, EK_NONE, addWings, ()) dragonDiscipleWings.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, wingsRadial, ()) dragonDiscipleWings.AddHook(ET_OnD20PythonActionCheck, toggleFlyingId, checkWingsIndoors, ()) dragonDiscipleWings.AddHook(ET_OnD20PythonActionPerform, toggleFlyingId, toggleWings, ()) -dragonDiscipleWings.AddHook(ET_OnGetTooltip, EK_NONE, tooltipFyling, ()) -dragonDiscipleWings.AddHook(ET_OnConditionAdd, EK_NONE, resetWings, ()) dragonDiscipleWings.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetWings, ()) dragonDiscipleWings.AddHook(ET_OnD20Signal, EK_S_Teleport_Reconnect, resetWings, ()) -dragonDiscipleWings.AddHook(ET_OnD20PythonQuery, "PQ_IS_FLYING", queryIsFlying, ()) #### Dragon Apotheosis diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py new file mode 100644 index 000000000..c5972cb0c --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py @@ -0,0 +1,41 @@ +from templeplus.pymod import PythonModifier +from __main__ import game +from toee import * +import tpdp + +def floatImmunity(attachee, immunityEffect, immunityTag): + attachee.float_text_line("Immune due to flying", tf_red) + game.create_history_freeform("{} is immune to ~{}~[{}] effects\n\n".format(attachee.description, immunityEffect, immunityTag)) + +def preventConditions(attachee, args, evt_obj): + if evt_obj.is_modifier("sp-Grease Hit"): + evt_obj.return_val = 0 + floatImmunity(attachee, "Grease", "TAG_SPELLS_GREASE") + elif evt_obj.is_modifier("sp-Entangle On"): + evt_obj.return_val = 0 + floatImmunity(attachee, "Entangle", "TAG_SPELLS_ENTANGLE") + return 0 + +def preventAoO(attachee, args, evt_obj): + attachee.float_text_line("Flying!") + evt_obj.return_val = 0 + return 0 + +def queryIsFlying(attachee, args, evt_obj): + evt_obj.return_val = 1 + return 0 + +def tooltipFlying(attachee, args, evt_obj): + evt_obj.append("Flying!") + return 0 + +def singalStopFlying(attachee, args, evt_obj): + args.condition_remove() + return 0 + +flyingCondition = PythonModifier("Flying Condition", 3) #empty, empty, empty +flyingCondition.AddHook(ET_OnConditionAddPre, EK_NONE, preventConditions, ()) +flyingCondition.AddHook(ET_OnD20Query, EK_Q_AOOIncurs, preventAoO,()) +flyingCondition.AddHook(ET_OnGetTooltip, EK_NONE, tooltipFlying, ()) +flyingCondition.AddHook(ET_OnD20PythonQuery, "PQ_Is_Flying", queryIsFlying, ()) +flyingCondition.AddHook(ET_OnD20PythonSignal, "PS_Flying_End", singalStopFlying, ()) From 2ebb5a7b7f579720abb7dd35bfe3e8c169c5338b Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Thu, 14 Oct 2021 10:08:30 +0200 Subject: [PATCH 12/32] added Darley Wings Mesh as Wings for the DD --- tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py | 4 ++-- tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index abcef92f6..5f6d25e33 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -270,8 +270,8 @@ def resetBreathWeapon(attachee, args, evt_obj): ###Wings def addWings(attachee, args, evt_obj): - #meshId = - #evt_obj.append(meshId) + meshId = 14201 #Darley Wings + evt_obj.append(meshId) return 0 def wingsRadial(attachee, args, evt_obj): diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py index c5972cb0c..3d9e9e10b 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/flying_condition.py @@ -29,7 +29,7 @@ def tooltipFlying(attachee, args, evt_obj): evt_obj.append("Flying!") return 0 -def singalStopFlying(attachee, args, evt_obj): +def signalStopFlying(attachee, args, evt_obj): args.condition_remove() return 0 @@ -38,4 +38,4 @@ def singalStopFlying(attachee, args, evt_obj): flyingCondition.AddHook(ET_OnD20Query, EK_Q_AOOIncurs, preventAoO,()) flyingCondition.AddHook(ET_OnGetTooltip, EK_NONE, tooltipFlying, ()) flyingCondition.AddHook(ET_OnD20PythonQuery, "PQ_Is_Flying", queryIsFlying, ()) -flyingCondition.AddHook(ET_OnD20PythonSignal, "PS_Flying_End", singalStopFlying, ()) +flyingCondition.AddHook(ET_OnD20PythonSignal, "PS_Flying_End", signalStopFlying, ()) From 7ec38eeadcf95a7c2e62dcf0127878924fbff3a0 Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Mon, 22 Nov 2021 08:37:59 +0200 Subject: [PATCH 13/32] String fixes: DD requirements fit inside scroll box; feat descrition replaced weird inverted comma --- tpdatasrc/tpgamefiles/mes/stat_ext.mes | 3 +-- .../tpgamefiles/rules/feats/dragon disciple natural armor.txt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tpdatasrc/tpgamefiles/mes/stat_ext.mes b/tpdatasrc/tpgamefiles/mes/stat_ext.mes index 71e43bbf3..2fbd9e916 100644 --- a/tpdatasrc/tpgamefiles/mes/stat_ext.mes +++ b/tpdatasrc/tpgamefiles/mes/stat_ext.mes @@ -233,8 +233,7 @@ Feats: Cleave, Power Attack {13016}Dragon Disciple{A descendant of dragonkind who uses their innate magical power as a catalyst to ignite their dragon blood. -Requirements: -Level: 5 +Requirements: Level: 5 Spellcasting: Ability to cast arcane spells without preparation.} diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple natural armor.txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple natural armor.txt index cd680fecb..c5e7d45a3 100644 --- a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple natural armor.txt +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple natural armor.txt @@ -1,5 +1,5 @@ name: Dragon Disciple Natural Armor flags: 8 prereqs: -description: At 1st, 4th, and 7th level, a dragon disciple gains a progressive +1 increase to the character's existing natural armor (if any). As his skin thickens, a dragon disciple takes on more and more of his progenitor’s physical aspect. +description: At 1st, 4th, and 7th level, a dragon disciple gains a progressive +1 increase to the character's existing natural armor (if any). As his skin thickens, a dragon disciple takes on more and more of his progenitor's physical aspect. prereq descr: Dragon Disciple level 1. \ No newline at end of file From b085be23d0a0f29ab2ec2e3f4a471ee53a1fbe4c Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Mon, 22 Nov 2021 08:39:18 +0200 Subject: [PATCH 14/32] Added PyObjHndl method anim_goal_throw_spell_w_cast_anim to properly handle casting animations from non-spell cast actions get_cur_seq() will now return a reference rather than a copy --- .../python/python_integration_d20_action.cpp | 6 +++--- TemplePlus/python/python_object.cpp | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/TemplePlus/python/python_integration_d20_action.cpp b/TemplePlus/python/python_integration_d20_action.cpp index f1cdc4646..71e94ca9a 100644 --- a/TemplePlus/python/python_integration_d20_action.cpp +++ b/TemplePlus/python/python_integration_d20_action.cpp @@ -133,9 +133,9 @@ PYBIND11_EMBEDDED_MODULE(tpactions, m) { return TRUE; }); - m.def("get_cur_seq", []()->ActnSeq &{ - return **actSeqSys.actSeqCur; - }); + m.def("get_cur_seq", []()->ActnSeq *{ + return *actSeqSys.actSeqCur; + }, py::return_value_policy::reference ); m.def("get_current_tb_actor", []()->objHndl { auto actor = tbSys.turnBasedGetCurrentActor(); diff --git a/TemplePlus/python/python_object.cpp b/TemplePlus/python/python_object.cpp index c824242d9..9ae4ce07e 100644 --- a/TemplePlus/python/python_object.cpp +++ b/TemplePlus/python/python_object.cpp @@ -2797,6 +2797,24 @@ static PyObject* PyObjHandle_AnimGoalPushHitByWeapon(PyObject* obj, PyObject* ar return PyInt_FromLong(gameSystems->GetAnim().PushGoalHitByWeapon( attacker, self->handle)); } +static PyObject* PyObjHandle_AnimGoalThrowSpellWithCastAnim(PyObject* obj, PyObject* args) { + + auto self = GetSelf(obj); + if (!self->handle) { + return PyInt_FromLong(0); + } + + if (!PyArg_ParseTuple(args, ":objhndl.anim_goal_throw_spell_w_cast_anim")) { + return 0; + } + auto curSeq = *actSeqSys.actSeqCur; + if (!curSeq || curSeq->performer != self->handle) { + return PyInt_FromLong(0); + } + auto &pkt = curSeq->spellPktBody; + return PyInt_FromLong(gameSystems->GetAnim().PushSpellCast(pkt, objHndl::null)); +} + static PyObject* PyObjHandle_AnimGoalPushUseObject(PyObject* obj, PyObject* args) { auto self = GetSelf(obj); if (!self->handle) { @@ -4342,6 +4360,7 @@ static PyMethodDef PyObjHandleMethods[] = { { "anim_goal_push_attack", PyObjHandle_AnimGoalPushAttack, METH_VARARGS, NULL }, { "anim_goal_push_dodge", PyObjHandle_AnimGoalPushDodge, METH_VARARGS, NULL }, { "anim_goal_push_hit_by_weapon", PyObjHandle_AnimGoalPushHitByWeapon, METH_VARARGS, NULL }, + { "anim_goal_throw_spell_w_cast_anim", PyObjHandle_AnimGoalThrowSpellWithCastAnim, METH_VARARGS, NULL }, { "anim_goal_use_object", PyObjHandle_AnimGoalPushUseObject, METH_VARARGS, NULL }, { "anim_goal_get_new_id", PyObjHandle_AnimGoalGetNewId, METH_VARARGS, NULL }, { "apply_projectile_particles", PyObjHandle_ApplyProjectileParticles, METH_VARARGS, NULL }, From 9696b90a999e18c02e8a4ddd23b066b21ab2602c Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Mon, 22 Nov 2021 08:45:31 +0200 Subject: [PATCH 15/32] DD fixes Breath weapon --- .../scr/tpModifiers/dragon_disciple.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 5f6d25e33..320ecd29e 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -227,22 +227,20 @@ def checkBreathWeapon(attachee, args, evt_obj): return 0 def performBreathWeapon(attachee, args, evt_obj): - target = evt_obj.d20a.target - #attachee.turn_towards(target) - if attachee.anim_goal_push_attack(target, 0, 0 ,0): - new_anim_id = attachee.anim_goal_get_new_id() - evt_obj.d20a.flags |= D20CAF_NEED_ANIM_COMPLETED - evt_obj.d20a.anim_id = new_anim_id - return 0 - -def frameBreathWeapon(attachee, args, evt_obj): currentSequence = tpactions.get_cur_seq() spellPacket = currentSequence.spell_packet newSpellId = tpactions.get_new_spell_id() spellPacket.caster_level = attachee.stat_level_get(classEnum) tpactions.register_spell_cast(spellPacket, newSpellId) - tpactions.trigger_spell_effect(newSpellId) + currentSequence.spell_packet.spell_id = newSpellId + + if attachee.anim_goal_throw_spell_w_cast_anim(): # note: the animation goal has internal calls to trigger_spell_effect and the action frame + new_anim_id = attachee.anim_goal_get_new_id() + evt_obj.d20a.flags |= D20CAF_NEED_ANIM_COMPLETED + evt_obj.d20a.anim_id = new_anim_id + return 0 +def frameBreathWeapon(attachee, args, evt_obj): #Reduce Breath Weapon Daily uses by 1 chargesLeft = args.get_arg(0) chargesLeft -= 1 From 1aabeb197f253b690a67e5c490eb5adb6c0a40c6 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Tue, 23 Nov 2021 12:26:04 +0100 Subject: [PATCH 16/32] Update to DD Draconic Heritage Handling --- .../mes/help/breath_weapon_feats_help.tab | 2 + .../char_class/class023_dragon_disciple.py | 29 ++- ...on02301_dragon_disciple_select_heritage.py | 18 -- .../rules/feats/draconic heritage black.txt | 6 + .../rules/feats/draconic heritage blue.txt | 6 + .../rules/feats/draconic heritage brass.txt | 6 + .../rules/feats/draconic heritage bronze.txt | 6 + .../rules/feats/draconic heritage copper.txt | 6 + .../rules/feats/draconic heritage gold.txt | 6 + .../rules/feats/draconic heritage green.txt | 6 + .../rules/feats/draconic heritage red.txt | 6 + .../rules/feats/draconic heritage silver.txt | 6 + .../rules/feats/draconic heritage white.txt | 6 + .../rules/feats/draconic heritage.txt | 5 + .../rules/feats/extra exhalation.txt | 5 + .../scr/feats/feat - Draconic Heritage.py | 11 ++ .../scr/feats/feat - Extra Exhalation.py | 10 + .../tpgamefiles/scr/heritage_feat_utils.py | 174 ++++++++++++++++++ .../scr/tpModifiers/breath_weapon.py | 80 ++++++++ .../scr/tpModifiers/dragon_disciple.py | 158 +++++----------- 20 files changed, 419 insertions(+), 133 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/mes/help/breath_weapon_feats_help.tab delete mode 100644 tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage black.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage blue.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage brass.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage bronze.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage copper.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage gold.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage green.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage red.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage silver.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage white.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/draconic heritage.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/extra exhalation.txt create mode 100644 tpdatasrc/tpgamefiles/scr/feats/feat - Draconic Heritage.py create mode 100644 tpdatasrc/tpgamefiles/scr/feats/feat - Extra Exhalation.py create mode 100644 tpdatasrc/tpgamefiles/scr/heritage_feat_utils.py create mode 100644 tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py diff --git a/tpdatasrc/tpgamefiles/mes/help/breath_weapon_feats_help.tab b/tpdatasrc/tpgamefiles/mes/help/breath_weapon_feats_help.tab new file mode 100644 index 000000000..dcbad8e5b --- /dev/null +++ b/tpdatasrc/tpgamefiles/mes/help/breath_weapon_feats_help.tab @@ -0,0 +1,2 @@ +TAG_BREATH_FEATS TAG_FEATS_DES Breath Channeling Feats Dragons and creatures of ~Draconic Heritage~[TAG_DRACONIC_HERITAGE] that have breath weapons can choose these feats, which channel the destructive energy of a breath weapon into some other magical or supernatural effect. Using a breath channeling feat requires a creature to activate its breath weapon and counts of a use of that breath weapon. Breath Feats: [CMD_CHILDREN_SORTED] +TAG_EXTRA_EXHALATION TAG_FEATS_DES TAG_BREATH_FEATS Extra Exhalation You can use your breath weapon one more time per day than normal. Prerequisite: ~Draconic Heritage~[TAG_DRACONIC_HERITAGE], Breath Weapon with limited uses per day Benefit: You can use your breath weapon one additional time per day. The interval you must wait between breaths is 1d4 rounds. Special: You can gain this feat multiple times. Each time you take it, you can breathe one additional time per day. diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 927d1e561..250625203 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -1,6 +1,7 @@ from toee import * import char_class_utils import char_editor +from heritage_feat_utils import getDraconicHeritageColourString ################################################### @@ -25,7 +26,7 @@ def GetClassHelpTopic(): class_feats = { -1: ("Dragon Disciple Heritage", "Dragon Disciple Natural Armor",), +1: ("Dragon Disciple Natural Armor",), 2: ("Dragon Disciple Claws and Bite",), 3: ("Dragon Disciple Breath Weapon",), 9: ("Dragon Disciple Wings",), @@ -105,10 +106,34 @@ def ObjMeetsPrereqs(obj): # Levelup +def alreadyHasDraconicHeritage(obj): + hasDraconicHeritageFeat = False + for heritage in range(heritage_draconic_black, heritage_draconic_white + 1): + colourString = getDraconicHeritageColourString(heritage) + if char_editor.has_feat("Draconic Heritage {}".format(colourString)): + hasDraconicHeritageFeat = True + break + return True if hasDraconicHeritageFeat else False def IsSelectingFeatsOnLevelup(obj): - return 0 + newLvl = char_editor.stat_level_get(classEnum) + if not newLvl == 1: + return 0 + if alreadyHasDraconicHeritage(obj): + return 0 + return 1 def LevelupGetBonusFeats(obj): + newLvl = char_editor.stat_level_get(classEnum) + bonus_feats = [] + if newLvl == 1: + bonus_feats.append("Draconic Heritage") + + bonFeatInfo = [] + for ft in bonus_feats: + featInfo = char_editor.FeatInfo(ft) + featInfo.feat_status_flags |= 4 # always pickable + bonFeatInfo.append(featInfo) + char_editor.set_bonus_feats(bonFeatInfo) return diff --git a/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py b/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py deleted file mode 100644 index 08ad3c207..000000000 --- a/tpdatasrc/tpgamefiles/rules/d20_actions/action02301_dragon_disciple_select_heritage.py +++ /dev/null @@ -1,18 +0,0 @@ -from toee import * -import tpactions - -def GetActionName(): - return "Dragon Disciple Select Heritage" - -def GetActionDefinitionFlags(): - return D20ADF_None - -def GetTargetingClassification(): - return D20TC_Target0 - -def GetActionCostType(): - return D20ACT_NULL - -def AddToSequence(d20action, action_seq, tb_status): - action_seq.add_action(d20action) - return AEC_OK \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage black.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage black.txt new file mode 100644 index 000000000..13780ae13 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage black.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Black +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage blue.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage blue.txt new file mode 100644 index 000000000..f9896d6fa --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage blue.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Blue +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage brass.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage brass.txt new file mode 100644 index 000000000..32d1dd615 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage brass.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Brass +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage bronze.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage bronze.txt new file mode 100644 index 000000000..9ecc609a4 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage bronze.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Bronze +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage copper.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage copper.txt new file mode 100644 index 000000000..9851e3778 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage copper.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Copper +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage gold.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage gold.txt new file mode 100644 index 000000000..f11cef093 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage gold.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Gold +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage green.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage green.txt new file mode 100644 index 000000000..25d146ad0 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage green.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Green +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage red.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage red.txt new file mode 100644 index 000000000..31a0e5b1e --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage red.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Red +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage silver.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage silver.txt new file mode 100644 index 000000000..b9a6e8789 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage silver.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage Silver +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage white.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage white.txt new file mode 100644 index 000000000..7cb3e021d --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage white.txt @@ -0,0 +1,6 @@ +name: Draconic Heritage White +flags: 4194304 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1 +parent: Draconic Heritage \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/draconic heritage.txt b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage.txt new file mode 100644 index 000000000..3be52eac6 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/draconic heritage.txt @@ -0,0 +1,5 @@ +name: Draconic Heritage +flags: 13107200 +prereqs: +description: You have a greater connection with your draconic bloodline than others of your kind. +prereq descr: Sorcercer Level 1; automatically granted to Dragon Disciple and Dragon Shaman diff --git a/tpdatasrc/tpgamefiles/rules/feats/extra exhalation.txt b/tpdatasrc/tpgamefiles/rules/feats/extra exhalation.txt new file mode 100644 index 000000000..6b6113083 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/extra exhalation.txt @@ -0,0 +1,5 @@ +name: Extra Exhalation +flags: 12582913 +prereqs: +description: You can use your Breath Weapon one more time per day than normal. Special: You can take this feat multiple times. Each time you take it, you can breath one additional time per day. +prereq descr: Limited Breath Weapon use per day diff --git a/tpdatasrc/tpgamefiles/scr/feats/feat - Draconic Heritage.py b/tpdatasrc/tpgamefiles/scr/feats/feat - Draconic Heritage.py new file mode 100644 index 000000000..b7526c659 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/feats/feat - Draconic Heritage.py @@ -0,0 +1,11 @@ +from toee import * +import char_editor +from heritage_feat_utils import hasDifferentHeritageFeat + +def CheckPrereq(attachee, classLevelled, abilityScoreRaised): + if char_editor.stat_level_get(stat_level_sorcerer) < 1: + return 0 + #Check if character already has any heritage feats + if hasDifferentHeritageFeat(char_editor, "Draconic Heritage"): + return 0 + return 1 diff --git a/tpdatasrc/tpgamefiles/scr/feats/feat - Extra Exhalation.py b/tpdatasrc/tpgamefiles/scr/feats/feat - Extra Exhalation.py new file mode 100644 index 000000000..2e9721d4e --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/feats/feat - Extra Exhalation.py @@ -0,0 +1,10 @@ +from templeplus.pymod import PythonModifier +from toee import * +import char_editor + +def CheckPrereq(attachee, classLevelled, abilityScoreRaised): + #Check if character has a limited usage Breath Weapon + #At the moment only Dragon Diciple has one: + if not char_editor.has_feat("Dragon Disciple Breath Weapon"): + return 0 + return 1 diff --git a/tpdatasrc/tpgamefiles/scr/heritage_feat_utils.py b/tpdatasrc/tpgamefiles/scr/heritage_feat_utils.py new file mode 100644 index 000000000..9b82de5ae --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/heritage_feat_utils.py @@ -0,0 +1,174 @@ +from templeplus.pymod import PythonModifier +from toee import * +import tpdp + +# This file handles heritage feats + +def heritageTypeList(): + typeList = [ + "Celestial Sorcerer Heritage", + "Draconic Heritage Black", + "Draconic Heritage Blue", + "Draconic Heritage Brass", + "Draconic Heritage Bronze", + "Draconic Heritage Copper", + "Draconic Heritage Gold", + "Draconic Heritage Green", + "Draconic Heritage Red", + "Draconic Heritage Silver", + "Draconic Heritage White", + "Fey Heritage", + "Fiendish Heritage", + "Infernal Sorcerer Heritage" + ] + return typeList + +# PHB II +def getCelestialSorcererHeritageFeatList(): + featList = [ + "Celestial Sorcerer Aura", + "Celestial Sorcerer Heritage", + "Celestial Sorcerer Lance", + "Celestial Sorcerer Lore", + "Celestial Sorcerer Wings" + ] + return featList + +# Draconic Feat List +# Note: Draconic Aura and Double Aura are not part of the draconic feats! +# Missing: Complete Arcane: Claw, Flight, Legacy(maybe a skip), Power, Presence +# Missing Dragon Magic: [Colour] Dragon Lineage (started), Draconic Knowledge (skip as there are no knowledge skills?), Draconic Senses +# Missing Dragon Magic Multiclass options: Dragonfire Assault (Power Attack), Dragonfire Channeling (Turn/Rebuke), +# Dragonfire Inspiration (Bardic Music), Dragonfire Strike (Sneak/Skirmish/Sudden Strike) +# Missing Races of the Dragon: Persuasion +def getDraconicHeritageFeatList(): + featList = [ + "Draconic Arcane Grace", + "Draconic Armor", + "Draconic Breath", + "Draconic Claw ", + "Draconic Flight" + "Draconic Heritage Black", + "Draconic Heritage Blue", + "Draconic Heritage Brass", + "Draconic Heritage Bronze", + "Draconic Heritage Copper", + "Draconic Heritage Gold", + "Draconic Heritage Green", + "Draconic Heritage Red", + "Draconic Heritage Silver", + "Draconic Heritage White", + "Draconic Knowledge", + "Draconic Legacy", + "Draconic Persuasion", + "Draconic Power", + "Draconic Presence", + "Draconic Resistance", + "Draconic Senses", + "Draconic Skin", + "Draconic Toughness", + "Draconic Vigor", + "Dragon Lineage Black", + "Dragon Lineage Blue", + "Dragon Lineage Brass", + "Dragon Lineage Bronze", + "Dragon Lineage Copper", + "Dragon Lineage Gold", + "Dragon Lineage Green", + "Dragon Lineage Red", + "Dragon Lineage Silver", + "Dragon Lineage White", + "Dragonfire Assault", + "Dragonfire Channeling", + "Dragonfire Inspiration", + "Dragonfire Strike" + ] + return featList + +# Complete Mage +def getFeyHeritageFeatList(): + featList = [ + "Fey Heritage", + "Fey Legacy", + "Fey Power", + "Fey Presence", + "Fey Skin" + ] + return featList + +# Complete Mage +def getFiendishHeritageFeatList(): + featList = [ + "Fiendish Heritage", + "Fiendish Legacy", + "Fiendish Power", + "Fiendish Presence", + "Fiendish Resistance" + ] + return featList + +# PHB II +def getInfernalSorcererHeritageFeatList(): + featList = [ + "Infernal Sorcerer Eyes", + "Infernal Sorcerer Heritage", + "Infernal Sorcerer Howl", + "Infernal Sorcerer Resistance", + ] + return featList + +def hasDifferentHeritageFeat(attachee, featToAquire): + for heritageFeat in heritageTypeList(): + if not featToAquire == heritageFeat: + if attachee.has_feat(heritageFeat): + return True + return False + +# Counts the number of heritage feats a character has +# Used by several feats +def countHeritageFeats(attachee, heritageType): + if heritageType == heritage_celestial_sorcerer: + featList = getCelestialSorcererHeritageFeatList() + elif heritageType in range(heritage_draconic_black, heritage_draconic_white + 1): + featList = getDraconicHeritageFeatList() + elif heritageType == heritage_fey: + featList = getFeyHeritageFeatList() + elif heritageType == heritage_fiendish: + featList = getFiendishHeritageFeatList() + elif heritageType == heritage_infernal_sorcerer: + featList = getInfernalSorcererHeritageFeatList() + numberOfFeats = 0 + for feat in featList: + if attachee.has_feat(feat): + numberOfFeats += 1 + return numberOfFeats + +# Draconic heritage mappings +# [colourString, elementType, breathWeaponShape] + +def getDictDraconicHeritage(): + dictDraconicHeritage = { + heritage_draconic_black: ["Black", D20DT_ACID, dragon_breath_shape_line], + heritage_draconic_blue: ["Blue", D20DT_ELECTRICITY, dragon_breath_shape_line], + heritage_draconic_brass: ["Brass", D20DT_FIRE, dragon_breath_shape_line], + heritage_draconic_bronze: ["Bronze", D20DT_ELECTRICITY, dragon_breath_shape_line], + heritage_draconic_copper: ["Copper", D20DT_ACID, dragon_breath_shape_line], + heritage_draconic_gold: ["Gold", D20DT_FIRE, dragon_breath_shape_cone], + heritage_draconic_green: ["Green", D20DT_ACID, dragon_breath_shape_cone], + heritage_draconic_red: ["Red", D20DT_FIRE, dragon_breath_shape_cone], + heritage_draconic_silver: ["Silver", D20DT_COLD, dragon_breath_shape_cone], + heritage_draconic_white: ["White", D20DT_COLD, dragon_breath_shape_cone] + } + return dictDraconicHeritage + +def getDraconicHeritageColourString(heritage): + dictDraconicHeritage = getDictDraconicHeritage() + return dictDraconicHeritage[heritage][0] + +def getDraconicHeritageElement(heritage): + dictDraconicHeritage = getDictDraconicHeritage() + return dictDraconicHeritage[heritage][1] + +def getDraconicHeritageBreathShape(heritage): + dictDraconicHeritage = getDictDraconicHeritage() + return dictDraconicHeritage[heritage][2] diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py new file mode 100644 index 000000000..648377c3a --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py @@ -0,0 +1,80 @@ +from templeplus.pymod import PythonModifier +from toee import * +import tpdp + +################################################### + +print "Registering Breath Weapon" + +################################################### + +def breathWeaponOnConditionAdd(attachee, args, evt_obj): + breathWeaponId = args.get_param(0) + breathWeaponCharges = args.get_param(1) + cooldown = 0 + args.set_arg(0, breathWeaponId) + args.set_arg(1, breathWeaponCharges) + args.set_arg(2, cooldown) + args.set_arg(3, 0) + args.set_arg(4, 0) + return 0 + +# Handle Breath Weapon Charges +def addExtraExhalationCharges(attachee): + extraBreathExhalation = attachee.has_feat("Extra Exhalation") + return extraBreathExhalation + +def getMaxCharges(attachee): + maxCharges = 1 + addExtraExhalationCharges(attachee) + return maxCharges + +def resetBreathWeaponUses(attachee, args, evt_obj): + if not args.get_arg(1) == -1: + maxCharges = getMaxCharges(attachee) + args.set_arg(1, maxCharges) + return 0 + +# Handle Breath Weapon Cooldown +def getBreathWeaponCoolDown(): + cooldownDice = dice_new('1d4') + return cooldownDice.roll() + +def reduceBreathWeaponCooldown(attachee, args, evt_obj): + if args.get_arg(2) > -1: + cooldown = args.get_arg(2) + cooldown -= evt_obj.data1 + if cooldown < 0 and game.combat_is_active(): + attachee.float_text_line("Breath Weapon ready") + args.set_arg(2, cooldown) + return 0 + +# Trigger Breath Weapon Used +def signalBreathWeaponUsed(attachee, args, evt_obj): + signalId = evt_obj.data1 + breathWeaponId = args.get_arg(0) + if signalId == breathWeaponId: + breathWeaponCharges = args.get_arg(1) + if not breathWeaponCharges == 1: + cooldown = getBreathWeaponCoolDown() + args.set_arg(2, cooldown) + if not breathWeaponCharges == -1: + breathWeaponCharges -= 1 + args.set_arg(1, breathWeaponCharges) + return 0 + +class BreathWeaponModifier(PythonModifier): + # Breath Weapon modifiers have 5 arguments: + # 0: breathWeaponId, 1: charges, 2: Cooldown, 3: empty, 4: empty + # Charges set to -1 indicates limitless breath weapon uses + # A Breath Weapon usage always triggers a 1d4 long cooldown + # before the Breath Weapon becomes availible again + def __init__(self, name): + PythonModifier.__init__(self, name, 5, True) + self.AddHook(ET_OnD20PythonSignal, "PS_Breath_Weapon_Used", signalBreathWeaponUsed, ()) + self.AddHook(ET_OnBeginRound, EK_NONE, reduceBreathWeaponCooldown, ()) + self.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeaponUses, ()) + + # This hook needs to be added for every BreathWeaponModifier + def breathWeaponSetArgs(self, breathWeaponId, breathWeaponCharges): + self.AddHook(ET_OnConditionAdd, EK_NONE, breathWeaponOnConditionAdd, (breathWeaponId, breathWeaponCharges,)) + diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 320ecd29e..a83b6f6d4 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -3,6 +3,8 @@ import tpdp import tpactions import char_class_utils +import heritage_feat_utils +import breath_weapon ################################################### @@ -17,27 +19,9 @@ def GetSpellCasterConditionName(): classEnum = stat_level_dragon_disciple classSpecModule = __import__('class023_dragon_disciple') -selectHeritageId = 2301 breathWeaponEnum = 2302 toggleFlyingId = 2303 -#Dict to handle Dragon Disciple Heritage -#Can easily be expanded by adding a new type of heritage to the dict -#[Colour, ElementType, Breath Weapon Shape (1 = Cone, 2 = Line)] -#If a new element type would be added e.g. sonic or negative, -#this also would be needed to add in both spells and in the partsys -dictDragonHeritage = { -1: ["Black", D20DT_ACID, 2], -2: ["Blue", D20DT_ELECTRICITY, 2], -3: ["Green", D20DT_ACID, 1], -4: ["Red", D20DT_FIRE, 1], -5: ["White", D20DT_COLD, 1], -6: ["Brass", D20DT_FIRE, 2], -7: ["Bronze", D20DT_ELECTRICITY, 2], -8: ["Copper", D20DT_ACID, 2], -9: ["Gold", D20DT_FIRE, 1], -10: ["Silver", D20DT_COLD, 1] -} ################################################### @@ -72,55 +56,7 @@ def OnGetSaveThrowWill(attachee, args, evt_obj): ##### Dragon Disciple Class Features ##### -###Handle Heritage -def selectHeritageRadial(attachee, args, evt_obj): - if not args.get_arg(0): - radialSelectHeritageParent = tpdp.RadialMenuEntryParent("Select Dragon Disciple Heritage") - radialSelectHeritageParentId = radialSelectHeritageParent.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) - for key in dictDragonHeritage.keys(): - dragonColour = dictDragonHeritage[key][0] - radialHeritageColourId = tpdp.RadialMenuEntryPythonAction("{} Dragon Heritage".format(dragonColour), D20A_PYTHON_ACTION, selectHeritageId, key, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_HERITAGE") - radialHeritageColourId.add_as_child(attachee, radialSelectHeritageParentId) - return 0 - -def setHeritage(attachee, args, evt_obj): - chosenHeritage = evt_obj.d20a.data1 - print "Selected Heritage: {}".format(chosenHeritage) - args.set_arg(0, chosenHeritage) - #Visual Feedback for selected Heritage - heritageColour = dictDragonHeritage[chosenHeritage][0] - attachee.float_text_line("{} Heritage chosen".format(heritageColour)) - return 0 - -def querySelectedHeritage(attachee, args, evt_obj): - heritage = args.get_arg(0) - evt_obj.return_val = heritage - return 0 - -def queryHeritageElementType(attachee, args, evt_obj): - heritage = args.get_arg(0) - elementType = dictDragonHeritage[heritage][1] - evt_obj.return_val = elementType - return 0 - -def queryHeritageBreathWeaponType(attachee, args, evt_obj): - heritage = args.get_arg(0) - evt_obj.return_val = dictDragonHeritage[heritage][2] - return 0 - -def initialHeritageValue(attachee, args, evt_obj): - args.set_arg(0, 0) - return 0 - -dragonHeritage = PythonModifier("Dragon Disciple Heritage", 3) #heritage, empty, empty -dragonHeritage.MapToFeat("Dragon Disciple Heritage") -dragonHeritage.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, selectHeritageRadial, ()) -dragonHeritage.AddHook(ET_OnD20PythonActionPerform, selectHeritageId, setHeritage, ()) -dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Selected_Heritage", querySelectedHeritage, ()) -dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Element_Type", queryHeritageElementType, ()) -dragonHeritage.AddHook(ET_OnD20PythonQuery, "PQ_Dragon_Disciple_Breath_Weapon_Type", queryHeritageBreathWeaponType, ()) -dragonHeritage.AddHook(ET_OnConditionAdd, EK_NONE, initialHeritageValue, ()) - +### Draconic Heritage is handle by the Draconic Heritage Feat now ### AC Bonus def naturalArmorACBonus(attachee, args, evt_obj): @@ -141,21 +77,6 @@ def naturalArmorACBonus(attachee, args, evt_obj): naturalArmorInc.MapToFeat("Dragon Disciple Natural Armor") naturalArmorInc.AddHook(ET_OnGetAC, EK_NONE, naturalArmorACBonus, ()) -### Ability Bonus -#def OnGetAbilityScore(attachee, args, evt_obj): - #statType = args.get_param(0) -# lvl = attachee.stat_level_get(classEnum) -# statMod = args.get_param(1) -# -# newValue = statMod + evt_obj.bonus_list.get_sum() -# if (newValue < 3): # ensure minimum stat of 3 -# statMod = 3-newValue -# evt_obj.bonus_list.add(statMod, 0, 139) -# return 0 - -#classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_STRENGTH, OnGetAbilityScore, ()) - - def onGetAbilityScoreStr(attachee, args, evt_obj): level = attachee.stat_level_get(classEnum) if level < 2: @@ -200,30 +121,35 @@ def onGetAbilityScoreCha(attachee, args, evt_obj): classSpecObj.AddHook(ET_OnAbilityScoreLevel, EK_STAT_CHARISMA, onGetAbilityScoreCha, ()) ### Claws and Bite +# ToDo! ### Breath Weapon def breathWeaponRadial(attachee, args, evt_obj): - chargesLeft = args.get_arg(0) - maxCharges = args.get_arg(1) - #I display the heritage colour in the Breath Weapon Radial - #So the player gets a visual feedback, which colour he did choose - heritage = attachee.d20_query("PQ_Dragon_Disciple_Selected_Heritage") - dragonColour = dictDragonHeritage[heritage][0] - breathWeaponShape = attachee.d20_query("PQ_Dragon_Disciple_Breath_Weapon_Type") - spellEnum = spell_dragon_disciple_cone_breath if breathWeaponShape == 1 else spell_dragon_disciple_line_breath - - breathWeaponId = tpdp.RadialMenuEntryPythonAction("{} Breath Weapon {}/{}".format(dragonColour, chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") - spellData = tpdp.D20SpellData(spellEnum) - casterLevel = attachee.stat_level_get(classEnum) - spellData.set_spell_class(classEnum) - spellData.set_spell_level(casterLevel) - breathWeaponId.set_spell_data(spellData) + breathWeaponCooldown = args.get_arg(2) + if breathWeaponCooldown > -1: + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon Cooldown ({} round(s))".format(breathWeaponCooldown), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_EXTRA_EXALATION") + else: + chargesLeft = args.get_arg(1) + maxCharges = breath_weapon.getMaxCharges(attachee) + print "chargesLeft: {}, maxCharges: {}".format(chargesLeft, maxCharges) + heritage = attachee.d20_query("PQ_Selected_Draconic_Heritage") + print "heritage: {}".format(heritage) + breathWeaponShape = heritage_feat_utils.getDraconicHeritageBreathShape(heritage) + print "breathWeaponShape: {}".format(breathWeaponShape) + spellEnum = 3231 if breathWeaponShape == dragon_breath_shape_cone else 3232 + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({}/{})".format(chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") + spellData = tpdp.D20SpellData(spellEnum) + spellData.set_spell_class(classEnum) + spellData.set_spell_level(9) #Setting this to 9 here so, it passes globes of invulnerability, as they should not protect against Breath Weapons + breathWeaponId.set_spell_data(spellData) breathWeaponId.add_child_to_standard(attachee, tpdp.RadialMenuStandardNode.Class) return 0 def checkBreathWeapon(attachee, args, evt_obj): - if args.get_arg(0) < 1: + if args.get_arg(1) < 1: evt_obj.return_val = AEC_OUT_OF_CHARGES + elif args.get_arg(2) > -1: + evt_obj.return_val = AEC_INVALID_ACTION return 0 def performBreathWeapon(attachee, args, evt_obj): @@ -240,27 +166,32 @@ def performBreathWeapon(attachee, args, evt_obj): evt_obj.d20a.anim_id = new_anim_id return 0 -def frameBreathWeapon(attachee, args, evt_obj): - #Reduce Breath Weapon Daily uses by 1 - chargesLeft = args.get_arg(0) - chargesLeft -= 1 - args.set_arg(0, chargesLeft) +def frameBreathWeapon(attachee, args, evt_obj): + genderString = "his" if attachee.stat_level_get(stat_gender) == 1 else "her" + game.create_history_freeform("{} uses {} ~Breath Weapon~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON]\n\n".format(attachee.description, genderString)) +# currentSequence = tpactions.get_cur_seq() +# spellPacket = currentSequence.spell_packet +# newSpellId = tpactions.get_new_spell_id() +# print "spellPacket.caster_level: {}".format(spellPacket.caster_level) +# spellPacket.caster_level = attachee.stat_level_get(classEnum) + # Setting the dc here does not affect the spell when triggered down below :( +# spellPacket.dc = 10 + attachee.stat_level_get(classEnum) + ((attachee.stat_level_get(stat_constitution)-10)/2) +# print "Debug Spell DC Breath Weapon: {}".format(spellPacket.dc) +# tpactions.register_spell_cast(spellPacket, newSpellId) +# tpactions.trigger_spell_effect(newSpellId) + #Send Breath Weapon Used Signal + breathWeaponId = args.get_arg(0) + attachee.d20_send_signal("PS_Breath_Weapon_Used", breathWeaponId) return 0 -def resetBreathWeapon(attachee, args, evt_obj): - maxCharges = 1 - args.set_arg(0, maxCharges) - args.set_arg(1, maxCharges) - return 0 - -dragonDiscipleBreathWeapon = PythonModifier("Dragon Disciple Breath Weapon", 3) #chargesLeft, maxCharges, empty +dragonDiscipleBreathWeapon = breath_weapon.BreathWeaponModifier("Dragon Disciple Breath Weapon") dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") +dragonDiscipleBreathWeapon.breathWeaponSetArgs(classEnum, 1) dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, checkBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionFrame, breathWeaponEnum, frameBreathWeapon, ()) -dragonDiscipleBreathWeapon.AddHook(ET_OnConditionAdd, EK_NONE, resetBreathWeapon, ()) -dragonDiscipleBreathWeapon.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeapon, ()) + ### Blindsense @@ -324,7 +255,8 @@ def sleepParalyzeImmunity(attachee, args, evt_obj): return 0 def elementImmunity(attachee, args, evt_obj): - elementType = attachee.d20_query("PQ_Dragon_Disciple_Element_Type") + heritage = attachee.d20_query("PQ_Selected_Draconic_Heritage") + elementType = heritage_feat_utils.getDraconicHeritageElement(heritage) damageMesLine = 132 #ID 132 in damage.mes is Immunity evt_obj.damage_packet.add_mod_factor(0.0, elementType, damageMesLine) return 0 From 2d2d8cb52422788d5aceab86c83727816e61a24a Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Wed, 24 Nov 2021 12:01:46 +0100 Subject: [PATCH 17/32] First Draft of Bonus Spells Feature --- .../char_class/class023_dragon_disciple.py | 9 +- .../rules/feats/bonus spell bard 0.txt | 6 ++ .../rules/feats/bonus spell bard 1.txt | 6 ++ .../rules/feats/bonus spell bard 2.txt | 6 ++ .../rules/feats/bonus spell bard 3.txt | 6 ++ .../rules/feats/bonus spell sorcerer 0.txt | 6 ++ .../rules/feats/bonus spell sorcerer 1.txt | 6 ++ .../rules/feats/bonus spell sorcerer 2.txt | 6 ++ .../rules/feats/bonus spell sorcerer 3.txt | 6 ++ .../rules/feats/bonus spell wizard 0.txt | 6 ++ .../rules/feats/bonus spell wizard 1.txt | 6 ++ .../rules/feats/bonus spell wizard 2.txt | 6 ++ .../rules/feats/bonus spell wizard 3.txt | 6 ++ .../tpgamefiles/rules/feats/bonus spell.txt | 5 ++ .../rules/partsys/breath_weapons_partsys.tab | 24 +++++ .../rules/partsys/dragon_disciple_partsys.tab | 24 ----- .../dragon_disciple_spell_enum.mes | 3 - ...Spell3231 - Dragon Disciple Cone Breath.py | 25 +++--- ...Spell3232 - Dragon Disciple Line Breath.py | 19 ++-- .../scr/tpModifiers/bonus_spell.py | 68 ++++++++++++++ .../scr/tpModifiers/dragon_disciple.py | 89 +------------------ 21 files changed, 199 insertions(+), 139 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt create mode 100644 tpdatasrc/tpgamefiles/rules/partsys/breath_weapons_partsys.tab delete mode 100644 tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab create mode 100644 tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index 250625203..c6fd820bd 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -117,9 +117,10 @@ def alreadyHasDraconicHeritage(obj): def IsSelectingFeatsOnLevelup(obj): newLvl = char_editor.stat_level_get(classEnum) - if not newLvl == 1: - return 0 - if alreadyHasDraconicHeritage(obj): + if newLvl == 1: + if alreadyHasDraconicHeritage(obj): + return 0 + elif newLvl not in [2, 4, 5, 6, 8, 9]: return 0 return 1 @@ -128,6 +129,8 @@ def LevelupGetBonusFeats(obj): bonus_feats = [] if newLvl == 1: bonus_feats.append("Draconic Heritage") + elif newLvl in [2, 4, 5, 6, 8, 9]: + bonus_feats.append("Bonus Spell") bonFeatInfo = [] for ft in bonus_feats: diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt new file mode 100644 index 000000000..2e7050e79 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Bard 0) +flags: 9 +prereqs: 8 1 +description: Grants a Bard bonus Spell per Day for Spell Level 0 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt new file mode 100644 index 000000000..b4fc06deb --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Bard 1) +flags: 9 +prereqs: 8 2 +description: Grants a Bard bonus Spell per Day for Spell Level 1 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt new file mode 100644 index 000000000..78ba1274d --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Bard 2) +flags: 9 +prereqs: 8 4 +description: Grants a Bard bonus Spell per Day for Spell Level 2 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt new file mode 100644 index 000000000..ec4a1b56c --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Bard 3) +flags: 9 +prereqs: 8 7 +description: Grants a Bard bonus Spell per Day for Spell Level 3 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt new file mode 100644 index 000000000..57907f5a6 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Sorcerer 0) +flags: 9 +prereqs: 16 1 +description: Grants a Sorcerer bonus Spell per Day for Spell Level 0 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt new file mode 100644 index 000000000..5ecf732a4 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Sorcerer 1) +flags: 9 +prereqs: 16 1 +description: Grants a Sorcerer bonus Spell per Day for Spell Level 1 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt new file mode 100644 index 000000000..259e0d56a --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Sorcerer 2) +flags: 9 +prereqs: 16 4 +description: Grants a Sorcerer bonus Spell per Day for Spell Level 2 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt new file mode 100644 index 000000000..eb76da192 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Sorcerer 3) +flags: 9 +prereqs: 16 6 +description: Grants a Sorcerer bonus Spell per Day for Spell Level 3 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt new file mode 100644 index 000000000..c34c41fbd --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Wizard 0) +flags: 9 +prereqs: 17 1 +description: Grants a Wizard bonus Spell per Day for Spell Level 0 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt new file mode 100644 index 000000000..576110882 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Wizard 1) +flags: 9 +prereqs: 17 1 +description: Grants a Wizard bonus Spell per Day for Spell Level 1 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt new file mode 100644 index 000000000..0449bd9b0 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Wizard 2) +flags: 9 +prereqs: 17 3 +description: Grants a Wizard bonus Spell per Day for Spell Level 2 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt new file mode 100644 index 000000000..33c35c130 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt @@ -0,0 +1,6 @@ +name: Bonus Spell (Wizard 3) +flags: 9 +prereqs: 17 5 +description: Grants a Wizard bonus Spell per Day for Spell Level 3 +prereq descr: +parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt new file mode 100644 index 000000000..bfca83c2c --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt @@ -0,0 +1,5 @@ +name: Bonus Spell +flags: 9 +prereqs: +description: Grants a Bonus Spell per Day +prereq descr: diff --git a/tpdatasrc/tpgamefiles/rules/partsys/breath_weapons_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/breath_weapons_partsys.tab new file mode 100644 index 000000000..c9c2b4a62 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/partsys/breath_weapons_partsys.tab @@ -0,0 +1,24 @@ +sp-Breath Weapon Cone Medium Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?360 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Breath Weapon Cone Medium Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Breath Weapon Cone Medium Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 +sp-Breath Weapon Cone Medium Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Breath Weapon Cone Medium Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Breath Weapon Cone Medium Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Breath Weapon Cone Medium Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Breath Weapon Cone Medium Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 +sp-Breath Weapon Cone Medium Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 +sp-Breath Weapon Cone Medium Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 +sp-Breath Weapon Cone Medium Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 +sp-Breath Weapon Cone Medium Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 +sp-Breath Weapon Line Medium Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 +sp-Breath Weapon Line Medium Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 +sp-Breath Weapon Line Medium Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 +sp-Breath Weapon Line Medium Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 +sp-Breath Weapon Line Medium Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Breath Weapon Line Medium Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 +sp-Breath Weapon Line Medium Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 +sp-Breath Weapon Line Medium Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 +sp-Breath Weapon Line Medium Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 +sp-Breath Weapon Line Medium Fire smoke emit 10 20 400 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 +sp-Breath Weapon Line Medium Fire Fire Strike 30 50 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 +sp-Breath Weapon Line Medium Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 diff --git a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab b/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab deleted file mode 100644 index 32788c940..000000000 --- a/tpdatasrc/tpgamefiles/rules/partsys/dragon_disciple_partsys.tab +++ /dev/null @@ -1,24 +0,0 @@ -sp-Dragon Disciple Cone Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?360 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 -sp-Dragon Disciple Cone Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 -sp-Dragon Disciple Cone Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 -sp-Dragon Disciple Cone Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Cone Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 -sp-Dragon Disciple Cone Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Cone Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 -sp-Dragon Disciple Cone Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 -sp-Dragon Disciple Cone Breath Fire smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -30?30 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 -sp-Dragon Disciple Cone Breath Fire Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 -sp-Dragon Disciple Cone Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -30?30 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 -sp-Dragon Disciple Line Breath Acid smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?600 5?40 0 0 0?360 0 0 0,16,0 0 255 144 100 -sp-Dragon Disciple Line Breath Acid Fire Strike 30 75 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 20?60 0 0 0?720 0 0 0,255,0 0 255 144 20 -sp-Dragon Disciple Line Breath Acid Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,600 0,10 0 0 0?720 0 0 0,255,0 0 255 144 30 -sp-Dragon Disciple Line Breath Cold smoke emit 10 20 600 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,16,0 255 255 255 100 -sp-Dragon Disciple Line Breath Cold Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Line Breath Cold fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 0 255 255 255 100 -sp-Dragon Disciple Line Breath Electricity Fire Strike 30 100 Object YPR Sprite Same as Emitter Polar flare-big 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0 0 128 255 20 -sp-Dragon Disciple Line Breath Electricity fire strike 3 sparklies 30 50 Object YPR Sprite Same as Emitter Polar flare-1 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 255,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3,1,30,20,25,6,26,15,2,28,3, 0 0 0,360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 100 -sp-Dragon Disciple Line Breath Electricity water droplets 15 30 30 Object YPR bip01 head Sprite Same as Emitter Polar Cartesian flare-1 5 Add 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 3?15 0 0 0?360 0 0 255,0 255,64,64,64,64 255,128,128,128,128 255,255 5 -sp-Dragon Disciple Line Breath Fire smoke emit 10 20 400 Object YPR Sprite Same as Emitter Polar Cartesian fire-sprite 120 Blend 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0?30 0 -3?3 30?330 5?40 0 0 0?360 0 0 0,32,0 0 0 0 100 -sp-Dragon Disciple Line Breath Fire Fire Strike 30 50 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 64,0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 20?60 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 20 -sp-Dragon Disciple Line Breath Fire Fire Strike 2 30 300 Object YPR Disc Same as Emitter Polar flare 30 Add 0 0 0 0 0 0 0 0 40 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -3?3 0 0,360 0,10 0 0 0?720 0 0 0,255,255,0 255(6),255(10) 255(6),130(10) 0 30 diff --git a/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes index 0780f3878..91a27524d 100644 --- a/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes +++ b/tpdatasrc/tpgamefiles/rules/spell_enums/dragon_disciple_spell_enum.mes @@ -3,8 +3,5 @@ {3231}{Dragon Disciple Cone Breath} {3232}{Dragon Disciple Line Breath} -{8231}{Dragon Disciple Cone Breath} -{8232}{Dragon Disciple Line Breath} - {23231}{DRAGON_DISCIPLE_CONE_BREATH} {23232}{DRAGON_DISCIPLE_LINE_BREATH} diff --git a/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py index a3a6707a1..f0d8cf6c6 100644 --- a/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py +++ b/tpdatasrc/tpgamefiles/scr/Spell3231 - Dragon Disciple Cone Breath.py @@ -1,4 +1,5 @@ from toee import * +from heritage_feat_utils import getDraconicHeritageElement def OnBeginSpellCast(spell): print "Dragon Disciple Cone Breath OnBeginSpellCast" @@ -10,10 +11,7 @@ def OnSpellEffect(spell): targetsToRemove = [] spell.duration = 0 - spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)-10)/2) - print "Debug Dragon Disciple Cone Breath" - print "Dragon Disciple Breath Weapon DC: {}".format(spell.dc) - print "Dragon Disciple Caster Level: {}".format(spell.caster_level) + spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)- 10 ) / 2) spellDamageDice = dice_new('1d8') if spell.caster_level < 7: @@ -22,27 +20,26 @@ def OnSpellEffect(spell): spellDamageDice.number = 4 else: spellDamageDice.number = 6 - print "spellDamageDice: {}".format(spellDamageDice) saveType = D20_Save_Reduction_Half - damageType = spell.caster.d20_query("PQ_Dragon_Disciple_Element_Type") - print "Dragon Disciple Breath Weapon Element: {}".format(damageType) - #If different Breath Weapon Types get added (e.g. Sonic) add them here + + heritage = spell.caster.d20_query("PQ_Selected_Draconic_Heritage") + damageType = getDraconicHeritageElement(heritage) if damageType == D20DT_ACID: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ACID - particleEffect = "sp-Dragon Disciple Cone Breath Acid" + elementString = "Acid" elif damageType == D20DT_COLD: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_COLD - particleEffect = "sp-Dragon Disciple Cone Breath Cold" + elementString = "Cold" elif damageType == D20DT_ELECTRICITY: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ELECTRICITY - particleEffect = "sp-Dragon Disciple Cone Breath Electricity" + elementString = "Electricity" elif damageType == D20DT_FIRE: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_FIRE - particleEffect = "sp-Dragon Disciple Cone Breath Fire" + elementString = "Fire" else: #Fallback saveDescriptor = D20STD_F_NONE - particleEffect = "sp-Dragon Disciple Cone Breath Fire" - print "Dragon Disciple Breath Weapon Descriptor: {}".format(saveDescriptor) + elementString = "Fire" + particleEffect = "sp-Breath Weapon Cone Medium {}".format(elementString) game.particles(particleEffect, spell.caster) diff --git a/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py index ed892ab77..dd23cccdf 100644 --- a/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py +++ b/tpdatasrc/tpgamefiles/scr/Spell3232 - Dragon Disciple Line Breath.py @@ -1,4 +1,5 @@ from toee import * +from heritage_feat_utils import getDraconicHeritageElement def OnBeginSpellCast(spell): print "Dragon Disciple Line Breath OnBeginSpellCast" @@ -10,7 +11,7 @@ def OnSpellEffect(spell): targetsToRemove = [] spell.duration = 0 - spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution)-10)/2) + spell.dc = 10 + spell.caster_level + ((spell.caster.stat_level_get(stat_constitution) - 10) / 2) spellDamageDice = dice_new('1d8') if spell.caster_level < 7: @@ -20,23 +21,25 @@ def OnSpellEffect(spell): else: spellDamageDice.number = 6 saveType = D20_Save_Reduction_Half - damageType = spell.caster.d20_query("PQ_Dragon_Disciple_Element_Type") - #If different Breath Weapon Types get added (e.g. Sonic) add them here + + heritage = spell.caster.d20_query("PQ_Selected_Draconic_Heritage") + damageType = getDraconicHeritageElement(heritage) if damageType == D20DT_ACID: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ACID - particleEffect = "sp-Dragon Disciple Line Breath Acid" + elementString = "Acid" elif damageType == D20DT_COLD: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_COLD - particleEffect = "sp-Dragon Disciple Line Breath Cold" + elementString = "Cold" elif damageType == D20DT_ELECTRICITY: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_ELECTRICITY - particleEffect = "sp-Dragon Disciple Line Breath Electricity" + elementString = "Electricity" elif damageType == D20DT_FIRE: saveDescriptor = D20STD_F_SPELL_DESCRIPTOR_FIRE - particleEffect = "sp-Dragon Disciple Line Breath Fire" + elementString = "Fire" else: #Fallback saveDescriptor = D20STD_F_NONE - particleEffect = "sp-Dragon Disciple Line Breath Fire" + elementString = "Fire" + particleEffect = "sp-Breath Weapon Line Medium {}".format(elementString) game.particles(particleEffect, spell.caster) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py new file mode 100644 index 000000000..0f10095a1 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py @@ -0,0 +1,68 @@ +from templeplus.pymod import PythonModifier +from toee import * +import tpdp + +print "Registering Bonus Spell Slot Feats" + +def setSpellLevelArg(attachee, args, evt_obj): + spellLevel = args.get_param(0) + args.set_arg(2, spellLevel) + return 0 + +def applyExtraSpell(attachee, args, evt_obj): + classEnum = args.get_arg(1) + spellLevel = args.get_arg(2) + #I am unsure why evt_obj.caster_class is not working, can't get a print of it either + #print "evt_obj.caster_class: {}".format(evt_obj.caster_class) + #Disabeld for now due to this + #if not evt_obj.caster_class == classEnum: + # return 0 + if evt_obj.spell_level == spellLevel: + bonusValue = 1 + bonusType = 0 #ID 0 untyped (stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "Bonus Spell Slot") + return 0 + +class BonusSpellModifier(PythonModifier): + # BonusSpellModifier have 4 arguments: + # 0: featEnum, 1: classEnum, 2: spellLevel, 3: empty + def __init__(self, name): + PythonModifier.__init__(self, name, 4, False) + self.AddHook(ET_OnGetSpellsPerDayMod, EK_NONE, applyExtraSpell, ()) + + #There is no feat_cond_arg3 so I can't use a for loop + #And have to set the arg manually + def bonusSpellLevelSetArg(self, slotLevel): + self.AddHook(ET_OnConditionAdd, EK_NONE, setSpellLevelArg, (slotLevel,)) + +bonusSpellSorc0 = BonusSpellModifier("Bonus Spell Sorc 0") +bonusSpellSorc0.MapToFeat("Bonus Spell (Sorcerer 0)", feat_cond_arg2 = stat_level_sorcerer) +bonusSpellSorc0.bonusSpellLevelSetArg(0) + +bonusSpellSorc1 = BonusSpellModifier("Bonus Spell Sorc 1") +bonusSpellSorc1.MapToFeat("Bonus Spell (Sorcerer 1)", feat_cond_arg2 = stat_level_sorcerer) +bonusSpellSorc1.bonusSpellLevelSetArg(1) + +bonusSpellSorc2 = BonusSpellModifier("Bonus Spell Sorc 2") +bonusSpellSorc2.MapToFeat("Bonus Spell (Sorcerer 2)", feat_cond_arg2 = stat_level_sorcerer) +bonusSpellSorc2.bonusSpellLevelSetArg(2) + +bonusSpellSorc3 = BonusSpellModifier("Bonus Spell Sorc 3") +bonusSpellSorc3.MapToFeat("Bonus Spell (Sorcerer 3)", feat_cond_arg2 = stat_level_sorcerer) +bonusSpellSorc3.bonusSpellLevelSetArg(3) + +bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 0") +bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 0)", feat_cond_arg2 = stat_level_wizard) +bonusSpellWiz0.bonusSpellLevelSetArg(0) + +bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 1") +bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 1)", feat_cond_arg2 = stat_level_wizard) +bonusSpellWiz0.bonusSpellLevelSetArg(1) + +bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 2") +bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 0)", feat_cond_arg2 = stat_level_wizard) +bonusSpellWiz0.bonusSpellLevelSetArg(2) + +bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 2") +bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 2)", feat_cond_arg2 = stat_level_wizard) +bonusSpellWiz0.bonusSpellLevelSetArg(2) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index a83b6f6d4..a1b6470a4 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -131,12 +131,9 @@ def breathWeaponRadial(attachee, args, evt_obj): else: chargesLeft = args.get_arg(1) maxCharges = breath_weapon.getMaxCharges(attachee) - print "chargesLeft: {}, maxCharges: {}".format(chargesLeft, maxCharges) heritage = attachee.d20_query("PQ_Selected_Draconic_Heritage") - print "heritage: {}".format(heritage) breathWeaponShape = heritage_feat_utils.getDraconicHeritageBreathShape(heritage) - print "breathWeaponShape: {}".format(breathWeaponShape) - spellEnum = 3231 if breathWeaponShape == dragon_breath_shape_cone else 3232 + spellEnum = spell_dragon_diciple_cone_breath if breathWeaponShape == dragon_breath_shape_cone else spell_dragon_diciple_line_breath breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({}/{})".format(chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") spellData = tpdp.D20SpellData(spellEnum) spellData.set_spell_class(classEnum) @@ -156,7 +153,7 @@ def performBreathWeapon(attachee, args, evt_obj): currentSequence = tpactions.get_cur_seq() spellPacket = currentSequence.spell_packet newSpellId = tpactions.get_new_spell_id() - spellPacket.caster_level = attachee.stat_level_get(classEnum) + spellPacket.caster_level += attachee.stat_level_get(classEnum) tpactions.register_spell_cast(spellPacket, newSpellId) currentSequence.spell_packet.spell_id = newSpellId @@ -169,16 +166,6 @@ def performBreathWeapon(attachee, args, evt_obj): def frameBreathWeapon(attachee, args, evt_obj): genderString = "his" if attachee.stat_level_get(stat_gender) == 1 else "her" game.create_history_freeform("{} uses {} ~Breath Weapon~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON]\n\n".format(attachee.description, genderString)) -# currentSequence = tpactions.get_cur_seq() -# spellPacket = currentSequence.spell_packet -# newSpellId = tpactions.get_new_spell_id() -# print "spellPacket.caster_level: {}".format(spellPacket.caster_level) -# spellPacket.caster_level = attachee.stat_level_get(classEnum) - # Setting the dc here does not affect the spell when triggered down below :( -# spellPacket.dc = 10 + attachee.stat_level_get(classEnum) + ((attachee.stat_level_get(stat_constitution)-10)/2) -# print "Debug Spell DC Breath Weapon: {}".format(spellPacket.dc) -# tpactions.register_spell_cast(spellPacket, newSpellId) -# tpactions.trigger_spell_effect(newSpellId) #Send Breath Weapon Used Signal breathWeaponId = args.get_arg(0) attachee.d20_send_signal("PS_Breath_Weapon_Used", breathWeaponId) @@ -265,75 +252,3 @@ def elementImmunity(attachee, args, evt_obj): dragonDiscipleApotheosis.MapToFeat("Dragon Disciple Dragon Apotheosis") dragonDiscipleApotheosis.AddHook(ET_OnConditionAddPre, EK_NONE, sleepParalyzeImmunity, ()) dragonDiscipleApotheosis.AddHook(ET_OnTakingDamage2, EK_NONE, elementImmunity, ()) - - -#region Spell casting - -# configure the spell casting condition to hold the highest Arcane classs -def OnAddSpellCasting(attachee, args, evt_obj): - #arg0 holds the arcane class - if (args.get_arg(0) == 0): - args.set_arg(0, char_class_utils.GetHighestArcaneClass(attachee)) - - return 0 - -# Extend caster level for base casting class -def OnGetBaseCasterLevel(attachee, args, evt_obj): - class_extended_1 = args.get_arg(0) - class_code = evt_obj.arg0 - if (class_code != class_extended_1): - if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution - return 0 - classLvl = attachee.stat_level_get(classEnum) - if classLvl > 1: - evt_obj.bonus_list.add(classLvl - 1, 0, 137) - return 0 - -def OnSpellListExtensionGet(attachee, args, evt_obj): - class_extended_1 = args.get_arg(0) - class_code = evt_obj.arg0 - if (class_code != class_extended_1): - if (evt_obj.arg1 == 0): # arg1 != 0 means you're looking for this particular class's contribution - return 0 - classLvl = attachee.stat_level_get(classEnum) - if classLvl > 1: - evt_obj.bonus_list.add(classLvl - 1, 0, 137) - return 0 - -def OnInitLevelupSpellSelection(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - classLvl = attachee.stat_level_get(classEnum) - if (classLvl == 0): - return 0 - class_extended_1 = args.get_arg(0) - classSpecModule.InitSpellSelection(attachee, class_extended_1) - return 0 - -def OnLevelupSpellsCheckComplete(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - class_extended_1 = args.get_arg(0) - if (not classSpecModule.LevelupCheckSpells(attachee, class_extended_1) ): - evt_obj.bonus_list.add(-1, 0, 137) # denotes incomplete spell selection - return 1 - -def OnLevelupSpellsFinalize(attachee, args, evt_obj): - if (evt_obj.arg0 != classEnum): - return 0 - classLvl = attachee.stat_level_get(classEnum) - if (classLvl == 0): - return 0 - class_extended_1 = args.get_arg(0) - classSpecModule.LevelupSpellsFinalize(attachee, class_extended_1) - return - -spellCasterSpecObj = PythonModifier(GetSpellCasterConditionName(), 8) -spellCasterSpecObj.AddHook(ET_OnConditionAdd, EK_NONE, OnAddSpellCasting, ()) -spellCasterSpecObj.AddHook(ET_OnGetBaseCasterLevel, EK_NONE, OnGetBaseCasterLevel, ()) -spellCasterSpecObj.AddHook(ET_OnSpellListExtensionGet, EK_NONE, OnSpellListExtensionGet, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Activate, OnInitLevelupSpellSelection, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Check_Complete, OnLevelupSpellsCheckComplete, ()) -spellCasterSpecObj.AddHook(ET_OnLevelupSystemEvent, EK_LVL_Spells_Finalize, OnLevelupSpellsFinalize, ()) - -#endregion \ No newline at end of file From a43fa6aaa0158d33e32db77398996ce9bfb1d331 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Thu, 25 Nov 2021 09:04:09 +0100 Subject: [PATCH 18/32] Forgot to merge the draconic_heritage feat to the DD --- .../scr/tpModifiers/draconic_heritage.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tpdatasrc/tpgamefiles/scr/tpModifiers/draconic_heritage.py diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/draconic_heritage.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/draconic_heritage.py new file mode 100644 index 000000000..81dfe9bf2 --- /dev/null +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/draconic_heritage.py @@ -0,0 +1,78 @@ +from templeplus.pymod import PythonModifier +from toee import * +import tpdp +import char_class_utils +import heritage_feat_utils + +# Draconic Heritage: Complete Arcane, p. 77 + +print "Registering Draconic Heritage" + +def getDraconicHeritageClassSkill(heritage): + skillDict = { + heritage_draconic_black: skill_hide, + heritage_draconic_blue: skill_listen, + heritage_draconic_green: skill_move_silently, + heritage_draconic_red: skill_intimidate, + heritage_draconic_white: skill_balance, + heritage_draconic_brass: skill_gather_information, + heritage_draconic_bronze: skill_wilderness_lore, + heritage_draconic_copper: skill_hide, + heritage_draconic_gold: skill_heal, + heritage_draconic_silver: skill_disguise + } + return skillDict[heritage] + +def getSaveDescriptor(heritageElement): + if heritageElement == D20DT_ACID: + return D20STD_F_SPELL_DESCRIPTOR_ACID + elif heritageElement == D20DT_COLD: + return D20STD_F_SPELL_DESCRIPTOR_COLD + elif heritageElement == D20DT_ELECTRICITY: + return D20STD_F_SPELL_DESCRIPTOR_ELECTRICITY + elif heritageElement == D20DT_FIRE: + return D20STD_F_SPELL_DESCRIPTOR_FIRE + return 0 + +def addClassSkill(attachee, args, evt_obj): + heritage = args.get_arg(1) + skillEnum = getDraconicHeritageClassSkill(heritage) + if evt_obj.data1 == skillEnum: + evt_obj.return_val = 1 + return 0 + +def addSavingThrowBonus(attachee, args, evt_obj): + if attachee.stat_level_get(stat_level_sorcerer) > 0: + heritage = args.get_arg(1) + heritageElement = heritage_feat_utils.getDraconicHeritageElement(heritage) + saveDescriptor = getSaveDescriptor(heritageElement) + flags = evt_obj.flags + #This is not working properly, because as soon as the spell uses reflex_save_and_damage + #the Save Descriptor gets dropped :( + #I did not use reflex_save_and_damage for my damage spells in the spell compendium + #but instead the normal saving_throw_spell and then dealt damage afterwards with + #spell_damage_with_reduction/spell_damage which leads to the situation + #that the below save bonus works with the spell compendium spells + #but not with the core spells. + print "flags: {}".format(flags) + if (flags & (1 << (saveDescriptor-1))): + bonusValue = heritage_feat_utils.countHeritageFeats(attachee, heritage) + bonusType = 0 # ID 0 = Untyped (stacking) + evt_obj.bonus_list.add(bonusValue ,bonusType ,"~Draconic Heritage~[TAG_DRACONIC_HERITAGE]") + #elif #Sleep and Paralyze missing + #there are no slepp or paralyze flags/descriptors atm, which means, you can do immunities to both types + #but no save bonus effects + return 0 + +def querySelectedHeritage(attachee, args, evt_obj): + heritage = args.get_arg(1) + evt_obj.return_val = heritage + return 0 + +draconicHeritageFeat = PythonModifier("Draconic Heritage Feat", 3) #featEnum, heritage, empty +for heritage in range(heritage_draconic_black, heritage_draconic_white + 1): + colourString = heritage_feat_utils.getDraconicHeritageColourString(heritage) + draconicHeritageFeat.MapToFeat("Draconic Heritage {}".format(colourString), feat_cond_arg2 = heritage) +draconicHeritageFeat.AddHook(ET_OnD20PythonQuery, "PQ_Selected_Draconic_Heritage", querySelectedHeritage, ()) +draconicHeritageFeat.AddHook(ET_OnSaveThrowLevel, EK_NONE, addSavingThrowBonus, ()) +draconicHeritageFeat.AddHook(ET_OnD20PythonQuery, "Is Class Skill", addClassSkill, ()) From 8f9182eed9d21d3688ae924b93ed38b90fad7118 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Mon, 6 Dec 2021 23:34:09 +0100 Subject: [PATCH 19/32] Added new error codes --- TemplePlus/action_sequence.cpp | 7 +- TemplePlus/action_sequence.h | 5 +- tpdata/templeplus/lib/templeplus/constants.py | 108 +++++++++++++++++- tpdatasrc/tpgamefiles/mes/action_ext.mes | 5 + .../scr/tpModifiers/dragon_disciple.py | 2 +- 5 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 tpdatasrc/tpgamefiles/mes/action_ext.mes diff --git a/TemplePlus/action_sequence.cpp b/TemplePlus/action_sequence.cpp index 34cf595e9..81fd99a6a 100644 --- a/TemplePlus/action_sequence.cpp +++ b/TemplePlus/action_sequence.cpp @@ -4308,12 +4308,15 @@ const char*actionErrorCodeStrings[] = "AEC_NEED_A_STRAIGHT_LINE", "AEC_NO_ACTIONS", "AEC_NOT_IN_COMBAT", - "AEC_AREA_NOT_SAFE" + "AEC_AREA_NOT_SAFE", + "AEC_ABILITY_ON_COOLDOWN", + "AEC_ALREADY_USED_THIS_TURN", + "AEC_ALREADY_ACTIVE" }; ostream & operator<<(ostream & str, ActionErrorCode aec) { size_t i = (size_t)aec; - if (i <= AEC_AREA_NOT_SAFE) { + if (i <= AEC_ALREADY_ACTIVE) { str << actionErrorCodeStrings[i]; } else { diff --git a/TemplePlus/action_sequence.h b/TemplePlus/action_sequence.h index 795bf8f97..5fbdc3894 100644 --- a/TemplePlus/action_sequence.h +++ b/TemplePlus/action_sequence.h @@ -48,7 +48,10 @@ enum ActionErrorCode : uint32_t AEC_NEED_A_STRAIGHT_LINE, AEC_NO_ACTIONS, AEC_NOT_IN_COMBAT, - AEC_AREA_NOT_SAFE + AEC_AREA_NOT_SAFE, + AEC_ABILITY_ON_COOLDOWN, + AEC_ALREADY_USED_THIS_TURN, + AEC_ALREADY_ACTIVE }; // Allows for direct use of ActionErrorCode in format() strings diff --git a/tpdata/templeplus/lib/templeplus/constants.py b/tpdata/templeplus/lib/templeplus/constants.py index 3a475cafa..2ba057910 100644 --- a/tpdata/templeplus/lib/templeplus/constants.py +++ b/tpdata/templeplus/lib/templeplus/constants.py @@ -3358,8 +3358,105 @@ spell_new_slot_lvl_8 = 1613 spell_new_slot_lvl_9 = 1614 -spell_dragon_disciple_cone_breath = 3231 -spell_dragon_disciple_line_breath = 3232 +# Python Action Spells +spell_epic_of_the_lost_king = 3080 +spell_ki_blast = 3120 + +#Dragon Disciple Breath Weapons +spell_dragon_diciple_cone_breath = 3231 +spell_dragon_diciple_line_breath = 3232 + +# Dragon Lineage Spell Like Abilities +spell_dragon_lineage_black = 3242 +spell_dragon_lineage_blue = 3243 +spell_dragon_lineage_brass = 3244 +spell_dragon_lineage_bronze = 3245 +spell_dragon_lineage_copper = 3246 +spell_dragon_lineage_gold = 3247 +spell_dragon_lineage_green = 3248 +spell_dragon_lineage_red = 3249 +spell_dragon_lineage_silver = 3250 +spell_dragon_lineage_white = 3251 + +# Marshal Spells +spell_marshal_minor_aura = 3831 +spell_marshal_major_aura = 3832 +spell_marshal_adrenaline_boost = 3833 + +# Dragon Shaman Spells +spell_draconic_aura = 3840 +spell_double_draconic_aura = 3841 + +# Dummy Spells for Python Actions +spell_dummy_spell_cone_small = 3900 +spell_dummy_spell_cone_medium = 3901 +spell_dummy_spell_cone_large = 3902 +spell_dummy_spell_line_small = 3903 +spell_dummy_spell_line_medium = 3904 +spell_dummy_spell_line_large = 3905 + +# Dragon Breath Shapes +dragon_breath_shape_cone = 1 +dragon_breath_shape_line = 2 + +# Heritage Types +heritage_draconic_black = 1 +heritage_draconic_blue = 2 +heritage_draconic_brass = 3 +heritage_draconic_bronze = 4 +heritage_draconic_copper = 5 +heritage_draconic_gold = 6 +heritage_draconic_green = 7 +heritage_draconic_red = 8 +heritage_draconic_silver = 9 +heritage_draconic_white = 10 +heritage_celestial_sorcerer = 11 +heritage_fey = 12 +heritage_fiendish = 13 +heritage_infernal_sorcerer = 14 + +# Aura Types +aura_type_minor = 1 +aura_type_major = 2 +aura_type_draconic = 3 +aura_type_double_draconic = 4 + +#Aura Enums +aura_accurate_strike = 1 +aura_art_of_war = 2 +aura_Demand_fortitude = 3 +aura_Determined_caster = 4 +aura_force_of_will = 5 +aura_master_of_opportunity = 6 +aura_master_of_tactics = 7 +aura_motivate_charisma = 8 +aura_motivate_constitution = 9 +aura_motivate_dexterity = 10 +aura_motivate_intelligence = 11 +aura_motivate_strength = 12 +aura_motivate_wisdom = 13 +aura_over_the_top = 14 +aura_watchful_eye = 15 +aura_hardy_soldiers = 16 +aura_motivate_ardor = 17 +aura_motivate_attack = 18 +aura_motivate_care = 19 +aura_motivate_urgency = 20 +aura_resilient_troops = 21 +aura_steady_hand = 22 +aura_energy_shield = 23 +aura_power = 24 +aura_presence = 25 +aura_resistance = 26 +aura_senses = 27 +aura_toughness = 28 +aura_vigor = 29 +aura_break_spell_resistance = 30 +aura_energy = 31 +aura_insight = 32 +aura_resolve = 33 +aura_stamina = 34 +aura_swiftness =35 stat_strength = 0 stat_dexterity = 1 @@ -3440,6 +3537,10 @@ stat_level_shadow_sun_ninja = 81 stat_level_fochlucan_lyrist = 82 stat_level_marshal = 83 +stat_level_dragon_shaman = 84 +stat_level_dragonheart_mage = 85 +stat_level_battle_howler_of_gruumsh = 86 +stat_level_fist_of_the_forest = 87 @@ -4090,6 +4191,9 @@ AEC_NO_ACTIONS = 24 AEC_NOT_IN_COMBAT = 25 AEC_AREA_NOT_SAFE = 26 +AEC_ABILITY_ON_COOLDOWN = 27 +AEC_ALREADY_USED_THIS_TURN = 28 +AEC_ALREADY_ACTIVE = 29 BM_INSPIRE_COURAGE = 1 diff --git a/tpdatasrc/tpgamefiles/mes/action_ext.mes b/tpdatasrc/tpgamefiles/mes/action_ext.mes new file mode 100644 index 000000000..6dc08f6fb --- /dev/null +++ b/tpdatasrc/tpgamefiles/mes/action_ext.mes @@ -0,0 +1,5 @@ +// New error codes added by Sagenlicht + +{1027}{Ability on Cooldown} +{1028}{Already used this turn} +{1029}{Already active!} diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index a1b6470a4..918b041e8 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -146,7 +146,7 @@ def checkBreathWeapon(attachee, args, evt_obj): if args.get_arg(1) < 1: evt_obj.return_val = AEC_OUT_OF_CHARGES elif args.get_arg(2) > -1: - evt_obj.return_val = AEC_INVALID_ACTION + evt_obj.return_val = AEC_ABILITY_ON_COOLDOWN return 0 def performBreathWeapon(attachee, args, evt_obj): From 1586499858c6e8919833c2329f83dcd845eb501d Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Wed, 22 Dec 2021 11:53:23 +0200 Subject: [PATCH 20/32] Condition hashtable init replacement; added log message for when it overflows --- TemplePlus/condition.cpp | 25 +++++++++++++++++++++++++ TemplePlus/condition.h | 19 +++---------------- TemplePlus/config/config.cpp | 1 + TemplePlus/hashtable.h | 17 +++++++++++++---- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp index b92eba5af..24a4595b7 100644 --- a/TemplePlus/condition.cpp +++ b/TemplePlus/condition.cpp @@ -338,6 +338,9 @@ class ConditionFunctionReplacement : public TempleFix { void apply() override { logger->info("Replacing Condition-related Functions"); + replaceFunction(0x100E19A0, []() { + conds.hashmethods.ConditionHashtableInit(conds.mCondStructHashtable); + }); //dispTypeConditionAddPre static int(__cdecl* orgTempAbilityLoss)(DispatcherCallbackArgs) = replaceFunction(0x100EA1F0, [](DispatcherCallbackArgs args) { Stat statDamaged = (Stat)args.GetCondArg(0); @@ -7449,3 +7452,25 @@ void CondStructNew::AddAoESpellRemover() { AddHook(dispTypeD20Signal, DK_SIG_Spell_End, spCallbacks.AoeSpellRemove); } +uint32_t CondHashSystem::ConditionHashtableInit(ToEEHashtable* hashtable) +{ + const int VANILLA_COND_CAP = 1000; + return HashtableInit(hashtable, VANILLA_COND_CAP /*2023*/); +} + +uint32_t CondHashSystem::CondStructAddToHashtable(CondStruct* condStruct, bool overriding) +{ + + uint32_t key = StringHash(condStruct->condName); + CondStruct* condFound; + uint32_t result = HashtableSearch(condHashTable, key, &condFound); + if (result || overriding) + { + result = HashtableOverwriteItem(condHashTable, key, condStruct); + } + if (result == 3) { // over capacity + logger->error("Condition hashtable over capacity ({})! Trying to add {}", condHashTable->capacity, condStruct->condName); + } + return result; + +} diff --git a/TemplePlus/condition.h b/TemplePlus/condition.h index 34ab7afdd..9b3a7811c 100644 --- a/TemplePlus/condition.h +++ b/TemplePlus/condition.h @@ -22,22 +22,9 @@ struct CondHashSystem : ToEEHashtableSystem < CondStruct > temple::Dll::RegisterAddressPtr((void**)&condHashTable); } - uint32_t ConditionHashtableInit(ToEEHashtable * hashtable) - { - return HashtableInit(hashtable, 1000); - } - - uint32_t CondStructAddToHashtable(CondStruct * condStruct, bool overriding = false) - { - uint32_t key = StringHash(condStruct->condName); - CondStruct * condFound; - uint32_t result = HashtableSearch(condHashTable, key, &condFound); - if (result || overriding) - { - result = HashtableOverwriteItem(condHashTable, key, condStruct); - } - return result; - } + uint32_t ConditionHashtableInit(ToEEHashtable* hashtable); + + uint32_t CondStructAddToHashtable(CondStruct* condStruct, bool overriding = false); int GetCondStructHashkey(CondStruct* condStruct) { diff --git a/TemplePlus/config/config.cpp b/TemplePlus/config/config.cpp index 9c735b7c1..c6f383529 100644 --- a/TemplePlus/config/config.cpp +++ b/TemplePlus/config/config.cpp @@ -129,6 +129,7 @@ static ConfigSetting configSettings[] = { CONF_BOOL(antialiasing), CONF_BOOL(softShadows), CONF_BOOL(noSound), + //CONF_BOOL(showFps), CONF_BOOL(featPrereqWarnings), CONF_BOOL(spellAlreadyKnownWarnings), CONF_BOOL(NPCsLevelLikePCs), diff --git a/TemplePlus/hashtable.h b/TemplePlus/hashtable.h index c9bb81608..c78e3e573 100644 --- a/TemplePlus/hashtable.h +++ b/TemplePlus/hashtable.h @@ -8,11 +8,13 @@ struct ToEEHashtable : temple::TempleAlloc { uint32_t numItems; uint32_t capacity; - uint32_t powerOfTwo; - uint32_t * keyArray; + uint32_t powerOfTwo; // 2*capacity, rounded up to power of 2 + uint32_t * keyArray; // capacity: powerOfTwo T** dataArray; - uint32_t * idxArray; + uint32_t * idxArray; // capacity: capacity uint32_t pad; + + uint32_t Init(uint32_t capacity); }; template @@ -22,7 +24,8 @@ struct ToEEHashtableSystem : temple::AddressTable { hashtableOut->capacity = capacity; uint32_t powerOfTwo = 1; - for (hashtableOut->numItems = 0; powerOfTwo < 2 * capacity; powerOfTwo *= 2); + hashtableOut->numItems = 0; + for (; powerOfTwo < 2 * capacity; powerOfTwo *= 2); hashtableOut->powerOfTwo = powerOfTwo; hashtableOut->dataArray = _dataArrayNew(powerOfTwo); @@ -152,3 +155,9 @@ struct ToEEHashtableSystem : temple::AddressTable uint32_t(__cdecl* ELFhash)(char * stringIn); }; + +template +inline uint32_t ToEEHashtable::Init(uint32_t capacity) +{ + return uint32_t(); +} From b43a73d866eae93fb0cce5d7b7f6655543d75cbe Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Wed, 22 Dec 2021 14:45:56 +0200 Subject: [PATCH 21/32] reflex throw will now use SpellSaveThrow when used for spell actions, so it'll correctly apply the spell descriptors --- TemplePlus/damage.cpp | 74 ++++++++++++++++++++++++++++++++++++--- TemplePlus/dispatcher.cpp | 26 ++++++++++++++ TemplePlus/dispatcher.h | 4 ++- 3 files changed, 98 insertions(+), 6 deletions(-) diff --git a/TemplePlus/damage.cpp b/TemplePlus/damage.cpp index 4706e58f5..8db22c89b 100644 --- a/TemplePlus/damage.cpp +++ b/TemplePlus/damage.cpp @@ -21,6 +21,8 @@ #include "pybind11/pybind11.h" #include "python/python_dice.h" +const int DAMAGE_MES_UNKNOWN = 103; + namespace py = pybind11; template <> class py::detail::type_caster { @@ -962,16 +964,78 @@ bool Damage::SavingThrowSpell(objHndl obj, objHndl attacker, int dc, SavingThrow // return addresses.SavingThrowSpell(obj, attacker, dc, type, flags, spellId); } -bool Damage::ReflexSaveAndDamage(objHndl obj, objHndl attacker, int dc, int reduction, int flags, const Dice& dice, DamageType damageType, int attackPower, D20ActionType actionType, int spellId) { - SpellPacketBody spPkt(spellId); - BonusList bonlist; +int GetTargetSpellDcBonus(objHndl attacker, objHndl obj, SpellPacketBody &spPkt) { + if (!spPkt.spellEnum) { + return 0; + } // Gets a DC bonus based on the target of the spell + BonusList bonlist; dispatch.DispatchTargetSpellDCBonus(attacker, obj, &bonlist, &spPkt); - int nDCBonus = bonlist.GetEffectiveBonusSum(); + return nDCBonus; +} + +/* 0x100B9500 */ +// Note: this is also used in traps, where spellId = 0 +bool Damage::ReflexSaveAndDamage(objHndl obj, objHndl attacker, int dc, int reduction, int flags, const Dice& dice, DamageType damageType, int attackPower, D20ActionType actionType, int spellId) { + //return addresses.ReflexSaveAndDamage(obj, attacker, dc, reduction, flags, dice.ToPacked(), damageType, attackPower, actionType, spellId); + + auto isSpellSave = actionType == D20A_CAST_SPELL; // todo: might require generalization for new action types? - return addresses.ReflexSaveAndDamage(obj, attacker, dc, reduction, flags, dice.ToPacked(), damageType, attackPower, actionType, spellId); + DispIoReflexThrow evtObj; + evtObj.reduction = (D20SavingThrowReduction)reduction; + evtObj.attackPower = (D20AttackPower)attackPower; + evtObj.damageMesLine = 105; // {105}{~Saving Throw~[TAG_SAVING_THROW_DESC]} + evtObj.attackType = (int)damageType; + evtObj.flags = (D20SavingThrowFlag)flags; + + if (isSpellSave) { // in vanilla, this always called SavingThrow, which would not apply the descriptor flags (and also the new DC bonus dispatch) + evtObj.throwResult = SavingThrowSpell(obj, attacker, dc, SavingThrowType::Reflex, flags, spellId); + } + else { + evtObj.throwResult = SavingThrow(obj, attacker, dc, SavingThrowType::Reflex, flags); + } + + auto caf = D20CAF_NONE; + if (evtObj.throwResult == 1) { + caf = D20CAF_SAVE_SUCCESSFUL; + if (reduction == D20SavingThrowReduction::D20_Save_Reduction_None) { + evtObj.effectiveReduction = 0; + } + else if (reduction == D20SavingThrowReduction::D20_Save_Reduction_Half) { + evtObj.effectiveReduction = 50; + } + else if (reduction == D20SavingThrowReduction::D20_Save_Reduction_Quarter) { + evtObj.effectiveReduction = 25; + } + else { + evtObj.effectiveReduction = 100; + } + } + + + dispatch.Dispatch49ReflexSaveReduction(obj, &evtObj); + if (evtObj.effectiveReduction == 100) { + if (isSpellSave) { + DealSpellDamage(obj, attacker, dice, (DamageType)evtObj.attackType, evtObj.attackPower, + 100, DAMAGE_MES_UNKNOWN, D20A_CAST_SPELL, spellId, caf); + } + else { + DealDamage(obj, attacker, dice, (DamageType)evtObj.attackType, evtObj.attackPower, + 100, DAMAGE_MES_UNKNOWN, actionType); + } + } + else if (isSpellSave) { + DealSpellDamage(obj, attacker, dice, (DamageType)evtObj.attackType, evtObj.attackPower, + evtObj.effectiveReduction, evtObj.damageMesLine, D20A_CAST_SPELL, spellId, caf); + } + else { + DealDamage(obj, attacker, dice, (DamageType)evtObj.attackType, evtObj.attackPower, + 100, evtObj.damageMesLine, actionType); + } + + return evtObj.throwResult; } void Damage::DamagePacketInit(DamagePacket* dmgPkt) diff --git a/TemplePlus/dispatcher.cpp b/TemplePlus/dispatcher.cpp index cbe35e39b..0d9669a18 100644 --- a/TemplePlus/dispatcher.cpp +++ b/TemplePlus/dispatcher.cpp @@ -349,6 +349,20 @@ int DispatcherSystem::Dispatch44FinalSaveThrow(objHndl handle, SavingThrowType s return DispatchSavingThrow(handle, evtObj, dispTypeCountersongSaveThrow, (D20DispatcherKey)((int)saveType + D20DispatcherKey::DK_SAVE_FORTITUDE)); } +int DispatcherSystem::Dispatch49ReflexSaveReduction(objHndl handle, DispIoReflexThrow* evtObj) +{ + auto obj = objSystem->GetObject(handle); + if (!obj) + return 0; + + auto dispatcher = obj->GetDispatcher(); + if (!dispatcherValid(dispatcher)) + return 0; + + dispatcher->Process(dispTypeReflexThrow, D20DispatcherKey::DK_NONE, evtObj); + return evtObj->effectiveReduction; +} + DispIoCondStruct* DispatcherSystem::DispIoCheckIoType1(DispIoCondStruct* dispIo) { @@ -1684,3 +1698,15 @@ DispIoSpellsPerDay::DispIoSpellsPerDay() unk = 3001; unk2 = 0; } + +DispIoReflexThrow::DispIoReflexThrow() +{ + dispIOType = dispIOTypeReflexThrow; + effectiveReduction = 100; + reduction = D20SavingThrowReduction::D20_Save_Reduction_None; // 0 + damageMesLine = 103; // Unknown + attackPower = D20AttackPower::D20DAP_UNSPECIFIED; // 1 + attackType = (int)DamageType::Unspecified; // -1 + throwResult = 0; + flags = D20SavingThrowFlag::D20STF_NONE; +} diff --git a/TemplePlus/dispatcher.h b/TemplePlus/dispatcher.h index e3fd8c5b6..f5ef3b11c 100644 --- a/TemplePlus/dispatcher.h +++ b/TemplePlus/dispatcher.h @@ -23,6 +23,7 @@ struct DispIoDispelCheck; // 11 struct DispIoD20ActionTurnBased; // 12 struct DispIoMoveSpeed; //13 struct DispIOBonusListAndSpellEntry; // 14 +struct DispIoReflexThrow; // 15 struct DispIoObjEvent; // 17 struct DispIoSpellsPerDay; // 18 struct DispIoAbilityLoss; // 19 @@ -72,7 +73,7 @@ struct DispatcherSystem : temple::AddressTable int Dispatch13SavingThrow(objHndl handle, SavingThrowType saveType, DispIoSavingThrow* evtObj); int Dispatch14SavingThrowMod(objHndl handle, SavingThrowType saveType, DispIoSavingThrow* evtObj); int Dispatch44FinalSaveThrow(objHndl handle, SavingThrowType saveType, DispIoSavingThrow* evtObj); - + int Dispatch49ReflexSaveReduction(objHndl handle, DispIoReflexThrow* evtObj); #pragma region event object checkers @@ -457,6 +458,7 @@ struct DispIoReflexThrow : DispIO { // DispIoType = 15 int attackType; int throwResult; D20SavingThrowFlag flags; + DispIoReflexThrow(); }; struct DispIoObjEvent : DispIO // type 17 From e21d76653e592c42840cfc53852092db3820e3b2 Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Wed, 22 Dec 2021 15:19:41 +0200 Subject: [PATCH 22/32] Added get_caster_class method to python EventObjSpellsPerDay --- TemplePlus/python/python_dispatcher.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TemplePlus/python/python_dispatcher.cpp b/TemplePlus/python/python_dispatcher.cpp index 688d4900a..52d2cc33e 100644 --- a/TemplePlus/python/python_dispatcher.cpp +++ b/TemplePlus/python/python_dispatcher.cpp @@ -1175,7 +1175,10 @@ PYBIND11_EMBEDDED_MODULE(tpdp, m) { py::class_(m, "EventObjSpellsPerDay", "Used for retrieving spells per day mods. Resurrected in Temple+!") .def_readwrite("bonus_list", &DispIoSpellsPerDay::bonList) - .def_readwrite("caster_class", &DispIoSpellsPerDay::classCode) + //.def_readwrite("caster_class", &DispIoSpellsPerDay::classCode) + .def("get_caster_class", [](DispIoSpellsPerDay& self)->int { + return (int)(self.classCode); + }) .def_readwrite("spell_level", &DispIoSpellsPerDay::spellLvl) .def_readwrite("base_caster_level", &DispIoSpellsPerDay::casterEffLvl) ; From 05c1d8eadaf014be56e9713924668767dd2c298e Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Thu, 23 Dec 2021 19:04:14 +0200 Subject: [PATCH 23/32] Renamed chargeAfterPicker for more accurate meaning --- TemplePlus/action_sequence.cpp | 14 +++++++------- TemplePlus/d20.cpp | 26 +++++++++++++++----------- TemplePlus/d20_defs.h | 4 ++-- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/TemplePlus/action_sequence.cpp b/TemplePlus/action_sequence.cpp index 81fd99a6a..96a880e4a 100644 --- a/TemplePlus/action_sequence.cpp +++ b/TemplePlus/action_sequence.cpp @@ -1716,7 +1716,7 @@ int ActionSequenceSystem::TrimPathToRemainingMoveLength(D20Actn* d20a, float rem uint32_t ActionSequenceSystem::ActionCostNull(D20Actn* d20Actn, TurnBasedStatus* turnBasedStatus, ActionCostPacket* actionCostPacket) { actionCostPacket->hourglassCost = 0; - actionCostPacket->chargeAfterPicker = 0; + actionCostPacket->attackCost = 0; actionCostPacket->moveDistCost = 0; return 0; } @@ -3371,7 +3371,7 @@ BOOL ActionSequenceSystem::SimulsAdvance() int ActionSequenceSystem::ActionCostFullAttack(D20Actn* d20, TurnBasedStatus* tbStat, ActionCostPacket* acp) { - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; acp->hourglassCost = 4; int flags = d20->d20Caf; @@ -3512,13 +3512,13 @@ int ActionSequenceSystem::ActionCostProcess(TurnBasedStatus* tbStat, D20Actn* d2 if (tbStat->surplusMoveDistance >= actCost.moveDistCost) { tbStat->surplusMoveDistance -= actCost.moveDistCost; - if ( actCost.chargeAfterPicker <= 0 - || actCost.chargeAfterPicker + tbStat->attackModeCode <= tbStat->baseAttackNumCode + tbStat->numBonusAttacks) + if ( actCost.attackCost <= 0 + || actCost.attackCost + tbStat->attackModeCode <= tbStat->baseAttackNumCode + tbStat->numBonusAttacks) { - if ((int) tbStat->numBonusAttacks < actCost.chargeAfterPicker) - tbStat->attackModeCode += actCost.chargeAfterPicker; + if (actCost.attackCost > (int) tbStat->numBonusAttacks ) + tbStat->attackModeCode += actCost.attackCost; else - tbStat->numBonusAttacks -= actCost.chargeAfterPicker; + tbStat->numBonusAttacks -= actCost.attackCost; if (tbStat->attackModeCode == tbStat->baseAttackNumCode && !tbStat->numBonusAttacks) tbStat->tbsFlags &= ~TBSF_FullAttack; result = AEC_OK; diff --git a/TemplePlus/d20.cpp b/TemplePlus/d20.cpp index 45f1523dd..181ccb64a 100644 --- a/TemplePlus/d20.cpp +++ b/TemplePlus/d20.cpp @@ -2882,7 +2882,7 @@ ActionErrorCode D20ActionCallbacks::ActionCheckTripAttack(D20Actn* d20a, TurnBas ActionErrorCode D20ActionCallbacks::ActionCostCastSpell(D20Actn * d20a, TurnBasedStatus * tbStat, ActionCostPacket * acp){ acp->hourglassCost = 0; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0.0f; auto flags = d20a->d20Caf; if ( (flags & D20CAF_FREE_ACTION) || !combatSys.isCombatActive()){ @@ -2946,7 +2946,7 @@ ActionErrorCode D20ActionCallbacks::ActionCostCastSpell(D20Actn * d20a, TurnBase } ActionErrorCode D20ActionCallbacks::ActionCostFullRound(D20Actn* d20a, TurnBasedStatus* tbStat, ActionCostPacket* acp){ - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; acp->hourglassCost = 4; if ( (d20a->d20Caf & D20CAF_FREE_ACTION) || !combatSys.isCombatActive()) @@ -3772,7 +3772,7 @@ ActionErrorCode D20ActionCallbacks::TurnBasedStatusCheckPython(D20Actn* d20a, Tu } ActionErrorCode D20ActionCallbacks::ActionCostFullAttack(D20Actn * d20a, TurnBasedStatus * tbStat, ActionCostPacket * acp){ - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; acp->hourglassCost = 4; //int flags = d20a->d20Caf; @@ -3789,7 +3789,7 @@ ActionErrorCode D20ActionCallbacks::ActionCostFullAttack(D20Actn * d20a, TurnBas } ActionErrorCode D20ActionCallbacks::ActionCostPartialCharge(D20Actn * d20a, TurnBasedStatus * tbStat, ActionCostPacket * acp){ - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; acp->hourglassCost = 3; if ((d20a->d20Caf & D20CAF_FREE_ACTION) || !combatSys.isCombatActive()) @@ -3803,6 +3803,10 @@ ActionErrorCode D20ActionCallbacks::ActionCostPython(D20Actn* d20a, TurnBasedSta return (ActionErrorCode) d20Sys.GetPyActionCost(d20a, tbStat, acp); } +/* +0x100910F0 +Used in: Standard attack, Standard Ranged attack, Trip Attack, Touch Attack, Throw Weapon, Throw Grenade +*/ ActionErrorCode D20ActionCallbacks::ActionCostStandardAttack(D20Actn* d20a, TurnBasedStatus* tbStat, ActionCostPacket* acp){ if ( d20Sys.d20Query(d20a->d20APerformer, DK_QUE_HoldingCharge) @@ -3813,12 +3817,12 @@ ActionErrorCode D20ActionCallbacks::ActionCostStandardAttack(D20Actn* d20a, Turn } acp->hourglassCost = 0; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; if (!(d20a->d20Caf & D20CAF_FREE_ACTION) && combatSys.isCombatActive()) { - acp->chargeAfterPicker = 1; + acp->attackCost = 1; auto retainSurplusMoveDist = false; @@ -3841,7 +3845,7 @@ ActionErrorCode D20ActionCallbacks::ActionCostStandardAttack(D20Actn* d20a, Turn ActionErrorCode D20ActionCallbacks::ActionCostMoveAction(D20Actn *d20, TurnBasedStatus *tbStat, ActionCostPacket *acp) { acp->hourglassCost = 0; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; if (!(d20->d20Caf & D20CAF_FREE_ACTION) && combatSys.isCombatActive()) { @@ -3857,7 +3861,7 @@ ActionErrorCode D20ActionCallbacks::ActionCostMoveAction(D20Actn *d20, TurnBased ActionErrorCode D20ActionCallbacks::ActionCostNull(D20Actn* d20a, TurnBasedStatus* tbStat, ActionCostPacket* acp){ acp->hourglassCost = 0; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; return AEC_OK; } @@ -3869,7 +3873,7 @@ ActionErrorCode D20ActionCallbacks::ActionCostSwift(D20Actn* d20a, TurnBasedStat } acp->hourglassCost = 0; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; tbStat->tbsFlags |= TBSF_SwiftActionPerformed; return AEC_OK; @@ -3877,13 +3881,13 @@ ActionErrorCode D20ActionCallbacks::ActionCostSwift(D20Actn* d20a, TurnBasedStat ActionErrorCode D20ActionCallbacks::ActionCostStandardAction(D20Actn*, TurnBasedStatus*, ActionCostPacket*acp){ acp->hourglassCost = 2; - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; return AEC_OK; }; ActionErrorCode D20ActionCallbacks::ActionCostWhirlwindAttack(D20Actn* d20a, TurnBasedStatus* tbStat, ActionCostPacket*acp) { - acp->chargeAfterPicker = 0; + acp->attackCost = 0; acp->moveDistCost = 0; acp->hourglassCost = 4; if ( ( d20a->d20Caf & D20CAF_FREE_ACTION) || !combatSys.isCombatActive()){ diff --git a/TemplePlus/d20_defs.h b/TemplePlus/d20_defs.h index 9378dd67a..828f543f4 100644 --- a/TemplePlus/d20_defs.h +++ b/TemplePlus/d20_defs.h @@ -109,10 +109,10 @@ enum class ActionCostType : int { struct ActionCostPacket { int hourglassCost; - int chargeAfterPicker; // flag I think; is only set at stuff that requires using the picker it seems + int attackCost; // how many attacks does this consume when doing Full Attack? (0 if not relevant; haven't seen a value > 1) float moveDistCost; - ActionCostPacket() { hourglassCost = 0; chargeAfterPicker = 0; moveDistCost = 0.0f; } + ActionCostPacket() { hourglassCost = 0; attackCost = 0; moveDistCost = 0.0f; } }; //const auto TestSizeOfActionCostPacket = sizeof(ActionCostPacket); // should be 12 (0xC) From 5fe70c7d75ed8f81168bfce7b45b79b5524d00de Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Sat, 25 Dec 2021 00:03:09 +0200 Subject: [PATCH 24/32] Minor magic number cleanup from natural attacks review --- TemplePlus/action_sequence.cpp | 7 ++++--- TemplePlus/condition.cpp | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/TemplePlus/action_sequence.cpp b/TemplePlus/action_sequence.cpp index 96a880e4a..8bf039931 100644 --- a/TemplePlus/action_sequence.cpp +++ b/TemplePlus/action_sequence.cpp @@ -3782,12 +3782,13 @@ int ActionSequenceSystem::StdAttackTurnBasedStatusCheck(D20Actn* d20a, TurnBased return AEC_TARGET_INVALID; } + const int STD_ATK_HG_COST= 2; if (hgState != -1) - hgState = turnBasedStatusTransitionMatrix[hgState][2]; + hgState = turnBasedStatusTransitionMatrix[hgState][STD_ATK_HG_COST]; tbStat->hourglassState = hgState; - if (inventory.ItemWornAt(d20a->d20APerformer, 3) || dispatch.DispatchD20ActionCheck(d20a, tbStat, dispTypeGetCritterNaturalAttacksNum) <= 0) - tbStat->attackModeCode = 0; + if (inventory.ItemWornAt(d20a->d20APerformer, EquipSlot::WeaponPrimary) || dispatch.DispatchD20ActionCheck(d20a, tbStat, dispTypeGetCritterNaturalAttacksNum) <= 0) + tbStat->attackModeCode = ATTACK_CODE_PRIMARY; else tbStat->attackModeCode = ATTACK_CODE_NATURAL_ATTACK; tbStat->baseAttackNumCode = tbStat->attackModeCode + 1; diff --git a/TemplePlus/condition.cpp b/TemplePlus/condition.cpp index 24a4595b7..48bc1bf6a 100644 --- a/TemplePlus/condition.cpp +++ b/TemplePlus/condition.cpp @@ -1865,16 +1865,16 @@ int __cdecl GlobalToHitBonus(DispatcherCallbackArgs args) && !d20Sys.d20Query(args.objHndCaller, DK_QUE_Polymorphed) ) { int attackIdx = dispIo->attackPacket.dispKey - (ATTACK_CODE_NATURAL_ATTACK + 1); - int bonValue = 0; // temporarily used as an index value for obj_f_attack_bonus_idx field + int atkBonIdx = 0; for (int i = 0, j=0; i < 4; i++) { j += objects.getArrayFieldInt32(args.objHndCaller, obj_f_critter_attacks_idx, i); // number of attacks if (attackIdx < j){ - bonValue = i; + atkBonIdx = i; break; } } - bonValue = objects.getArrayFieldInt32(args.objHndCaller, obj_f_attack_bonus_idx, bonValue); + int bonValue = objects.getArrayFieldInt32(args.objHndCaller, obj_f_attack_bonus_idx, atkBonIdx); bonusSys.bonusAddToBonusList(&dispIo->bonlist, bonValue, 1, 118); // base attack } From 370c30b5ca8f098f831bec39d1572b18e7f21e16 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Tue, 18 Jan 2022 10:38:58 +0100 Subject: [PATCH 25/32] Changed Bonus Spells per Day * Changed Bonus Spells per Day to be in line with general PrC handling in ToEE (highest arcane class is automatically selected) *Minor Breath Weapon Fix --- .../char_class/class023_dragon_disciple.py | 26 ++++--- .../rules/feats/bonus spell bard 0.txt | 6 -- .../rules/feats/bonus spell bard 1.txt | 6 -- .../rules/feats/bonus spell bard 2.txt | 6 -- .../rules/feats/bonus spell bard 3.txt | 6 -- .../rules/feats/bonus spell sorcerer 0.txt | 6 -- .../rules/feats/bonus spell sorcerer 1.txt | 6 -- .../rules/feats/bonus spell sorcerer 2.txt | 6 -- .../rules/feats/bonus spell sorcerer 3.txt | 6 -- .../rules/feats/bonus spell wizard 0.txt | 6 -- .../rules/feats/bonus spell wizard 1.txt | 6 -- .../rules/feats/bonus spell wizard 2.txt | 6 -- .../rules/feats/bonus spell wizard 3.txt | 6 -- .../tpgamefiles/rules/feats/bonus spell.txt | 5 -- ...ragon disciple bonus spell per day (1).txt | 5 ++ ...ragon disciple bonus spell per day (2).txt | 5 ++ ...ragon disciple bonus spell per day (3).txt | 5 ++ ...ragon disciple bonus spell per day (4).txt | 5 ++ ...ragon disciple bonus spell per day (5).txt | 5 ++ ...ragon disciple bonus spell per day (6).txt | 5 ++ ...ragon disciple bonus spell per day (7).txt | 5 ++ .../scr/tpModifiers/bonus_spell.py | 68 ------------------- .../scr/tpModifiers/breath_weapon.py | 41 +++++------ .../scr/tpModifiers/dragon_disciple.py | 40 +++++++++-- 24 files changed, 105 insertions(+), 182 deletions(-) delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt delete mode 100644 tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (1).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (2).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (3).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (4).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (5).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (6).txt create mode 100644 tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (7).txt delete mode 100644 tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py diff --git a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py index c6fd820bd..b437e4e12 100644 --- a/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/rules/char_class/class023_dragon_disciple.py @@ -26,10 +26,14 @@ def GetClassHelpTopic(): class_feats = { -1: ("Dragon Disciple Natural Armor",), -2: ("Dragon Disciple Claws and Bite",), +1: ("Dragon Disciple Natural Armor", "Dragon Disciple Bonus Spell per Day (1)",), +2: ("Dragon Disciple Claws and Bite", "Dragon Disciple Bonus Spell per Day (2)",), 3: ("Dragon Disciple Breath Weapon",), -9: ("Dragon Disciple Wings",), +4: ("Dragon Disciple Bonus Spell per Day (3)",), +5: ("Dragon Disciple Bonus Spell per Day (4)",), +6: ("Dragon Disciple Bonus Spell per Day (5)",), +8: ("Dragon Disciple Bonus Spell per Day (6)",), +9: ("Dragon Disciple Wings", "Dragon Disciple Bonus Spell per Day (7)",), 10: ("Dragon Disciple Dragon Apotheosis",) } @@ -90,8 +94,8 @@ def KnowledgeArcanaCheck( obj ): def DragonRaceCheck(obj): #Can't check if character is half-dragon; it does not exist in ToEE if obj.is_category_type(mc_type_dragon): - return 1 - return 0 + return 0 + return 1 def ObjMeetsPrereqs(obj): if not KnowledgeArcanaCheck(obj): @@ -117,21 +121,15 @@ def alreadyHasDraconicHeritage(obj): def IsSelectingFeatsOnLevelup(obj): newLvl = char_editor.stat_level_get(classEnum) - if newLvl == 1: - if alreadyHasDraconicHeritage(obj): - return 0 - elif newLvl not in [2, 4, 5, 6, 8, 9]: - return 0 - return 1 + if newLvl == 1 and not alreadyHasDraconicHeritage(obj): + return 1 + return 0 def LevelupGetBonusFeats(obj): newLvl = char_editor.stat_level_get(classEnum) bonus_feats = [] if newLvl == 1: bonus_feats.append("Draconic Heritage") - elif newLvl in [2, 4, 5, 6, 8, 9]: - bonus_feats.append("Bonus Spell") - bonFeatInfo = [] for ft in bonus_feats: featInfo = char_editor.FeatInfo(ft) diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt deleted file mode 100644 index 2e7050e79..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 0.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Bard 0) -flags: 9 -prereqs: 8 1 -description: Grants a Bard bonus Spell per Day for Spell Level 0 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt deleted file mode 100644 index b4fc06deb..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 1.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Bard 1) -flags: 9 -prereqs: 8 2 -description: Grants a Bard bonus Spell per Day for Spell Level 1 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt deleted file mode 100644 index 78ba1274d..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 2.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Bard 2) -flags: 9 -prereqs: 8 4 -description: Grants a Bard bonus Spell per Day for Spell Level 2 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt deleted file mode 100644 index ec4a1b56c..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell bard 3.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Bard 3) -flags: 9 -prereqs: 8 7 -description: Grants a Bard bonus Spell per Day for Spell Level 3 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt deleted file mode 100644 index 57907f5a6..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 0.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Sorcerer 0) -flags: 9 -prereqs: 16 1 -description: Grants a Sorcerer bonus Spell per Day for Spell Level 0 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt deleted file mode 100644 index 5ecf732a4..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 1.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Sorcerer 1) -flags: 9 -prereqs: 16 1 -description: Grants a Sorcerer bonus Spell per Day for Spell Level 1 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt deleted file mode 100644 index 259e0d56a..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 2.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Sorcerer 2) -flags: 9 -prereqs: 16 4 -description: Grants a Sorcerer bonus Spell per Day for Spell Level 2 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt deleted file mode 100644 index eb76da192..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell sorcerer 3.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Sorcerer 3) -flags: 9 -prereqs: 16 6 -description: Grants a Sorcerer bonus Spell per Day for Spell Level 3 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt deleted file mode 100644 index c34c41fbd..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 0.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Wizard 0) -flags: 9 -prereqs: 17 1 -description: Grants a Wizard bonus Spell per Day for Spell Level 0 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt deleted file mode 100644 index 576110882..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 1.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Wizard 1) -flags: 9 -prereqs: 17 1 -description: Grants a Wizard bonus Spell per Day for Spell Level 1 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt deleted file mode 100644 index 0449bd9b0..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 2.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Wizard 2) -flags: 9 -prereqs: 17 3 -description: Grants a Wizard bonus Spell per Day for Spell Level 2 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt deleted file mode 100644 index 33c35c130..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell wizard 3.txt +++ /dev/null @@ -1,6 +0,0 @@ -name: Bonus Spell (Wizard 3) -flags: 9 -prereqs: 17 5 -description: Grants a Wizard bonus Spell per Day for Spell Level 3 -prereq descr: -parent: Bonus Spell \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt b/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt deleted file mode 100644 index bfca83c2c..000000000 --- a/tpdatasrc/tpgamefiles/rules/feats/bonus spell.txt +++ /dev/null @@ -1,5 +0,0 @@ -name: Bonus Spell -flags: 9 -prereqs: -description: Grants a Bonus Spell per Day -prereq descr: diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (1).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (1).txt new file mode 100644 index 000000000..d9728e8ec --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (1).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (1) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 1. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (2).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (2).txt new file mode 100644 index 000000000..458cda6d8 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (2).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (2) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 2. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (3).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (3).txt new file mode 100644 index 000000000..61d10dd18 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (3).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (3) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 4. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (4).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (4).txt new file mode 100644 index 000000000..3b1b02491 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (4).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (4) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 5. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (5).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (5).txt new file mode 100644 index 000000000..e605fb0c5 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (5).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (5) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 6. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (6).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (6).txt new file mode 100644 index 000000000..0bb3d6926 --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (6).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (6) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 8. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (7).txt b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (7).txt new file mode 100644 index 000000000..5e08d3ece --- /dev/null +++ b/tpdatasrc/tpgamefiles/rules/feats/dragon disciple bonus spell per day (7).txt @@ -0,0 +1,5 @@ +name: Dragon Disciple Bonus Spell per Day (7) +flags: 8 +prereqs: +description: Dragon Disciples gain bonus spells as they gain levels in this prestige class, as if from having a high ability score. The bonus spell is granted to the highest Arcane Casting Class and is applied to the highest spell level. +prereq descr: Dragon Disciple level 9. \ No newline at end of file diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py deleted file mode 100644 index 0f10095a1..000000000 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/bonus_spell.py +++ /dev/null @@ -1,68 +0,0 @@ -from templeplus.pymod import PythonModifier -from toee import * -import tpdp - -print "Registering Bonus Spell Slot Feats" - -def setSpellLevelArg(attachee, args, evt_obj): - spellLevel = args.get_param(0) - args.set_arg(2, spellLevel) - return 0 - -def applyExtraSpell(attachee, args, evt_obj): - classEnum = args.get_arg(1) - spellLevel = args.get_arg(2) - #I am unsure why evt_obj.caster_class is not working, can't get a print of it either - #print "evt_obj.caster_class: {}".format(evt_obj.caster_class) - #Disabeld for now due to this - #if not evt_obj.caster_class == classEnum: - # return 0 - if evt_obj.spell_level == spellLevel: - bonusValue = 1 - bonusType = 0 #ID 0 untyped (stacking) - evt_obj.bonus_list.add(bonusValue, bonusType, "Bonus Spell Slot") - return 0 - -class BonusSpellModifier(PythonModifier): - # BonusSpellModifier have 4 arguments: - # 0: featEnum, 1: classEnum, 2: spellLevel, 3: empty - def __init__(self, name): - PythonModifier.__init__(self, name, 4, False) - self.AddHook(ET_OnGetSpellsPerDayMod, EK_NONE, applyExtraSpell, ()) - - #There is no feat_cond_arg3 so I can't use a for loop - #And have to set the arg manually - def bonusSpellLevelSetArg(self, slotLevel): - self.AddHook(ET_OnConditionAdd, EK_NONE, setSpellLevelArg, (slotLevel,)) - -bonusSpellSorc0 = BonusSpellModifier("Bonus Spell Sorc 0") -bonusSpellSorc0.MapToFeat("Bonus Spell (Sorcerer 0)", feat_cond_arg2 = stat_level_sorcerer) -bonusSpellSorc0.bonusSpellLevelSetArg(0) - -bonusSpellSorc1 = BonusSpellModifier("Bonus Spell Sorc 1") -bonusSpellSorc1.MapToFeat("Bonus Spell (Sorcerer 1)", feat_cond_arg2 = stat_level_sorcerer) -bonusSpellSorc1.bonusSpellLevelSetArg(1) - -bonusSpellSorc2 = BonusSpellModifier("Bonus Spell Sorc 2") -bonusSpellSorc2.MapToFeat("Bonus Spell (Sorcerer 2)", feat_cond_arg2 = stat_level_sorcerer) -bonusSpellSorc2.bonusSpellLevelSetArg(2) - -bonusSpellSorc3 = BonusSpellModifier("Bonus Spell Sorc 3") -bonusSpellSorc3.MapToFeat("Bonus Spell (Sorcerer 3)", feat_cond_arg2 = stat_level_sorcerer) -bonusSpellSorc3.bonusSpellLevelSetArg(3) - -bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 0") -bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 0)", feat_cond_arg2 = stat_level_wizard) -bonusSpellWiz0.bonusSpellLevelSetArg(0) - -bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 1") -bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 1)", feat_cond_arg2 = stat_level_wizard) -bonusSpellWiz0.bonusSpellLevelSetArg(1) - -bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 2") -bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 0)", feat_cond_arg2 = stat_level_wizard) -bonusSpellWiz0.bonusSpellLevelSetArg(2) - -bonusSpellWiz0 = BonusSpellModifier("Bonus Spell Wiz 2") -bonusSpellWiz0.MapToFeat("Bonus Spell (Wizard 2)", feat_cond_arg2 = stat_level_wizard) -bonusSpellWiz0.bonusSpellLevelSetArg(2) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py index 648377c3a..4818e4c55 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/breath_weapon.py @@ -10,27 +10,24 @@ def breathWeaponOnConditionAdd(attachee, args, evt_obj): breathWeaponId = args.get_param(0) - breathWeaponCharges = args.get_param(1) + baseCharges = args.get_param(1) cooldown = 0 args.set_arg(0, breathWeaponId) - args.set_arg(1, breathWeaponCharges) + args.set_arg(1, baseCharges) args.set_arg(2, cooldown) - args.set_arg(3, 0) + args.set_arg(3, baseCharges) args.set_arg(4, 0) return 0 # Handle Breath Weapon Charges -def addExtraExhalationCharges(attachee): - extraBreathExhalation = attachee.has_feat("Extra Exhalation") - return extraBreathExhalation - -def getMaxCharges(attachee): - maxCharges = 1 + addExtraExhalationCharges(attachee) - return maxCharges +def getMaxCharges (attachee, args): + baseCharges = args.get_arg(3) + extraCharges = attachee.d20_query("PQ_Extra_Breath_Charges") + return baseCharges + extraCharges def resetBreathWeaponUses(attachee, args, evt_obj): if not args.get_arg(1) == -1: - maxCharges = getMaxCharges(attachee) + maxCharges = getMaxCharges(attachee, args) args.set_arg(1, maxCharges) return 0 @@ -53,18 +50,23 @@ def signalBreathWeaponUsed(attachee, args, evt_obj): signalId = evt_obj.data1 breathWeaponId = args.get_arg(0) if signalId == breathWeaponId: - breathWeaponCharges = args.get_arg(1) - if not breathWeaponCharges == 1: + charges = args.get_arg(1) + if not charges == 1: cooldown = getBreathWeaponCoolDown() args.set_arg(2, cooldown) - if not breathWeaponCharges == -1: - breathWeaponCharges -= 1 - args.set_arg(1, breathWeaponCharges) + if not charges == -1: + charges -= 1 + args.set_arg(1, charges) + return 0 + +def queryLimitedBreathWeapon(attachee, args, evt_obj): + if args.get_arg(1) != -1: + evt_obj.return_val = 1 return 0 class BreathWeaponModifier(PythonModifier): # Breath Weapon modifiers have 5 arguments: - # 0: breathWeaponId, 1: charges, 2: Cooldown, 3: empty, 4: empty + # 0: breathWeaponId, 1: charges, 2: Cooldown, 3: baseCharges, 4: empty # Charges set to -1 indicates limitless breath weapon uses # A Breath Weapon usage always triggers a 1d4 long cooldown # before the Breath Weapon becomes availible again @@ -73,8 +75,9 @@ def __init__(self, name): self.AddHook(ET_OnD20PythonSignal, "PS_Breath_Weapon_Used", signalBreathWeaponUsed, ()) self.AddHook(ET_OnBeginRound, EK_NONE, reduceBreathWeaponCooldown, ()) self.AddHook(ET_OnNewDay, EK_NEWDAY_REST, resetBreathWeaponUses, ()) + self.AddHook(ET_OnD20PythonQuery, "PQ_Has_Limited_Breath_Weapon", queryLimitedBreathWeapon, ()) # This hook needs to be added for every BreathWeaponModifier - def breathWeaponSetArgs(self, breathWeaponId, breathWeaponCharges): - self.AddHook(ET_OnConditionAdd, EK_NONE, breathWeaponOnConditionAdd, (breathWeaponId, breathWeaponCharges,)) + def breathWeaponSetArgs(self, breathWeaponId, baseCharges): + self.AddHook(ET_OnConditionAdd, EK_NONE, breathWeaponOnConditionAdd, (breathWeaponId, baseCharges,)) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 918b041e8..02030f6ce 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -2,6 +2,7 @@ from toee import * import tpdp import tpactions +import char_editor import char_class_utils import heritage_feat_utils import breath_weapon @@ -58,6 +59,30 @@ def OnGetSaveThrowWill(attachee, args, evt_obj): ### Draconic Heritage is handle by the Draconic Heritage Feat now +### Bonus Spells per Day +def setBonusSpellPerDaySlot(attachee, args, evt_obj): + highestArcaneClass = attachee.highest_arcane_class + highestArcaneCasterLevel = attachee.highest_arcane_caster_level + spellLevelToApplyBonus = char_editor.get_max_spell_level(attachee, highestArcaneClass, highestArcaneCasterLevel) + args.set_arg(1, highestArcaneClass) + args.set_arg(2, spellLevelToApplyBonus) + return 0 + +def applyExtraSpell(attachee, args, evt_obj): + bonusSpellClass = args.get_arg(1) + spellLevel = args.get_arg(2) + if evt_obj.get_caster_class() == bonusSpellClass and evt_obj.spell_level == spellLevel: + bonusValue = 1 + bonusType = 0 #ID 0 untyped (stacking) + evt_obj.bonus_list.add(bonusValue, bonusType, "Bonus Spell Slot") + return 0 + +bonusSpellSlot = PythonModifier("Dragon Disciple Bonus Spell Slot", 4, False) #featEnum, bonusSpellClass, spellLevel, empty +for bonusSpellCount in range(1, 8): + bonusSpellSlot.MapToFeat("Dragon Disciple Bonus Spell per Day ({})".format(bonusSpellCount)) +bonusSpellSlot.AddHook(ET_OnGetSpellsPerDayMod, EK_NONE, applyExtraSpell, ()) +bonusSpellSlot.AddHook(ET_OnConditionAdd, EK_NONE, setBonusSpellPerDaySlot, ()) + ### AC Bonus def naturalArmorACBonus(attachee, args, evt_obj): classLevel = attachee.stat_level_get(classEnum) @@ -124,17 +149,23 @@ def onGetAbilityScoreCha(attachee, args, evt_obj): # ToDo! ### Breath Weapon + +def getBreathWeaponTag(): + return "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON" + def breathWeaponRadial(attachee, args, evt_obj): + print "breathWeaponRadial Hook" breathWeaponCooldown = args.get_arg(2) if breathWeaponCooldown > -1: breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon Cooldown ({} round(s))".format(breathWeaponCooldown), D20A_PYTHON_ACTION, breathWeaponEnum, 0, "TAG_EXTRA_EXALATION") else: chargesLeft = args.get_arg(1) - maxCharges = breath_weapon.getMaxCharges(attachee) + maxCharges = breath_weapon.getMaxCharges(attachee, args) heritage = attachee.d20_query("PQ_Selected_Draconic_Heritage") breathWeaponShape = heritage_feat_utils.getDraconicHeritageBreathShape(heritage) spellEnum = spell_dragon_diciple_cone_breath if breathWeaponShape == dragon_breath_shape_cone else spell_dragon_diciple_line_breath - breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({}/{})".format(chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, "TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON") + breathWeaponTag = getBreathWeaponTag() + breathWeaponId = tpdp.RadialMenuEntryPythonAction("Breath Weapon ({}/{})".format(chargesLeft, maxCharges), D20A_PYTHON_ACTION, breathWeaponEnum, spellEnum, breathWeaponTag) spellData = tpdp.D20SpellData(spellEnum) spellData.set_spell_class(classEnum) spellData.set_spell_level(9) #Setting this to 9 here so, it passes globes of invulnerability, as they should not protect against Breath Weapons @@ -164,8 +195,9 @@ def performBreathWeapon(attachee, args, evt_obj): return 0 def frameBreathWeapon(attachee, args, evt_obj): + breathWeaponTag = getBreathWeaponTag() genderString = "his" if attachee.stat_level_get(stat_gender) == 1 else "her" - game.create_history_freeform("{} uses {} ~Breath Weapon~[TAG_CLASS_FEATURES_DRAGON_DISCIPLES_BREATH_WEAPON]\n\n".format(attachee.description, genderString)) + game.create_history_freeform("{} uses {} ~Breath Weapon~[{}]\n\n".format(attachee.description, genderString, breathWeaponTag)) #Send Breath Weapon Used Signal breathWeaponId = args.get_arg(0) attachee.d20_send_signal("PS_Breath_Weapon_Used", breathWeaponId) @@ -173,7 +205,7 @@ def frameBreathWeapon(attachee, args, evt_obj): dragonDiscipleBreathWeapon = breath_weapon.BreathWeaponModifier("Dragon Disciple Breath Weapon") dragonDiscipleBreathWeapon.MapToFeat("Dragon Disciple Breath Weapon") -dragonDiscipleBreathWeapon.breathWeaponSetArgs(classEnum, 1) +dragonDiscipleBreathWeapon.breathWeaponSetArgs(classEnum, 1) #1 = baseCharges of the Breath Weapon dragonDiscipleBreathWeapon.AddHook(ET_OnBuildRadialMenuEntry, EK_NONE, breathWeaponRadial, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionCheck, breathWeaponEnum, checkBreathWeapon, ()) dragonDiscipleBreathWeapon.AddHook(ET_OnD20PythonActionPerform, breathWeaponEnum, performBreathWeapon, ()) From 422d1c4a45ef221d4a9df73612c71b91a6d20120 Mon Sep 17 00:00:00 2001 From: Sagenlicht Date: Tue, 18 Jan 2022 12:02:28 +0100 Subject: [PATCH 26/32] Update dragon_disciple.py --- .../tpgamefiles/scr/tpModifiers/dragon_disciple.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py index 02030f6ce..be2f74665 100644 --- a/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py +++ b/tpdatasrc/tpgamefiles/scr/tpModifiers/dragon_disciple.py @@ -2,7 +2,6 @@ from toee import * import tpdp import tpactions -import char_editor import char_class_utils import heritage_feat_utils import breath_weapon @@ -61,11 +60,10 @@ def OnGetSaveThrowWill(attachee, args, evt_obj): ### Bonus Spells per Day def setBonusSpellPerDaySlot(attachee, args, evt_obj): - highestArcaneClass = attachee.highest_arcane_class - highestArcaneCasterLevel = attachee.highest_arcane_caster_level - spellLevelToApplyBonus = char_editor.get_max_spell_level(attachee, highestArcaneClass, highestArcaneCasterLevel) - args.set_arg(1, highestArcaneClass) - args.set_arg(2, spellLevelToApplyBonus) + bonusSpellClass = attachee.highest_arcane_class + spellLevel = attachee.arcane_spell_level_can_cast() + args.set_arg(1, bonusSpellClass) + args.set_arg(2, spellLevel) return 0 def applyExtraSpell(attachee, args, evt_obj): From e590df559662812aed1c38f96ccc8f75987964ec Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Wed, 7 Sep 2022 06:55:33 +0300 Subject: [PATCH 27/32] Fixes animation script particle errors on map load --- TemplePlus/gamesystems/aas_hooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TemplePlus/gamesystems/aas_hooks.cpp b/TemplePlus/gamesystems/aas_hooks.cpp index 6146c9f8b..3a195e274 100644 --- a/TemplePlus/gamesystems/aas_hooks.cpp +++ b/TemplePlus/gamesystems/aas_hooks.cpp @@ -268,7 +268,7 @@ void AasDebugHooks::apply() { memset(&animParams, 0, sizeof(animParams)); animParams.scale = 1.0f; - model->Advance(1.0f, 0.0f, 0.0f, animParams); // this fixes bad radius/height errors by forcing an update inside 0x102682a0 (itself called by GetSubmesh) + model->Advance(0.0f, 0.0f, 0.1f, animParams); // this fixes bad radius/height errors by forcing an update inside 0x102682a0 (itself called by GetSubmesh) auto maxRadiusSquared = -10000.0f; for (uint32_t i = 0; i < submeshes.size(); i++) { From fdb94bf2bb4e0380e03cb96c07d95a611b3fb8c0 Mon Sep 17 00:00:00 2001 From: DudeMcDude Date: Wed, 7 Sep 2022 07:57:05 +0300 Subject: [PATCH 28/32] Minor wiki help errors --- tpdatasrc/tpgamefiles/mes/help/spell_compendium_help.tab | 2 +- tpdatasrc/tpgamefiles/mes/help/stormlord_help.tab | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tpdatasrc/tpgamefiles/mes/help/spell_compendium_help.tab b/tpdatasrc/tpgamefiles/mes/help/spell_compendium_help.tab index e7d7f078c..9fd44ae70 100644 --- a/tpdatasrc/tpgamefiles/mes/help/spell_compendium_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/spell_compendium_help.tab @@ -1,5 +1,5 @@ TAG_WEAPON_SHIELD_PROFICIENCY TAG_COMBAT Weapon, Armor, and Shield Proficiency A character who uses a weapon with which he or she is not proficient takes a -4 penalty on ~attack rolls~[TAG_ATTACK_ROLL]. A character who wears armor and/or uses a shield with which he or she is not proficient takes the armor's (and/or shield's) armor check penalty on attack rolls and on all Strength-based and Dexterity-based ability and skill checks. The penalty for nonproficiency with armor stacks with the penalty for nonproficiency with shields. Weapon, armor, or shield proficiency may be granted by the character's race, class or by the following feats: ~Armor Proficiency (Light)~[TAG_ARMOR_LIGHT] ~Armor Proficiency (Medium)~[TAG_ARMOR_MEDIUM] ~Armor Proficiency (Heavy)~[TAG_ARMOR_HEAVY] ~Exotic Weapon Proficiency~[TAG_EXOTIC_PROF] ~Martial Weapon Proficiency~[TAG_MARTIAL_PROF] ~Simple Weapon Proficiency~[TAG_SIMPLE_PROF] ~Shield Proficiency~[TAG_SHIELD_PROF] Note: I am unsure about the state of simple weapon prof and shield prof and need to verify the help text as they differ from actual D&D. -TAG_SPELLS_ACID_FOG TAG_SPELLS TAG_SORCERER_ TAG_WIZARD_6 TAG_WATER_D Acid Fog ~Conjuration~[TAG_MAGIC_SCHOOLS_CONJURATION](Creation)[Acid] Level: ~Sorcerer~[TAG_SORCERERS]/~Wizard~[TAG_WIZARDS] 6, ~Domain: Water~[TAG_WATER_D] 7 Components: V, S Casting Time: 1 ~standard action~[TAG_STANDARD_ACTION] Range: Medium (100 ft. + 10 ft./level) Area: 20-ft. radius Duration: 1 round/level Saving Throw: None Spell Resistance: No Acid fog creates a billowing mass of misty vapors similar to that produced by a ~solid fog~[TAG_SPELLS_SOLID_FOG] spell. In addition to slowing creatures down and obscuring sight, this spell's vapors are highly acidic. Each round on your turn, starting when you cast the spell, the fog deals 2d6 points of acid damage to each creature and object within it. +TAG_SPELLS_ACID_FOG TAG_SPELLS TAG_SORCERER_6 TAG_WIZARD_6 TAG_WATER_D Acid Fog ~Conjuration~[TAG_MAGIC_SCHOOLS_CONJURATION](Creation)[Acid] Level: ~Sorcerer~[TAG_SORCERERS]/~Wizard~[TAG_WIZARDS] 6, ~Domain: Water~[TAG_WATER_D] 7 Components: V, S Casting Time: 1 ~standard action~[TAG_STANDARD_ACTION] Range: Medium (100 ft. + 10 ft./level) Area: 20-ft. radius Duration: 1 round/level Saving Throw: None Spell Resistance: No Acid fog creates a billowing mass of misty vapors similar to that produced by a ~solid fog~[TAG_SPELLS_SOLID_FOG] spell. In addition to slowing creatures down and obscuring sight, this spell's vapors are highly acidic. Each round on your turn, starting when you cast the spell, the fog deals 2d6 points of acid damage to each creature and object within it. TAG_SPELLS_AID_MASS TAG_SPELLS TAG_CLERIC_3 Aid, Mass ~Enchantment~[TAG_MAGIC_SCHOOLS_ENCHANTMENT](Compulsion)[Mind-Affecting] Level: ~Cleric~[TAG_CLERICS] 3 Components: V, S Casting Time: 1 ~standard action~[TAG_STANDARD_ACTION] Range: Close (25 ft. + 5 ft./2 levels) Target: One creature/level no two more than 30 ft. apart Duration: 1 min./level Saving Throw: None Spell Resistance: No You hold your holy symbol aloft and cast the spell. A silvery radiance dances from your hands, leaping over all the nearby party members and strengthening them. Aid, Mass grants the targets a +1 ~morale~[TAG_MODIFIER_MORALE] bonus on ~attack rolls~[TAG_ATTACK_ROLL] and saves against ~fear~[TAG_FEAR] effects, plus ~temporary hit points~[TAG_TEMPORARY_HIT_POINTS] equal to 1d8 + ~caster level~[TAG_CASTER_LEVEL] (to a maximum of 1d8+15 temporary hit points). TAG_SPELLS_ALIGN_WEAPON_MASS TAG_SPELLS TAG_CLERIC_3 Align Weapon, Mass ~Transmutation~[TAG_MAGIC_SCHOOLS_TRANSMUTATION](see text) Level: ~Cleric~[TAG_CLERICS] 3 Components: V, S Casting Time: 1 ~standard action~[TAG_STANDARD_ACTION] Range: Close (25 ft. + 5 ft./2 levels) Target: One creature/level no two more than 30 ft. apart Duration: 1 min./level Saving Throw: None Spell Resistance: No You hold your holy symbol high and speak old words of power. Your party's weapons take on a pale blue radiance. This spell functions like ~align weapon~[TAG_SPELLS_ALIGN_WEAPON], except that it affects multiple weapons or projectiles at a distance. Note: The projectiles part is not implemented. Warning: as a cleric you can't cast a spell that does have an opposed Alignment Descriptor. I can't change the descriptor on the fly, which means you can actually try to cast the spell but it will fizzle and you loose the spell if you try to cast it with the wrong alignment! TAG_SPELLS_ALLEGRO TAG_SPELLS TAG_BARD_3 Allegro ~Transmutation~[TAG_MAGIC_SCHOOLS_TRANSMUTATION] ~Bard~[TAG_BARDS] 3 Components: V, S Casting Time: 1 ~free action~[TAG_FREE_ACTION] Range: 20 ft. Area: 20 ft. radius burst centered on you Duration: 1 min/level With a quick wiggle of your fingers and a few arcane words, you release the feather in your hand to complete the spell. Suddenly, translucent blue motes burst outward from you and collect on yourself and your nearby allies before fading away. Each creature within the spell's area gains a 30-foot ~enhancement~[TAG_ENHANCEMENT_BONUS] bonus to its land ~speed~[TAG_MOVEMENT_RATE], up to a maximum of double the creature's land speed. Affected creatures retain these effects for the duration of the spell, even if they leave the original area. diff --git a/tpdatasrc/tpgamefiles/mes/help/stormlord_help.tab b/tpdatasrc/tpgamefiles/mes/help/stormlord_help.tab index b2df729c8..3e1dcb577 100644 --- a/tpdatasrc/tpgamefiles/mes/help/stormlord_help.tab +++ b/tpdatasrc/tpgamefiles/mes/help/stormlord_help.tab @@ -6,5 +6,5 @@ TAG_CLASS_FEATURES_STORMLORD_ENHANCED_JAVELINS TAG_STORMLORDS Stormlord Enhanc TAG_CLASS_FEATURES_STORMLORD_RESISTANCE_TO_ELECTRICITY TAG_STORMLORDS Stormlord Resistance to Electricity As a stormlord gains levels in this prestige class, he becomes increasingly ~resistant~[TAG_SPECIAL_ABILITIES_RESISTANCE_TO_ENERGY] to electrical energy, starting at 1st level with resistance to electricity 5. This bonus increases at 4th and 7th level by an additional 5. TAG_CLASS_FEATURES_STORMLORD_SHOCK_WEAPONS TAG_STORMLORDS Stormlord Shock Weapons Any spear or javelin used by a stormlord of 2nd level or higher is treated as a shock weapon (dealing an extra 1d6 points of electricity damage). The weapon loses this ability when leaving the hand of the stormlord. For a stormlord of 8th level or higher, any spear or javelin he uses is instead treated as a shocking burst weapon. TAG_CLASS_FEATURES_STORMLORD_THUNDERING_WEAPONS TAG_STORMLORDS Stormlords Thundering Weapons For a stormlord of 5th level or higher, any spear or javelin he uses is treated as a thundering weapon. The weapon loses this ability when leaving the hand of the stormlord. This effect stacks with that of the stormlord's shock weapon ability. -TAG_CLASS_FEATURES_STORMLORD_IMMUNITY_TO_ELECTRICITY_TAG_STORMLORDS Stormlords Immunity to Electricity At 9th level, a stormlord gains immunity to electricity. +TAG_CLASS_FEATURES_STORMLORD_IMMUNITY_TO_ELECTRICITY TAG_STORMLORDS Stormlords Immunity to Electricity At 9th level, a stormlord gains immunity to electricity. TAG_CLASS_FEATURES_STORMLORD_STORM_OF_ELEMENTAL_FURY TAG_STORMLORDS Stormlords Storm of Elemental Fury At 10th level, a stormlord can summon a storm of great magnitude and power. Once per day, a stormlord can use storm of elemental fury as if he were a 17th-level cleric. Deactivated until Spell Compendium is merged to temple+ From b750dd0fa84c344b10067eb6b28a1d8584aa80dc Mon Sep 17 00:00:00 2001 From: Lei Chabolan Date: Sat, 10 Sep 2022 01:21:50 +0300 Subject: [PATCH 29/32] Scribe scroll - automated infrastructure instead of the old manual protos definition per each spell... --- TemplePlus/inventory.cpp | 1 + TemplePlus/spell.cpp | 24 +- TemplePlus/spell.h | 5 +- TemplePlus/ui/ui_item_creation.cpp | 557 ++++++++++++++++++++--------- TemplePlus/ui/ui_item_creation.h | 26 +- 5 files changed, 415 insertions(+), 198 deletions(-) diff --git a/TemplePlus/inventory.cpp b/TemplePlus/inventory.cpp index ab7fb4263..b1eaf2b91 100644 --- a/TemplePlus/inventory.cpp +++ b/TemplePlus/inventory.cpp @@ -411,6 +411,7 @@ objHndl InventorySystem::FindMatchingStackableItem(objHndl receiver, objHndl ite auto itemSpell = itemObj->GetSpell(obj_f_item_spell_idx, 0); auto invenItemSpell = invenItemObj->GetSpell(obj_f_item_spell_idx, 0); if (itemSpell.spellLevel != invenItemSpell.spellLevel + || itemSpell.spellEnum != invenItemSpell.spellEnum // Temple+: added this for automated scribed scrolls (who all use the same proto) || (itemObj->type == obj_t_scroll && spellSys.IsArcaneSpellClass(itemSpell.classCode) != spellSys.IsArcaneSpellClass(invenItemSpell.classCode) )) diff --git a/TemplePlus/spell.cpp b/TemplePlus/spell.cpp index 783b7d763..bfcd8dcb2 100644 --- a/TemplePlus/spell.cpp +++ b/TemplePlus/spell.cpp @@ -867,7 +867,7 @@ uint32_t LegacySpellSystem::spellRegistryCopy(uint32_t spellEnum, SpellEntry* sp return spellEntryRegistry.copy(spellEnum, spellEntry); } -void LegacySpellSystem::DoForSpellEntries(void(*cb)(SpellEntry & spellEntry)){ +void LegacySpellSystem::DoForSpellEntries( const std::function &cb){ for (auto it : spellEntryRegistry) { cb(*it.data); } @@ -882,10 +882,8 @@ int LegacySpellSystem::CopyLearnableSpells(objHndl& handle, int spellClass, std: for (auto it : spellEntryRegistry) { auto spEntry = it.data; - // new spells from supplemental materials are generally added above enum 802 in Temple+ - if (spEntry->spellEnum > SPELL_ENUM_MAX_VANILLA) { - if (!config.nonCoreMaterials) - continue; + if (IsNonCore(spEntry->spellEnum) && !config.nonCoreMaterials) { + continue; } if (GetSpellLevelBySpellClass(spEntry->spellEnum, spellClass) >= 0) { entries.push_back(*spEntry); @@ -1414,7 +1412,7 @@ void LegacySpellSystem::SpellPacketSetCasterLevel(SpellPacketBody* spellPkt) con } // item spell - else if (spellPkt->invIdx != 255 && (spellPkt->spellEnum < NORMAL_SPELL_RANGE || spellPkt->spellEnum > SPELL_LIKE_ABILITY_RANGE)){ + else if (spellPkt->invIdx != 255 && !IsMonsterSpell(spellPkt->spellEnum)){ spellPkt->casterLevel = 0; logger->info("Critter {} is casting item spell {} at base caster_level {}.", casterName, spellName, 0); } @@ -1436,7 +1434,7 @@ void LegacySpellSystem::SpellPacketSetCasterLevel(SpellPacketBody* spellPkt) con } else{ // domain spell if (spellPkt->spellClass == Domain_Special){ // domain special (usually used for monsters) - if (spellPkt->invIdx != 255 && (spellPkt->spellEnum < NORMAL_SPELL_RANGE || spellPkt->spellEnum > SPELL_LIKE_ABILITY_RANGE)) { + if (spellPkt->invIdx != 255 && !IsMonsterSpell(spellPkt->spellEnum)) { spellPkt->casterLevel = 0; logger->info("Critter {} is casting item spell {} at base caster_level {}.", casterName, spellName, 0); } @@ -2902,6 +2900,12 @@ int LegacySpellSystem::GetSpellSchool(int spellEnum){ return spEntry.spellSchoolEnum; } +bool LegacySpellSystem::IsMonsterSpell(int spellEnum) +{ + return spellEnum >= NORMAL_SPELL_RANGE + && spellEnum <= SPELL_LIKE_ABILITY_RANGE; +} + bool LegacySpellSystem::IsSpellLike(int spellEnum){ return (spellEnum >= NORMAL_SPELL_RANGE && spellEnum <= SPELL_LIKE_ABILITY_RANGE) || spellEnum >= CLASS_SPELL_LIKE_ABILITY_START; @@ -2922,6 +2926,12 @@ bool LegacySpellSystem::IsNewSlotDesignator(int spellEnum) return false; } +// Non-Core spells will be added in the expanded range +bool LegacySpellSystem::IsNonCore(int spellEnum) +{ + return (spellEnum > SPELL_ENUM_MAX_VANILLA); +} + int LegacySpellSystem::GetSpellLevelBySpellClass(int spellEnum, int spellClass, objHndl handle){ if (IsLabel(spellEnum)) diff --git a/TemplePlus/spell.h b/TemplePlus/spell.h index 68e15c787..944c89485 100644 --- a/TemplePlus/spell.h +++ b/TemplePlus/spell.h @@ -170,7 +170,7 @@ struct LegacySpellSystem : temple::AddressTable uint32_t spellRegistryCopy(uint32_t spellEnum, SpellEntry* spellEntry); - void DoForSpellEntries( void(__cdecl*cb)(SpellEntry & spellEntry)); + void DoForSpellEntries( const std::function &cb); int CopyLearnableSpells(objHndl & handle, int spellClass, std::vector & entries); uint32_t ConfigSpellTargetting(PickerArgs* pickerArgs, SpellPacketBody* spellPacketBody); @@ -224,10 +224,11 @@ struct LegacySpellSystem : temple::AddressTable These are not interruptible via Ready vs. Spell, and do not automatically show up in the console as [ACTOR] casts [SPELL]! */ + static bool IsMonsterSpell(int spellEnum); static bool IsSpellLike(int spellEnum); static bool IsLabel(int spellEnum); // check if it is a hardcoded "label" enum (used in the GUI etc) static bool IsNewSlotDesignator(int spellEnum); // check if it is a hardcoded "new slot" designator (used for sorting) enums 1605-1614 - + static bool IsNonCore(int spellEnum); int GetSpellLevelBySpellClass(int spellEnum, int spellClass, objHndl handle = objHndl::null); // returns -1 if not available for spell class bool SpellHasMultiSelection(int spellEnum); diff --git a/TemplePlus/ui/ui_item_creation.cpp b/TemplePlus/ui/ui_item_creation.cpp index 3ad381d11..75cf05d53 100644 --- a/TemplePlus/ui/ui_item_creation.cpp +++ b/TemplePlus/ui/ui_item_creation.cpp @@ -259,6 +259,8 @@ UiItemCreation::UiItemCreation(const UiSystemConf &config) { memset(craftedItemHandles, 0, sizeof(craftedItemHandles)); craftedItemNamePos = 0; craftingWidgetId = -1; + scribeScrollSpells.clear(); + mScribedScrollSpell = 0; LoadMaaSpecs(); @@ -408,7 +410,7 @@ int UiItemCreation::CraftedWandCasterLevel(objHndl item) return casterLvl; } -bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem){ +bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem, int spellEnum){ bool canCraft = 1; bool xpCheck = 0; auto insuffXp = itemCreationAddresses.craftInsufficientXP; @@ -416,10 +418,10 @@ bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem auto insuffSkill = itemCreationAddresses.craftSkillReqNotMet; auto insuffPrereqs = itemCreationAddresses.insuffPrereqs; auto surplusXP = d20LevelSys.GetSurplusXp(crafter); - uint32_t craftingCostCP; + uint32_t craftingCostCP = 0; auto partyMoney = party.GetMoney(); - auto itemObj = objSystem->GetObject(objHndItem); + *insuffXp = 0; *insuffCp = 0; @@ -427,28 +429,31 @@ bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem *insuffPrereqs = 0; // Check GP Section - int itemWorth = itemObj->GetInt32(obj_f_item_worth); - - // Scrolls + int itemWorth = 0; if (itemCreationType == ItemCreationType::ScribeScroll){ - itemWorth = ScribedScrollWorth(objHndItem, ScribedScrollCasterLevel(objHndItem)); - craftingCostCP = itemWorth / 2; - } - // MAA - else if (itemCreationType == ItemCreationType::CraftMagicArmsAndArmor){ - craftingCostCP = MaaCpCost( CRAFT_EFFECT_INVALID ); - } - // Wands & Potions - else if (itemCreationType == ItemCreationType::CraftWand){ - itemWorth = CraftedWandWorth(objHndItem, CraftedWandCasterLevel(objHndItem)); //ItemWorthAdjustedForCasterLevel(objHndItem, CraftedWandCasterLevel(objHndItem)); + itemWorth = ScribedScrollWorth(spellEnum, ScribedScrollCasterLevel(spellEnum)); craftingCostCP = itemWorth / 2; } - // Potions else { - // current method for crafting stuff: - craftingCostCP = itemWorth / 2; - }; + auto itemObj = objSystem->GetObject(objHndItem); + itemWorth = itemObj->GetInt32(obj_f_item_worth); + // MAA + if (itemCreationType == ItemCreationType::CraftMagicArmsAndArmor) { + craftingCostCP = MaaCpCost(CRAFT_EFFECT_INVALID); + } + // Wands & Potions + else if (itemCreationType == ItemCreationType::CraftWand) { + itemWorth = CraftedWandWorth(objHndItem, CraftedWandCasterLevel(objHndItem)); //ItemWorthAdjustedForCasterLevel(objHndItem, CraftedWandCasterLevel(objHndItem)); + craftingCostCP = itemWorth / 2; + } + // Potions + else { + // current method for crafting stuff: + craftingCostCP = itemWorth / 2; + }; + } + if ( ( (uint32_t)partyMoney ) < craftingCostCP){ *insuffCp = 1; canCraft = 0; @@ -458,63 +463,27 @@ bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem // Check XP & prerequisites section // Scrolls, Wands and Potions: - if ( itemCreationType != CraftMagicArmsAndArmor){ - // check requirements from rules\\item_creation.mes - if (!ItemCreationParseMesfileEntry(crafter, objHndItem)){ + if (itemCreationType == ItemCreationType::ScribeScroll) { + if (!ScribeScrollCheck(crafter, spellEnum)) { *insuffPrereqs = 1; canCraft = 0; } - - // scrolls - ensure caster can also actually cast the spell - else if (itemCreationType == ItemCreationType::ScribeScroll ) { - - auto spData = itemObj->GetSpell(obj_f_item_spell_idx, 0); // scrolls should only have a single spell... - auto spEnum = spData.spellEnum; - - std::vector spellClasses, spellLevels; - - auto isLevelOk = false; - - if (!spellSys.SpellKnownQueryGetData(crafter, spEnum, spellClasses, spellLevels)) - *insuffPrereqs = 1; - - for (auto i=0u; i < spellLevels.size(); i++){ - auto maxSpellLvl = -1; - auto spClass = spellClasses[i]; - if (spellSys.isDomainSpell(spClass)) - { - maxSpellLvl = spellSys.GetMaxSpellLevel(crafter, stat_level_cleric); - - } else - { - maxSpellLvl = spellSys.GetMaxSpellLevel(crafter, spellSys.GetCastingClass(spClass)); - } - - - if (maxSpellLvl >= spellLevels[i]) - { - isLevelOk = true; - break; - } - } - - if (!isLevelOk){ - *insuffPrereqs = 1; - canCraft = 0; - } - - } - - // check XP + // check XP int itemXPCost = itemWorth / 2500; xpCheck = surplusXP >= itemXPCost; - } - // MAA - else { + } + else if (itemCreationType == CraftMagicArmsAndArmor) { int magicArmsAndArmorXPCost = MaaXpCost(CRAFT_EFFECT_INVALID); xpCheck = surplusXP >= magicArmsAndArmorXPCost; } + else { + // check requirements from rules\\item_creation.mes + if (!ItemCreationParseMesfileEntry(crafter, objHndItem)){ + *insuffPrereqs = 1; + canCraft = 0; + } + } if (xpCheck){ return canCraft; @@ -526,6 +495,30 @@ bool UiItemCreation::CreateItemResourceCheck(objHndl crafter, objHndl objHndItem } +bool UiItemCreation::ScribeScrollCheck(objHndl crafter, int spEnum) +{ + std::vector spellClasses, spellLevels; + if (!spellSys.SpellKnownQueryGetData(crafter, spEnum, spellClasses, spellLevels)) + return false; + + for (auto i = 0u; i < spellLevels.size(); i++) { + auto maxSpellLvl = -1; + auto spClass = spellClasses[i]; + if (spellSys.isDomainSpell(spClass)) { + maxSpellLvl = spellSys.GetMaxSpellLevel(crafter, stat_level_cleric); + } + else { + maxSpellLvl = spellSys.GetMaxSpellLevel(crafter, spellSys.GetCastingClass(spClass)); + } + + + if (maxSpellLvl >= spellLevels[i]) { + return true; + } + } + return false; +} + const char* UiItemCreation::GetItemCreationMesLine(int lineId){ MesLine line; line.key = lineId; @@ -953,15 +946,35 @@ void UiItemCreation::CraftScrollWandPotionSetItemSpellData(objHndl objHndItem, o return; } + if (itemCreationType == ScribeScroll){ auto scrollSpell = obj->GetSpell(obj_f_item_spell_idx, 0); - ScribedScrollSpellGet(objHndItem, scrollSpell); + ScribedScrollSpellGet(mScribedScrollSpell, scrollSpell); obj->SetSpell(obj_f_item_spell_idx, 0, scrollSpell); + auto invAid = GetScrollInventoryIconId(mScribedScrollSpell); + obj->SetInt32(obj_f_item_inv_aid, invAid); + + + auto baseDescr = description.GetDescriptionString(obj->GetInt32(obj_f_description) ); + auto spellName = spellSys.GetSpellName(mScribedScrollSpell); + int SPELL_ENUM_AID = 1; + auto aidSpellName = spellSys.GetSpellName(SPELL_ENUM_AID); + auto pos = std::strstr(baseDescr, aidSpellName); + auto endPos = pos + std::strlen(aidSpellName); + char newName[1024] = {0,}; + auto idx = 0; + for (idx = 0; baseDescr + idx < pos; ++idx) { + newName[idx] = baseDescr[idx]; + } + for (auto i=0; i < strlen(spellName); ++i) { + newName[idx+i] = spellName[i]; + } - /*int casterLevelFinal = scrollSpell.spellLevel * 2 - 1; - if (casterLevelFinal < 1) - casterLevelFinal = 1;*/ - + for (auto i = 0; endPos + i < baseDescr + strlen(baseDescr); ++i) { + newName[idx + strlen(spellName) + i] = endPos[i]; + } + auto newNameId = description.CustomNameNew(newName); + obj->SetInt32(obj_f_description, newNameId); return; }; @@ -1013,7 +1026,7 @@ void UiItemCreation::CraftScrollWandPotionSetItemSpellData(objHndl objHndItem, o }; -void UiItemCreation::CreateItemDebitXPGP(objHndl crafter, objHndl objHndItem){ +void UiItemCreation::CreateItemDebitXPGP(objHndl crafter, objHndl objHndItem, int spellEnum){ uint32_t crafterXP = objects.getInt32(crafter, obj_f_critter_experience); uint32_t craftingCostCP = 0; uint32_t craftingCostXP = 0; @@ -1028,7 +1041,7 @@ void UiItemCreation::CreateItemDebitXPGP(objHndl crafter, objHndl objHndItem){ if (itemCreationType == ItemCreationType::CraftWand) itemWorth = CraftedWandWorth(objHndItem, CraftedWandCasterLevel(objHndItem)); else if (itemCreationType == ItemCreationType::ScribeScroll){ - itemWorth = ScribedScrollWorth(objHndItem, ScribedScrollCasterLevel(objHndItem)); + itemWorth = ScribedScrollWorth(spellEnum, ScribedScrollCasterLevel(spellEnum)); } else itemWorth = objects.getInt32(objHndItem, obj_f_item_worth); @@ -1121,7 +1134,7 @@ bool UiItemCreation::CraftedWandSpellGet(objHndl item, SpellStoreData & spellDat } -void UiItemCreation::ItemCreationCraftingCostTexts(int widgetId, objHndl objHndItem){ +void UiItemCreation::ItemCreationCraftingCostTexts(int widgetId, objHndl objHndItem, int spellEnum){ // prolog int32_t * insuffXp; int32_t * insuffCp; @@ -1132,19 +1145,23 @@ void UiItemCreation::ItemCreationCraftingCostTexts(int widgetId, objHndl objHndI TigRect rect(212 + 108 * mUseCo8Ui, 157, 159, 10); char * prereqString; - int casterLevelNew = -1; // h4x! - auto obj = objSystem->GetObject(objHndItem); - auto itemWorth = obj->GetInt32(obj_f_item_worth); + int casterLevelNew = -1; + auto itemWorth = 0; - if (itemCreationType == CraftWand){ + if (itemCreationType == ScribeScroll) { + casterLevelNew = ScribedScrollCasterLevel(spellEnum); + itemWorth = ScribedScrollWorth(spellEnum, casterLevelNew); + } + else if (itemCreationType == CraftWand){ casterLevelNew = CraftedWandCasterLevel(objHndItem); itemWorth = CraftedWandWorth(objHndItem, casterLevelNew); } - else if (itemCreationType == ScribeScroll){ - casterLevelNew = ScribedScrollCasterLevel(objHndItem); - itemWorth = ScribedScrollWorth(objHndItem, casterLevelNew); + else { + auto obj = objSystem->GetObject(objHndItem); + itemWorth = obj->GetInt32(obj_f_item_worth); } + insuffXp = itemCreationAddresses.craftInsufficientXP; insuffCp = itemCreationAddresses.craftInsufficientFunds; @@ -1205,11 +1222,58 @@ void UiItemCreation::ItemCreationCraftingCostTexts(int widgetId, objHndl objHndI rect.y = 200; rect.width = 150; rect.height = 105; - prereqString = temple::GetRef(0x101525B0)(itemCreationCrafter, objHndItem); - if (prereqString){ - UiRenderer::DrawTextInWidget(widgetId, prereqString, rect, *itemCreationAddresses.itemCreationTextStyle); + if (GetItemCreationType() == ItemCreationType::ScribeScroll) { + std::string tmp; + + auto prereqStr = PrintPrereqToken("S"); + auto prereqMet = spellSys.IsSpellKnown(itemCreationCrafter, spellEnum); + + auto minCasterLevel = 0; + auto clPrereqMet = false; + if (prereqMet) { + SpellStoreData spellData; + ScribedScrollSpellGet(spellEnum, spellData); + + auto castingClass = !spellSys.isDomainSpell(spellData.classCode) ? spellSys.GetCastingClass(spellData.classCode) : stat_level_cleric; + minCasterLevel = d20ClassSys.GetMinCasterLevelForSpellLevel(castingClass, spellData.spellLevel); + clPrereqMet = critterSys.GetCasterLevelForClass(itemCreationCrafter, castingClass) >= minCasterLevel; + + } + + auto clPrereqStr = PrintPrereqToken( fmt::format("C{}", minCasterLevel).c_str() ); + + + static auto prereqFieldLabel = temple::GetRef(0x10BED98C); + if (*itemCreationAddresses.craftInsufficientXP + || *itemCreationAddresses.craftInsufficientFunds + || *itemCreationAddresses.craftSkillReqNotMet + || *itemCreationAddresses.insuffPrereqs) { + + //_snprintf(tmp, 2000, "@0%s @%d%s", prereqFieldLabel, prereqMet ? 1 : 2, prereqStr); + tmp.append(fmt::format("@0{} @{:d}{}\n", prereqFieldLabel, prereqMet ? 1 : 2, prereqStr)); + if (prereqMet) { + tmp.append(fmt::format("@0{} @{:d}{}\n", prereqFieldLabel, clPrereqMet ? 1 : 2, clPrereqStr)); + } + } + else { + //_snprintf(tmp, 2000, "@0%s @3%s", prereqFieldLabel, prereqStr); + tmp.append(fmt::format("@0{} @3{}\n", prereqFieldLabel, prereqStr)); + if (prereqMet) { + tmp.append(fmt::format("@0{} @3{}\n", prereqFieldLabel, clPrereqStr)); + } + } + if (tmp.size()) { + UiRenderer::DrawTextInWidget(widgetId, tmp, rect, *itemCreationAddresses.itemCreationTextStyle); + } + } + else { + prereqString = temple::GetRef(0x101525B0)(itemCreationCrafter, objHndItem); + if (prereqString) { + UiRenderer::DrawTextInWidget(widgetId, prereqString, rect, *itemCreationAddresses.itemCreationTextStyle); + } } + if (!*insuffPrereq && (itemCreationType == ItemCreationType::CraftWand || itemCreationType == ItemCreationType::ScribeScroll)) { @@ -1252,7 +1316,11 @@ BOOL UiItemCreation::ItemCreationEntryMsg(int widId, TigMsg* msg){ craftingItemIdx = itemIdx; auto itemHandle = craftedItemHandles[itemCreationType][itemIdx]; - if (CreateItemResourceCheck(itemCreationCrafter, itemHandle)) { + mScribedScrollSpell = 0; + if (itemCreationType == ItemCreationType::ScribeScroll) { + mScribedScrollSpell = scribeScrollSpells[itemIdx]; + } + if (CreateItemResourceCheck(itemCreationCrafter, itemHandle, mScribedScrollSpell)) { uiManager->SetButtonState(mItemCreationCreateBtnId, LgcyButtonState::Normal); } else { @@ -1576,15 +1644,11 @@ uint32_t UiItemCreation::CraftedWandWorth(objHndl item, int casterLevelNew){ } -bool UiItemCreation::ScribedScrollSpellGet(objHndl item, SpellStoreData & spellDataOut, int * spellLevelBaseOut){ - if (!item) - return false; - auto obj = objSystem->GetObject(item); - if (!obj->GetSpellArray(obj_f_item_spell_idx).GetSize()) - return false; - +bool UiItemCreation::ScribedScrollSpellGet(int spellEnum, SpellStoreData & spellDataOut, int * spellLevelBaseOut){ + // get spell - auto spellData = obj->GetSpell(obj_f_item_spell_idx, 0); + SpellStoreData spellData; + spellData.spellEnum = spellEnum; // default values (shouldn't really be used...) int spellLevelBasic = 999; @@ -1655,15 +1719,15 @@ bool UiItemCreation::ScribedScrollSpellGet(objHndl item, SpellStoreData & spellD return true; } -int UiItemCreation::ScribedScrollSpellLevel(objHndl item) +int UiItemCreation::ScribedScrollSpellLevel(int spellEnum) { SpellStoreData scrollSpell; - if (!ScribedScrollSpellGet(item, scrollSpell)) + if (!ScribedScrollSpellGet(spellEnum, scrollSpell)) scrollSpell.spellLevel = -1; return scrollSpell.spellLevel; } -int UiItemCreation::ScribedScrollCasterLevel(objHndl item) +int UiItemCreation::ScribedScrollCasterLevel(int spellEnum) { // int result = ScribedScrollSpellLevel(item); /*if (result <= 1) @@ -1672,7 +1736,7 @@ int UiItemCreation::ScribedScrollCasterLevel(objHndl item) */ SpellStoreData scrollSpell; - if (!ScribedScrollSpellGet(item, scrollSpell)) + if (!ScribedScrollSpellGet(spellEnum, scrollSpell)) scrollSpell.spellLevel = -1; if (scrollSpell.spellLevel <= 1) return 1; @@ -1691,21 +1755,18 @@ int UiItemCreation::ScribedScrollCasterLevel(objHndl item) return casterLvl; } -uint32_t UiItemCreation::ScribedScrollWorth(objHndl item, int casterLevelNew) +uint32_t UiItemCreation::ScribedScrollWorth(int spellEnum, int casterLevelNew) { auto baseWorth = 25; - auto obj = objSystem->GetObject(item); - - // which spell? - auto spellData = obj->GetSpell(obj_f_item_spell_idx, 0); - // Calculate cost - SpellEntry spEntry(spellData.spellEnum); + // Calculate cost + SpellEntry spEntry(spellEnum); int materialCost = spEntry.costGp; // retrieve Spell Known data (e.g. for Bards) and caster level (as modified by user selection) - int spellLevelBase = spellData.spellLevel; // default value - ScribedScrollSpellGet(item, spellData, &spellLevelBase); + int spellLevelBase = 0; // default value + SpellStoreData spellData; + ScribedScrollSpellGet(spellEnum, spellData, &spellLevelBase); auto casterLevelBase = max(1,spellLevelBase * 2 - 1); auto casterClass = (Stat)spellSys.GetCastingClass(spellData.classCode); auto minCasterLevel = (int)d20ClassSys.GetMinCasterLevelForSpellLevel(casterClass, spellLevelBase); @@ -1822,7 +1883,7 @@ UiItemCreation::UiItemCreation(){ memset(craftedItemHandles, 0, sizeof(craftedItemHandles)); craftedItemNamePos = 0; craftingWidgetId = -1; - + mScribedScrollSpell = 0; } int UiItemCreation::GetItemCreationType(){ @@ -1998,8 +2059,14 @@ std::string UiItemCreation::PrintPrereqToken(const char * reqTxt) result = fmt::format("{}", d20Stats.GetRaceName(d20RaceSys.GetRaceEnum(reqTxt + 1)) ); break; case 'S': + { + if (itemCreationType == ItemCreationType::ScribeScroll) { + result = spellSys.GetSpellName(mScribedScrollSpell); + return result; + } result = fmt::format("{}", spellSys.GetSpellName(spellSys.GetSpellEnum(reqTxt + 1))); break; + } case 'O': { StringTokenizer tok(reqTxt + 1); @@ -2025,6 +2092,35 @@ std::string UiItemCreation::PrintPrereqToken(const char * reqTxt) return result; } +int UiItemCreation::GetScrollInventoryIconId(int spellEnum) +{ + SpellEntry entry(mScribedScrollSpell); + if (!entry.spellEnum) { + return 0; + } + + switch ((SpellSchools)entry.spellSchoolEnum) { + case SpellSchools::School_Abjuration: + return 384; + case SpellSchools::School_Conjuration: + return 379; + case SpellSchools::School_Divination: + return 386; + case SpellSchools::School_Enchantment: + return 383; + case SpellSchools::School_Evocation: + return 154; + case SpellSchools::School_Illusion: + return 381; + case SpellSchools::School_Necromancy: + return 380; + case SpellSchools::School_Transmutation: + return 382; + default: + return 154; + } +} + ItemEnhancementSpec::ItemEnhancementSpec(const std::string &condName, uint32_t Flags, int EffcBonus, int enhBonus) :condName(condName),flags(Flags),effectiveBonus(EffcBonus){ @@ -2196,18 +2292,27 @@ void UiItemCreation::ItemCreationWndRender(int widId){ // draw info pertaining to selected item if (craftingItemIdx >= 0 && (uint32_t) craftingItemIdx < numItemsCrafting[itemCreationType]) { - auto itemHandle = craftedItemHandles[itemCreationType][craftingItemIdx]; - + objHndl itemHandle = craftedItemHandles[itemCreationType][craftingItemIdx]; + const char* itemName = nullptr; + auto invAid = 0; + if (itemCreationType == ItemCreationType::ScribeScroll) { + itemName = spellSys.GetSpellName(mScribedScrollSpell); + invAid = GetScrollInventoryIconId(mScribedScrollSpell); + } + else { + itemName = ItemCreationGetItemName(itemHandle); + invAid = gameSystems->GetObj().GetObject(itemHandle)->GetInt32(obj_f_item_inv_aid); + } + // draw icon - auto invAid = (UiGenericAsset)gameSystems->GetObj().GetObject(itemHandle)->GetInt32(obj_f_item_inv_aid); int textureId; - uiAssets->GetAsset(UiAssetType::Inventory, invAid, textureId); + uiAssets->GetAsset(UiAssetType::Inventory, (UiGenericAsset)invAid, textureId); rect = TigRect(temple::GetRef(0x102FAEC4)); rect.x += 108 * mUseCo8Ui; UiRenderer::DrawTexture(textureId, rect); - auto itemName = ItemCreationGetItemName(itemHandle); + measText = UiRenderer::MeasureTextSize(itemName, temple::GetRef(0x10BED938)); if (measText.width > 161) { measText.width = 161; @@ -2215,7 +2320,7 @@ void UiItemCreation::ItemCreationWndRender(int widId){ rect = TigRect((161 - measText.width )/2 + 208 + 108 * mUseCo8Ui, 132, 161, 24); UiRenderer::DrawTextInWidget(widId, itemName, rect, temple::GetRef(0x10BEDFE8)); - ItemCreationCraftingCostTexts(widId, itemHandle); + ItemCreationCraftingCostTexts(widId, itemHandle, mScribedScrollSpell); } @@ -2243,21 +2348,30 @@ void UiItemCreation::ItemCreationEntryRender(int widId){ UiRenderer::PushFont(PredefinedFont::ARIAL_10); - auto itemHandle = craftedItemHandles[itemCreationType][itemIdx]; - auto itemName = ItemCreationGetItemName(itemHandle); + // special case for scrolls + const char* text = nullptr; + if (itemCreationType == ItemCreationType::ScribeScroll) { + auto spellEnum = scribeScrollSpells[itemIdx]; + text = spellSys.GetSpellName(spellEnum); + } + else { + auto itemHandle = craftedItemHandles[itemCreationType][itemIdx]; + text = ItemCreationGetItemName(itemHandle); + } + TigRect rect(32, 12 * widIdx + 55, 155 + mUseCo8Ui * 108, 12); auto checkRes = itemCreationResourceCheckResults[itemIdx]; if (itemIdx == craftingItemIdx) { if (checkRes) - UiRenderer::DrawTextInWidget(mItemCreationWndId, itemName, rect, temple::GetRef(0x10BEDFE8)); + UiRenderer::DrawTextInWidget(mItemCreationWndId, text, rect, temple::GetRef(0x10BEDFE8)); else - UiRenderer::DrawTextInWidget(mItemCreationWndId, itemName, rect, temple::GetRef(0x10BECE90)); + UiRenderer::DrawTextInWidget(mItemCreationWndId, text, rect, temple::GetRef(0x10BECE90)); } else { if (checkRes) - UiRenderer::DrawTextInWidget(mItemCreationWndId, itemName, rect, temple::GetRef(0x10BED938)); + UiRenderer::DrawTextInWidget(mItemCreationWndId, text, rect, temple::GetRef(0x10BED938)); else - UiRenderer::DrawTextInWidget(mItemCreationWndId, itemName, rect, temple::GetRef(0x10BED6D8)); + UiRenderer::DrawTextInWidget(mItemCreationWndId, text, rect, temple::GetRef(0x10BED6D8)); } UiRenderer::PopFont(); @@ -2492,23 +2606,38 @@ void UiItemCreation::MaaEnhBonusUpRender(int widId){ void UiItemCreation::ButtonStateInit(int wndId){ uiManager->SetHidden(wndId, false); *mItemCreationWnd = *uiManager->GetWindow(wndId); - itemCreationResourceCheckResults = new bool[numItemsCrafting[itemCreationType]]; - for (int i = 0; i < (int)numItemsCrafting[itemCreationType];i++) - { - auto itemHandle = craftedItemHandles[itemCreationType][i]; - if (itemHandle) { - itemCreationResourceCheckResults[i] = CreateItemResourceCheck(itemCreationCrafter, itemHandle); + + auto itemCount = numItemsCrafting[itemCreationType]; + + itemCreationResourceCheckResults = new bool[itemCount]; + if (itemCreationType == ItemCreationType::ScribeScroll) { + for (int i = 0u; i < itemCount; ++i) { + auto spellEnum = scribeScrollSpells[i]; + itemCreationResourceCheckResults[i] = CreateItemResourceCheck(itemCreationCrafter, objHndl::null, spellEnum); } } - - if (craftingItemIdx >= 0 && craftingItemIdx < (int)numItemsCrafting[itemCreationType]){ - if (CreateItemResourceCheck(itemCreationCrafter, craftedItemHandles[itemCreationType][craftingItemIdx])) + else { + for (int i = 0u; i < itemCount; i++) + { + auto itemHandle = craftedItemHandles[itemCreationType][i]; + if (itemHandle) { + itemCreationResourceCheckResults[i] = CreateItemResourceCheck(itemCreationCrafter, itemHandle); + } + } + } + + mScribedScrollSpell = 0; + if (craftingItemIdx >= 0 && craftingItemIdx < itemCount){ + if (itemCreationType == ItemCreationType::ScribeScroll) { + mScribedScrollSpell = scribeScrollSpells[craftingItemIdx]; + } + if (CreateItemResourceCheck(itemCreationCrafter, craftedItemHandles[itemCreationType][craftingItemIdx], mScribedScrollSpell)) uiManager->SetButtonState(mItemCreationCreateBtnId, LgcyButtonState::Normal); else uiManager->SetButtonState(mItemCreationCreateBtnId, LgcyButtonState::Disabled); } - uiManager->ScrollbarSetYmax(mItemCreationScrollbarId, numItemsCrafting[itemCreationType] - NUM_DISPLAYED_CRAFTABLE_ITEMS_MAX < 0 ? 0 : numItemsCrafting[itemCreationType] - NUM_DISPLAYED_CRAFTABLE_ITEMS_MAX); + uiManager->ScrollbarSetYmax(mItemCreationScrollbarId, itemCount - NUM_DISPLAYED_CRAFTABLE_ITEMS_MAX < 0 ? 0 : itemCount - NUM_DISPLAYED_CRAFTABLE_ITEMS_MAX); uiManager->ScrollbarSetY(mItemCreationScrollbarId, 0); mItemCreationScrollbarY = 0; uiManager->BringToFront(wndId); @@ -2661,9 +2790,9 @@ BOOL UiItemCreation::CreateBtnMsg(int widId, TigMsg* msg) itemHandle = craftedItemHandles[itemCreationType][craftingItemIdx]; } - if ( CreateItemResourceCheck(itemCreationCrafter, itemHandle) ) + if ( CreateItemResourceCheck(itemCreationCrafter, itemHandle, mScribedScrollSpell) ) { - CreateItemDebitXPGP(itemCreationCrafter, itemHandle); + CreateItemDebitXPGP(itemCreationCrafter, itemHandle, mScribedScrollSpell); CreateItemFinalize(itemCreationCrafter, itemHandle); } } @@ -2849,28 +2978,32 @@ void UiItemCreation::CreateItemFinalize(objHndl crafter, objHndl item){ //infrastructure::gKeyboard.Update(); // if ALT is pressed, keep the window open for more crafting! - //auto& itemCreationResourceCheckResults = temple::GetRef(0x10BEE330); if (altPressed) { // refresh the resource checks - auto numItemsCrafting = temple::GetRef(0x11E76C7C); // array containing number of protos - static auto craftingHandles = temple::GetRef(0x11E76C3C); // proto handles - - for (int i = 0; i < numItemsCrafting[itemCreationType]; i++) { - auto protoHandle = craftingHandles[itemCreationType][i]; - if (protoHandle) - itemCreationResourceCheckResults[i] = CreateItemResourceCheck(crafter, protoHandle); + if (itemCreationType == ItemCreationType::ScribeScroll) { + for (int i = 0; i < numItemsCrafting[itemCreationType]; i++) { + auto protoHandle = craftedItemHandles[itemCreationType][i]; + auto spellEnum = scribeScrollSpells[i]; + if (protoHandle) { + itemCreationResourceCheckResults[i] = CreateItemResourceCheck(crafter, protoHandle, spellEnum); + } + } } - - auto icItemIdx = temple::GetRef(0x10BEE398); - if (icItemIdx >= 0 && icItemIdx < numItemsCrafting[itemCreationType]) { + else { + for (int i = 0; i < numItemsCrafting[itemCreationType]; i++) { + auto protoHandle = craftedItemHandles[itemCreationType][i]; + if (protoHandle) + itemCreationResourceCheckResults[i] = CreateItemResourceCheck(crafter, protoHandle); + } + } + + if (craftingItemIdx >= 0 && craftingItemIdx < numItemsCrafting[itemCreationType]) { auto createBtnId = mItemCreationCreateBtnId; //temple::GetRef(0x10BED8B0); - if (CreateItemResourceCheck(crafter, item)) - { + if (CreateItemResourceCheck(crafter, item, mScribedScrollSpell)) { uiManager->SetButtonState(createBtnId, LgcyButtonState::Normal); } - else - { + else { uiManager->SetButtonState(createBtnId, LgcyButtonState::Disabled); } } @@ -3825,43 +3958,109 @@ bool UiItemCreation::InitItemCreationRules(){ for (auto i = (int)ItemCreationType::IC_Alchemy; i < ItemCreationType::Inactive; i++){ if (i == ItemCreationType::CraftMagicArmsAndArmor){ mMaaCraftableItemList.clear(); + continue; } - else + if (i == ItemCreationType::ScribeScroll) { // special handling now + continue; + } + + MesLine line(i); + mesFuncs.GetLine_Safe(icrules, &line); + numItemsCrafting[i] = 0; { - MesLine line(i); - mesFuncs.GetLine_Safe(icrules, &line); - numItemsCrafting[i] = 0; - { - StringTokenizer craftableTok(line.value); - while (craftableTok.next()) { - if (craftableTok.token().type == StringTokenType::Number) - ++numItemsCrafting[i]; - } + StringTokenizer craftableTok(line.value); + while (craftableTok.next()) { + if (craftableTok.token().type == StringTokenType::Number) + ++numItemsCrafting[i]; } - craftedItemHandles[i] = new objHndl[numItemsCrafting[i]+1]; - memset(craftedItemHandles[i], 0, sizeof(objHndl) *(numItemsCrafting[i] + 1)); - if (numItemsCrafting[i] > 0){ - int j = 0; - StringTokenizer craftableTok(line.value); - while (craftableTok.next()) { - if (craftableTok.token().type == StringTokenType::Number){ - auto protoHandle = gameSystems->GetObj().GetProtoHandle(craftableTok.token().numberInt); - if (protoHandle) - craftedItemHandles[i][j++] = protoHandle; - } - + } + craftedItemHandles[i] = new objHndl[numItemsCrafting[i]+1]; + memset(craftedItemHandles[i], 0, sizeof(objHndl) *(numItemsCrafting[i] + 1)); + if (numItemsCrafting[i] > 0){ + int j = 0; + StringTokenizer craftableTok(line.value); + while (craftableTok.next()) { + if (craftableTok.token().type == StringTokenType::Number){ + auto protoHandle = gameSystems->GetObj().GetProtoHandle(craftableTok.token().numberInt); + if (protoHandle) + craftedItemHandles[i][j++] = protoHandle; } - numItemsCrafting[i] = j; // in case there are invalid protos - std::sort(craftedItemHandles[i], &craftedItemHandles[i][numItemsCrafting[i]], - [this](objHndl first, objHndl second){ - auto firstName = ItemCreationGetItemName(first); - auto secondName = ItemCreationGetItemName(second); - auto comRes = _stricmp(firstName, secondName); - return comRes < 0; - }); + } - + numItemsCrafting[i] = j; // in case there are invalid protos + std::sort(craftedItemHandles[i], &craftedItemHandles[i][numItemsCrafting[i]], + [this](objHndl first, objHndl second){ + auto firstName = ItemCreationGetItemName(first); + auto secondName = ItemCreationGetItemName(second); + auto comRes = _stricmp(firstName, secondName); + return comRes < 0; + }); + } + + } + // Automated scribe scroll entries: + { + int i = (int)ItemCreationType::ScribeScroll; + numItemsCrafting[i] = 0; + spellSys.DoForSpellEntries( [&](SpellEntry& entry)->void { + auto spellEnum = entry.spellEnum; + if (spellSys.IsNonCore(spellEnum) && !config.nonCoreMaterials) { + return; + } + if (spellSys.IsMonsterSpell(spellEnum) || spellSys.IsSpellLike(spellEnum)) { + return; + } + // Todo: warlock... + if (spellEnum >= 2300 && spellEnum <= 2400) { + return; + } + if (!entry.spellLvlsNum) { // some non-spells in the 700 range are like that + return; + } + if (spellEnum >= 700 && spellEnum <= 802) { // special case some snowflakes grrr + // 733 Scorching Ray, 740 Ray of Clumsiness, 775 Resonance, 794, 795, 796, 797, 798, 799 - vigor spells, scintillating sphere, etc + switch (spellEnum) { + case 733: + case 740: + case 775: + case 794: + case 795: + case 796: + case 797: + case 798: + case 799: + break; + default: + return; + } + } + scribeScrollSpells.push_back(spellEnum); + numItemsCrafting[i]++; + }); + std::sort(scribeScrollSpells.begin(), scribeScrollSpells.end(), + [this](int first, int second) { + auto firstName = spellSys.GetSpellName(first); + auto secondName = spellSys.GetSpellName(second); + auto comRes = _stricmp(firstName, secondName); + return comRes < 0; + }); + craftedItemHandles[i] = new objHndl[numItemsCrafting[i] + 1]; + memset(craftedItemHandles[i], 0, sizeof(objHndl) * (numItemsCrafting[i] + 1)); + + // Use Aid scroll as prototype + auto SCROLL_OF_AID_PROTO = 9002, SPELL_AID_ENUM = 1; + auto scrollProtoHandle = gameSystems->GetObj().GetProtoHandle(SCROLL_OF_AID_PROTO); + if (!scrollProtoHandle) { + throw TempleException("InitItemCreationRules: Cannot Find aid scroll proto!"); + } + auto &spell = objSystem->GetObject(scrollProtoHandle)->GetSpell(obj_f_item_spell_idx, 0); + if (spell.spellEnum != SPELL_AID_ENUM) { + throw TempleException("InitItemCreationRules: Unexpected scroll proto - expected aid spell scroll!"); + } + // just fill it with the same handle + for (auto j = 0u; j < numItemsCrafting[i]; ++j) { + craftedItemHandles[i][j] = scrollProtoHandle; } } diff --git a/TemplePlus/ui/ui_item_creation.h b/TemplePlus/ui/ui_item_creation.h index 6bca5202d..62d39edc5 100644 --- a/TemplePlus/ui/ui_item_creation.h +++ b/TemplePlus/ui/ui_item_creation.h @@ -93,7 +93,7 @@ class UiItemCreation : public UiSystem { void ItemCreationWndRender(int widId); void ItemCreationEntryRender(int widId); - void ItemCreationCraftingCostTexts(int widId, objHndl objHndItem); + void ItemCreationCraftingCostTexts(int widId, objHndl objHndItem, int spellEnum); BOOL ItemCreationEntryMsg(int widId, TigMsg* msg); void ItemCreationCreateBtnRender(int widId) const; void ItemCreationCancelBtnRender(int widId) const; @@ -156,21 +156,22 @@ class UiItemCreation : public UiSystem { int MaaCpCost(int appliedEffectIdx); int MaaXpCost(int effIdx); // calculates XP cost for crafting effects in MAA - bool CreateItemResourceCheck(objHndl crafter, objHndl item); + bool CreateItemResourceCheck(objHndl crafter, objHndl item, int spellEnum = 0); + bool ScribeScrollCheck(objHndl crafter, int spellEnum); const char* GetItemCreationMesLine(int lineId); char const* ItemCreationGetItemName(objHndl itemHandle) const; objHndl MaaGetItemHandle(); - void CreateItemDebitXPGP(objHndl crafter, objHndl item); + void CreateItemDebitXPGP(objHndl crafter, objHndl item, int spellEnum = 0); bool CraftedWandSpellGet(objHndl item, SpellStoreData& spellData, int * spellLevelBase = nullptr); int CraftedWandSpellLevel(objHndl item); int CraftedWandCasterLevel(objHndl item); uint32_t ItemWorthAdjustedForCasterLevel(objHndl objHndItem, uint32_t casterLevelNew); uint32_t CraftedWandWorth(objHndl item, int casterLevelNew); - bool ScribedScrollSpellGet(objHndl item, SpellStoreData& spellData, int * spellLevelBase = nullptr); - int ScribedScrollSpellLevel(objHndl item); - int ScribedScrollCasterLevel(objHndl item); - uint32_t ScribedScrollWorth(objHndl item, int casterLevelNew); + bool ScribedScrollSpellGet(int spellEnum, SpellStoreData& spellData, int * spellLevelBase = nullptr); + int ScribedScrollSpellLevel(int spellEnum); + int ScribedScrollCasterLevel(int spellEnum); + uint32_t ScribedScrollWorth(int spellEnum, int casterLevelNew); int GetMaxSpellLevelFromCasterLevel(int cl); bool IsWeaponBonus(int effIdx); @@ -211,9 +212,12 @@ class UiItemCreation : public UiSystem { bool ItemCreationRulesParseReqText(objHndl crafter, const char* reqTxt); std::string PrintPrereqToken(const char* str); + int GetScrollInventoryIconId(int spellEnum); + int mItemCreationType = 9; objHndl mItemCreationCrafter; int mCraftingItemIdx = -1; + int mScribedScrollSpell = 0; int craftingWidgetId; // denotes which item creation widget is currently active @@ -270,7 +274,7 @@ class UiItemCreation : public UiSystem { int& maaSelectedEffIdx = mMaaSelectedEffIdx; // currently selected craftable effect - bool* itemCreationResourceCheckResults = nullptr; + bool* itemCreationResourceCheckResults = nullptr; // 0x10BEE330 std::vector mMaaCraftableItemList; // handles to enchantable inventory items std::vector appliedBonusIndices; @@ -280,8 +284,10 @@ class UiItemCreation : public UiSystem { int craftedItemExtraGold; // stores the crafted item existing (pre-crafting) extra gold cost int& craftingItemIdx = mCraftingItemIdx; //temple::GetRef(0x10BEE398); - uint32_t numItemsCrafting[8]; // for each Item Creation type - objHndl* craftedItemHandles[8]; // proto handles for new items, and item to modifyt for MAA + uint32_t numItemsCrafting[8]; // for each Item Creation type (0x11E76C7C) + objHndl* craftedItemHandles[8]; // proto handles for new items, and item to modify for MAA (0x11E76C7C) + std::vector scribeScrollSpells; // New: special casing for automated scroll scribing + int& itemCreationType = mItemCreationType; objHndl& itemCreationCrafter = mItemCreationCrafter;//temple::GetRef(0x10BECEE0); std::string craftedItemName; From 9b6d579830b2cde58267faa010a08f9bcf06be31 Mon Sep 17 00:00:00 2001 From: Lei Chabolan Date: Sat, 10 Sep 2022 17:19:19 +0300 Subject: [PATCH 30/32] Fixes crash issue when AI party member casts Dominate Monster (aka 'Suggestion') on enemy, e.g. summoned Balor from Skull --- TemplePlus/critter.cpp | 4 ++-- TemplePlus/spell_condition.cpp | 36 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/TemplePlus/critter.cpp b/TemplePlus/critter.cpp index ac1a2e956..19322e1cd 100644 --- a/TemplePlus/critter.cpp +++ b/TemplePlus/critter.cpp @@ -248,8 +248,8 @@ uint32_t LegacyCritterSystem::HasMet(objHndl critter, objHndl otherCritter) { return addresses.HasMet(critter, otherCritter); } -uint32_t LegacyCritterSystem::AddFollower(objHndl npc, objHndl pc, int unkFlag, bool asAiFollower) { - return addresses.AddFollower(npc, pc, unkFlag, asAiFollower); +uint32_t LegacyCritterSystem::AddFollower(objHndl npc, objHndl pc, int forcedFollower, bool asAiFollower) { + return addresses.AddFollower(npc, pc, forcedFollower, asAiFollower); } bool LegacyCritterSystem::FollowerAtMax(){ diff --git a/TemplePlus/spell_condition.cpp b/TemplePlus/spell_condition.cpp index 13b09df6f..41b0be5bb 100644 --- a/TemplePlus/spell_condition.cpp +++ b/TemplePlus/spell_condition.cpp @@ -20,6 +20,9 @@ #include "float_line.h" #include "action_sequence.h" #include "ai.h" +#include +#include +#include void PyPerformTouchAttack_PatchedCallToHitProcessing(D20Actn * pd20A, D20Actn d20A, uint32_t savedesi, uint32_t retaddr, PyObject * pyObjCaller, PyObject * pyTupleArgs); @@ -76,8 +79,13 @@ class SpellConditionFixes : public TempleFix { static int SpellResistance_SpellResistanceMod(DispatcherCallbackArgs args); + static int SuggestionOnAdd(DispatcherCallbackArgs args); + void apply() override { + // Fix for when summoned Balor from skull casts suggestion + replaceFunction(0x100D01D0, SuggestionOnAdd); + // Magic Circle Taking Damage - didn't check that attacker is not null replaceFunction(0x100C8D60, MagicCirclePreventDamage); @@ -1468,3 +1476,31 @@ int SpellConditionFixes::SpellResistance_SpellResistanceMod(DispatcherCallbackAr return 0; } +int SpellConditionFixes::SuggestionOnAdd(DispatcherCallbackArgs args) +{ + auto spellId = args.GetCondArg(0); + auto duration = args.GetCondArg(1); + auto arg2 = args.GetCondArg(2); // is 0... + + if (!conds.AddTo(args.objHndCaller, "Charmed", { spellId, duration, arg2 })) { + logger->error("d20_mods_spells.c / _begin_spell_suggestion(): unable to add condition"); + } + floatSys.FloatSpellLine(args.objHndCaller, 20018, FloatLineColor::Red); // Charmed! + auto partyLeader = party.GetConsciousPartyLeader(); + SpellPacketBody pkt(spellId); + if (party.IsInParty(pkt.caster)) { + if (party.ObjIsAIFollower(pkt.caster)) { + critterSys.AddFollower(args.objHndCaller, /*partyLeader*/ pkt.caster, 1, 1); + } + else { + critterSys.AddFollower(args.objHndCaller, pkt.caster, 1, 0); + uiSystems->GetParty().Update(); + } + } + else { + critterSys.AddFollower(args.objHndCaller, pkt.caster, 1, 1); + } + args.SetCondArg(2, 1); + return 0; +} + From 9eefcb04e111abe1f03b7e84d077d3464bafd2fd Mon Sep 17 00:00:00 2001 From: Lei Chabolan Date: Sat, 10 Sep 2022 23:43:04 +0300 Subject: [PATCH 31/32] Fixes #682 Orb of Golden Death infinite Earth Elemental summoning --- TemplePlus/fixes/generalfixes.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/TemplePlus/fixes/generalfixes.cpp b/TemplePlus/fixes/generalfixes.cpp index 0c09cd6df..4b34982f9 100644 --- a/TemplePlus/fixes/generalfixes.cpp +++ b/TemplePlus/fixes/generalfixes.cpp @@ -23,14 +23,19 @@ struct TigTextStyle; -// fixes size_colossal (was misspelled as size_clossal) -class SizeColossalFix : public TempleFix { + +class HexFixes : public TempleFix { public: void apply() override { + + // fixes size_colossal (was misspelled as size_clossal) writeHex(0x10278078, "73 69 7A 65 5F 63 6F 6C 6F 73 73 61 6C"); + + // fixes Orb of Golden Death infinite Earth Elementals + writeHex(0x101034D9, "C1 E0 10"); // was 08, a wrong shift (looks like a typo error, all the others are shifted by 0x10: radMenuEntry.d20ActionData1 = (invIdxFromCondArg2 << 16) | [spellIdx] } -} sizeColossalFix; +} hexFixes; // Makes Kukri Proficiency Martial class KukriFix : public TempleFix { From d1b285882abdb67f3df6c62247e9f65e0b54930d Mon Sep 17 00:00:00 2001 From: Lei Chabolan Date: Sun, 11 Sep 2022 00:29:34 +0300 Subject: [PATCH 32/32] Fixed error in targeting.py will_kos Possibly fixes #661 --- tpdatasrc/tpgamefiles/rules/d20_ai/targeting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tpdatasrc/tpgamefiles/rules/d20_ai/targeting.py b/tpdatasrc/tpgamefiles/rules/d20_ai/targeting.py index 423b8a65b..0383a6a4b 100644 --- a/tpdatasrc/tpgamefiles/rules/d20_ai/targeting.py +++ b/tpdatasrc/tpgamefiles/rules/d20_ai/targeting.py @@ -254,7 +254,7 @@ def will_kos(attacker, target, aiSearchingTgt): return 0 - if target.type == obj_t_npc: + if target.type != obj_t_npc: return 0 taifs = AiFightStatus(target)