Temple+ Modding Question

Discussion in 'General Modification' started by _doug_, Feb 21, 2018.

Remove all ads!
  1. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    About movement AOOs, movement is a special case. I think you need a hook for EK_QUE_AOOIncurs. See here:
    https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/d20.cpp#L991

    Target list: that's right, currently not possible. I could add such a method, but I'm not 100% that's the best way to handle this. Will have to get back to you on that (remind me on the weekend).
     
  2. Sagenlicht

    Sagenlicht Member

    Joined:
    Apr 14, 2004
    Messages:
    66
    Likes Received:
    7
    Thanks Sitra, AOOIncurs did it :), about the target list, sure :)

    About Masters Touch, this is more complex, as I have a working code but I think this is not the way to do it as I did not find a way to parse certain values and added them as lists. There must be a better way:
    Code:
    def    OnSpellEffect(spell):
        print "Masters Touch OnSpellEffect"
        spell.duration = 10 * spell.caster_level # 1 Min/cl
        spellTarget = spell.target_list[0]
        spellEnum = (str(spell)[str(spell).index('(')+len('('):str(spell).index(')')])
       
        mastersTouchPossiblyTargetSlots = []
        mastersTouchPossiblyTargetSlots.append(spell.caster.item_worn_at(3)) #mainhand
        mastersTouchPossiblyTargetSlots.append(spell.caster.item_worn_at(4)) #offhand
        mastersTouchPossiblyTargetSlots.append(spell.caster.item_worn_at(11)) #shield
       
        casterHasProficiency = False
        casterBaseProficiencies = []
        weaponProficiencyBard = [wt_sap, wt_short_sword, wt_longsword, wt_rapier, wt_shortbow, wt_composite_shortbow, wt_whip]
        weaponProficiencyDruid = [wt_dagger, wt_sickle, wt_club, wt_shortspear, wt_quarterstaff, wt_spear, wt_dart, wt_sling, wt_scimitar, wt_longspear]
        weaponProficiencyMonk = [wt_dagger, wt_club, wt_quarterstaff, wt_light_crossbow, wt_sling, wt_heavy_crossbow, wt_javelin, wt_handaxe, wt_kama, wt_nunchaku, wt_siangham, wt_shuriken]
        weaponProficiencyRogue = [wt_hand_crossbow, wt_rapier, wt_short_sword, wt_sap, wt_shortbow, wt_composite_shortbow]
        weaponProficiencyWizard = [wt_dagger, wt_club, wt_quarterstaff, wt_light_crossbow, wt_heavy_crossbow]
        weaponProficiencyElf = [wt_longsword, wt_rapier, wt_shortbow, wt_composite_shortbow, wt_longbow, wt_composite_longbow]
        if spell.caster.has_feat(feat_simple_weapon_proficiency_bard):
            casterBaseProficiencies.extend(weaponProficiencyBard)
            hasFullSimpleProficiency = True
        if spell.caster.has_feat(feat_simple_weapon_proficiency_druid):
            casterBaseProficiencies.extend(weaponProficiencyDruid)
        if spell.caster.has_feat(feat_simple_weapon_proficiency_monk):
            casterBaseProficiencies.extend(weaponProficiencyMonk)
        if spell.caster.has_feat(feat_simple_weapon_proficiency_rogue):
            casterBaseProficiencies.extend(weaponProficiencyRogue)
            hasFullSimpleProficiency = True
        if spell.caster.has_feat(feat_simple_weapon_proficiency_wizard):
            casterBaseProficiencies.extend(weaponProficiencyWizard)
        if spell.caster.has_feat(feat_simple_weapon_proficiency_elf):
            casterBaseProficiencies.extend(weaponProficiencyElf)
       
        if spellTarget.obj == OBJ_HANDLE_NULL:
            spell.caster.float_text_line("No item equipped", tf_red)
            game.particles( 'Fizzle', spell.caster )
            spell.target_list.remove_target( spellTarget.obj)
        else:
            try: #Masters Gift only works on equipped items in main or offhand
                itemSlot = mastersTouchPossiblyTargetSlots.index(spellTarget.obj)
            except ValueError:
                spell.caster.float_text_line("Item must be an equipped weapon or shield")
                game.particles('Fizzle', spell.caster)
                spell.target_list.remove_target(spellTarget.obj)
            else:
                if itemSlot == 0:
                    wornItemType = spell.caster.item_worn_at(3).obj_get_int(197) #Get weapon type mainhand
                elif itemSlot == 1:
                    wornItemType = spell.caster.item_worn_at(4).obj_get_int(197) #Get weapon type offhand
                else:
                    wornItemType = 0
                #check for Proficiency
                if wornItemType == 0:
                    if spell.caster.has_feat(278): #check if caster is already proficient with shields; Tower Shields don't seem to differenciate???
                        casterHasProficiency = True
                else:
                    featNeeded = game.get_feat_for_weapon_type(wornItemType)
                    if spell.caster.has_feat(featNeeded):
                        casterHasProficiency = True
                    if spell.caster.has_feat(feat_martial_weapon_proficiency_all):
                        if featNeeded in range(228, 259): #range inlcudes lower and excludes upper value
                            casterHasProficiency = True
                    if featNeeded == 281: #every simple weapon returns 281 when querying game.get_feat_for_weapon_type(wornItemType)
                        if hasFullSimpleProficiency:
                            casterHasProficiency = True
                    if wornItemType in casterBaseProficiencies:
                        casterHasProficiency = True
               
                if not casterHasProficiency:
                    spell.caster.condition_add_with_args('sp-Masters Touch', spell.id, spell.duration, int(spellEnum), wornItemType)
                    spellTarget.partsys_id = game.particles('sp-Detect Magic 2 Med', spell.caster)
                else:
                    spell.caster.float_text_line("Already proficient")
                    game.particles('Fizzle', spell.caster)
                    spell.target_list.remove_target(spellTarget.obj)
    
        spell.spell_end( spell.id)

    Here is my problem: game.get_feat_for_weapon_type(wornItemType) returns only (e.g. feat_martial_weapon_proficiency_battleaxe) but not any class based feats that also grants the proficiency for it. For example Longsword has not only feat_martial_weapon_proficiency_longsword (which is returned) but also martial_all, simple_bard and simple_elf which all are not returned. I assume that all of these were not part of the original game (more of this later). I am aware of the weapon.cpp (https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/weapon.cpp) from which I took the data for my lists in the code, but it would be much better if I actually could access the data.

    It's getting even worse with simple weapons as game.get_feat_for_weapon_type(wornItemType) always returns 281 (simple weapon prof) for them. All those return values are taken from rules\feat_enum.mes (at least I think so) and obviously the game originally granted all classes simple weapon prof, so there was no need to return something else.

    Long story short, the information I would like to have is in weapon.cpp, but how do I access it? As mentioned, at the moment I simply added the lists to the spell, but this feels bad.
     
  3. Sagenlicht

    Sagenlicht Member

    Joined:
    Apr 14, 2004
    Messages:
    66
    Likes Received:
    7
    How do I unset
    Code:
    attachee.ai_flee_add(tpdp.SpellPacket(spell_id).caster)
    I did take a look at https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/python/python_object.cpp and https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/ai.cpp but could not find an answer.
    Code:
    attachee.obj_get_int(obj_f_critter_fleeing_from)
    still returns a 0 after I do set ai_flee_add but the critter runs away.

    When I tried to do simply
    Code:
    attachee.critter_flag_set(OCF_FLEEING)
    the mob did not act as he was in flee mode but did not move, I guess because I would have to set obj_f_critter_fleeing_from but the arg only takes an integer, so I cannot simply pass the tpdp.SpellPacket(spell_id).caster arg.
     
  4. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    I don't understand why you go through all this work to determine whether the caster is already proficient. Just apply the condition - worst case, it's a pointless cast, which will have already been wasted regardless of whether it fizzles or not. Also I don't think it's too much to expect from the player to use the spell judiciously.

    Nevertheless for future reference, if you ever need to check whether a character is proficient with a certain weapon, this is handled here in the C++ side:
    https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/feat.cpp#L1461
    https://github.com/GrognardsFromHell/TemplePlus/blob/master/TemplePlus/feat.cpp#L1559
    it is easy to expose it to python, and is much more preferable than duplicating all the code in python.

    Some further general notes:

    1. Please do not use the explicit values of enumerations - such as 197 instead of obj_f_weapon_type, or the shield prof. feat. If you've seen it used elsewhere this way - that is also bad practice.
    2. spellEnum - again, there is no reason to store it in the condition args. This is a static quantity known in advance.
    3. Also, parsing the spell enum from the spell string representation is very hacky. If you really have need it for whatever reason, it's easy to add a "spell_enum" property to the PySpell to directly obtain it.
     
  5. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    BTW. It's kinda hard to keep track of outstanding issues in this format. Could you make a public google doc where I can reply inline and cross off addressed issues?
     
  6. Sagenlicht

    Sagenlicht Member

    Joined:
    Apr 14, 2004
    Messages:
    66
    Likes Received:
    7
    I don't want to apply the condition I just want to negate the penalty because I want to avoid that the game belives you are proficient with the weapon when you are not. Else you could simply cast the spell before leveling up and get a weapon focus for something you actually would not qualify for. At least this is what I am afraid of.

    I fully agree, I would prefer not to have duplicates, this is really bad.

    1. Will fix it.
    2. + 3. I fully agree that it's hacky. I am even unsure if I need it, I just added it due to the sanctuary thing. If these codelines are not needed in every spell:
    Code:
    def warCrySpellHasSpellActive(attachee, args, evt_obj):
        if evt_obj.data1 == args.get_arg(2):
            evt_obj.return_val = 1
        return 0
    
    warCrySpell.AddHook(ET_OnD20Query, EK_Q_Critter_Has_Spell_Active, warCrySpellHasSpellActive, ())
    I would simply drop it altogether. I just don't know if they are needed. I took dougs spell Moment of Presciency as a template (which I hope does not sound negative at all, I am pretty thankful I had a good starting point!).
    I am aware that you don't like passing the spell enum as it's a static property, if you really dislike it, I can change that but I personally like to gather all information that I know I need in the beginning. But as I said if you really dislike it, I can change it, no problem.

    Sure I can do a google docs document, no problem :)
     
  7. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    Oh, I see now. But in this case there's a simpler solution - make the condition apply a bonus capper to bonus type 37 (which is what the non-proficiency penalty is). You'll want this to occur on the OnGetToHitBonus2 event.
    Although, perhaps that is not accurate either. For example, what if a character is wearing bracers of archery? In that case, shouldn't the bracers apply the bonus that proficient characters get?
    To address that, perhaps it's better to modify the proficiency check with a flag indicating permanent / non-permanent status effects. I've done sthg similar for stat boosters like Fox Cunning vs. Headband of Intellect. Unfortunately it's a bit hairier in this case so I'm not sure that's the best solution.
    Also, I think I've seen that it is ok to get the weapon focus feat - ideally the game should check if you have the proficiency, and enable/disable its effects accordingly.
    All those things considered - I think it's best to simply grant the proficiency. Exploiters gonna exploit, enjoy it while you can :)

    There's a simple solution actually.
    You can retrieve the SpellPacket inside the HasSpellActive() callback, and then get its spell_enum, like so:
    Code:
    spell_id = args.get_arg(0)
    spell_packet = tpdp.SpellPacket(spell_id)
    spell_enum = spell_packet.spell_enum
    
    Cool :)
     
  8. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    P.S. here's the latest decompiled C code. I think this will be a handy reference for things not covered in the Temple+ code base (e.g. check out GlobalWeaponProficiencyToHitPenalty).

    https://fil.email/LwNXiYVK
     
    anatoliy likes this.
  9. Sagenlicht

    Sagenlicht Member

    Joined:
    Apr 14, 2004
    Messages:
    66
    Likes Received:
    7
  10. Sitra Achara

    Sitra Achara Senior Member

    Joined:
    Sep 1, 2003
    Messages:
    3,026
    Likes Received:
    216
    Yes, you have to add sp-Concentrating when the condition is added, using the ET_OnAdd event. It should have the same spell id in its args naturally.
    Your own condition should have a hook for S_Concentration_Broken.
     
  11. Sagenlicht

    Sagenlicht Member

    Joined:
    Apr 14, 2004
    Messages:
    66
    Likes Received:
    7
    Thanks, works like a charm with the exception that the sp-concentration does not expire at the end of the spells duration.
    Code:
    spellPacket.caster.condition_add_with_args('sp-Concentrating', args.get_arg(0),  args.get_arg(1))
    seems to ignore the second argument (which would be the duration). No big thing I guess, I just noticed it, when I mouseover over the caster and can still see the concentration tooltip when the spell expired naturally.
     
Our Host!