Jump to content

Bubb

Modders
  • Posts

    199
  • Joined

Posts posted by Bubb

  1. @CamDawg Thanks!

    -------------------------------------------------------------------------------------------------

    Another one:

    The SPEED column in WSPECIAL.2DA is ignored by the engine — High Mastery and Grand Mastery do not provide bonuses to speed factor as they should. This can probably be fixed via modding but posting here to highlight the engine bug.

  2. 2 hours ago, subtledoctor said:

    I mean theoretically that’s fixable, but it would be a fair amount of effort and might be fragile. Might be better off addressing it in tweak mods. 

    I'm curious, what would this look like? I imagine it would entail some serious spell-system abuse.

  3. op236, param2=3 (Simulacrum) is supposed to apply a level-drain effect to the clone equal to [(average of LEVEL, LEVEL2, LEVEL3) * 40%]. The code is bugged for dual class characters -- instead of using the character's average level it uses the level count of the original class.

    This means dual class mages usually end up with Simulacrums that are substantially more powerful than intended.

  4. op182 is supposed to apply the EFF in res2 if the target has the ITM in res1 equipped. This is bugged -- it treats the found slot-index as a boolean, so it always applies the EFF unless the ITM is in SLOT_AMULET.

    And this isn't really a bug, but op182 (if it worked) and triggers HasItemEquiped() + HasItemEquipedReal() don't consider SLOT_MISC19 (the magical weapon slot, used by op111) as an "equipped" slot. It would be nice if there was a way to check it as such.

  5. 13 hours ago, Luke said:

    Having said that, I'm still wondering why op182 is not working...

    After looking at it closer, there's multiple problems.

    • The function it uses to find the item, CGameSprite::FindItemPersonal(), doesn't consider SLOT_MISC19 as part of the "equipped" slots; this is probably intentional. So no detecting any magically-created weapons like the BBoD.
    • And the bug that makes it look like things are working: CGameSprite::FindItemPersonal() returns -1 on failure, but op182 takes any non-zero value to mean success. So op182 will always apply its EFF, making it useless.
  6. 5 hours ago, morpheus562 said:

    Easiest method is probably still using Opcode 177 (Use EFF file).

    This has the problem that Luke was warning about -- since op177 is coming from the equipment list it can't override proficiencies for characters that already have pips in the category. op232 / op272 is the only way around this.

    1 hour ago, jmerry said:

    Dual-class while the Black Blade is equipped, and the character permanently gains grand mastery in long swords.

    Lesser "while equipped" proficiency effects are also subject to getting upgraded by level-ups.

    Well, it doesn't even have to be a permanent effect. Since level-up pulls from the proficiency stats it's impossible to hide these "temporary" buffs from it. Pretty sure nothing short of a patch / EEex can fix this behavior.

  7. Ok, I see, op233 needs to be on the normal effects list to be evaluated after chargen proficiency effects and override them. There seems to be two (normal) ways to get a timed effect applied from an item, op232 and op272.

    Is the op182 even necessary if the item is the source of the repeating EFF? I just tested an (Instant/while equipped) op272 applying the op233 EFF (duration=1), and everything seemed to work.

  8. 2 hours ago, Luke said:

    Can you confirm this now works as expected...?

    The opcode appears to function, it applies res2 if an item with res1 is currently equipped. I'll have to test your setup, though -- why is putting op233 on the ITM itself not a viable option?

  9. And yet another correction, I said these objects could see backlist creatures when they otherwise failed to match a normal creature:

    • 29-37 - [SecondNearestEnemyOf-TenthNearestEnemyOf]
    • 38-46 - [SecondNearest-TenthNearest]
    • 51-59 - [SecondNearestEnemyOfType-TenthNearestEnemyOfType]
    • 61-69 - [SecondNearestMyGroupOfType-TenthNearestMyGroupOfType]
    • 96-104 - [SecondNearestAllyOf-TenthNearestAllyOf]
    • 106-114 - [SecondFarthestEnemyOf-TenthFarthestEnemyOf]

    They can't. Unlike their first "Nearest/Farthest" object, these ignore the backlist. Behavior actually changed here recently; in v2.5.16.6 the above actions would return the script runner for BALDUR.BCS on failure. Due to a technical change in v2.5.17.0, (which seems to have inadvertently affected this), these objects now fail completely instead of falling back to the global script runner.

    So... yeah. I'll probably be back here tomorrow with another correction given how convoluted this system is.

  10. 42 minutes ago, Dan_P said:

    Just to add to this, the NearestEnemyOfType() identifiers works the same for knocked out creatures, so you can still target object selectors. Against downed trolls, for example, you can attack them with Attack(NearestEnemyOfType([0.0.TROLL])), but not with Attack([EVILCUTOFF.0.TROLL]).

    Good point, thanks. You made me realize I missed a code path, turns out basically all of the Nearest / Farthest objects can see backlist creatures — I've edited the list.

  11. On 5/8/2022 at 4:23 AM, Luke said:

    Did you perhaps mean call lightning's secondary strikes

    Yes I did, typo...

    On 5/8/2022 at 4:23 AM, Luke said:
    • Candlekeep Watchers ("seeenemy.bcs", "AttackReevaluate([ENEMY],30)") ignore "sleeping" creatures (due to op12, p2=STUNNING)
    • "bddefai.bcs" ("AttackOneRound(NearestEnemyOf(Myself))") does not ignore "sleeping" creatures (due to op12, p2=STUNNING)
      • That is to say: what do you mean by "any sort of Nearest object"...?

    Ok, so I was partially wrong here. Object selectors never see backlist creatures, (your first example). The following objects:

    • 12 - NearestEnemyOf
    • 50 - NearestEnemyOfType
    • 60 - NearestMyGroupOfType
    • 95 - NearestAllyOf
    • 105 - FarthestEnemyOf

    can see backlist creatures, but they are de-prioritized to match only if the object fails to find another "normal" creature.

    So, for your examples:

    * The Watchers ignore sleeping creatures because their script is using an object selector, (it can't match backlist creatures). Now, if it was using one of the above objects it would initially prefer to attack normal creatures, but would attack a backlist creature if there are no normal creatures around / keep attacking one if it had already started doing so.

    * bddefai.bcs is using NearestEnemyOf() so it can see backlist creatures; like the above example with the conventional object, the prioritization rules determine how this behaves. Now, since AttackOneRound() actually ends periodically the script will reevaluate the target, potentially switching off of attacking a backlist creature to attack a normal creature.

    On 5/8/2022 at 4:23 AM, Luke said:

    Also, what if op39 is applied with `Special == 0`...? In such a case, everything functions as expected...?

    Not sure what you mean, op12 STUNNING applies op39 with Special == 0, it should act the same regardless if the engine applied it / you applied it.

    On 5/8/2022 at 4:23 AM, Luke said:

    Separately, do you perhaps know if there's a secret hidden mode that can bypass / ignore op101 (see `parameter2` of op24)...? If not, we still can't properly distinguish Earthquake from Sleep/Unconsciousness without introducing new splstates...

    No, there's nothing like that for op39.

  12. Stuff I've noticed while testing:

    • Parameter2 (Wake on damage?) == 1 -> Maintains splstate DOESNT_AWAKEN_ON_DAMAGE.
    • Special (Icon) != 0 -> Maintains splstate PRONE.
    • Special == 0 -> Moves creature to the backlist while in effect; this makes the creature invisible to most things that search the area. For example, any sort of Nearest object, object selectors (like [ENEMY]), and some projectiles, (like chain lightning's secondary strikes). This mode is applied when op12 knocks a creature out with STUNNING damage.
    • And a minor nitpick: Always maintains STATE_SLEEPING and STATE_HELPLESS, (this is noted at the bottom for certain timing modes, though it does this for all timing modes and all parameter configurations).
  13. Seems like all the engines handle dialog slightly differently...

    But yes, when an NPC interjects in oBG2 the engine clears their action queue. The EE doesn't do this.

    6 hours ago, jastey said:

    And can someone give me the flag for instant execution of variable setting and how it is used?

    Response flag 0x200 executes queued instant actions, though non-instant actions still block evaluation.

    Setting flag 0x400 on the "This answer sets the variable but leads to a NVROL." response fixes things. It clears the action queue, which almost mimics oBG2's behavior, (clearing on interjection vs on reply).

  14. v2.5+ pathfinding and party members getting stuck:

    Quote

    1) Pre-v2.5.16.6 group-targeting did very little adjustment to the character placements. The only verification it did amounted to checking if the main target, (where you clicked on the map), was passable. This meant that if you clicked near a wall and half of your characters ended up with an invalid target, those characters would just give up at some point.

    v2.5.16.6+ tried to fix this. If characters ended up pathing to an impassible spot on the map it would fall back to routing them to the main target. Well, this means you can get multiple party members all walking to the same exact spot — this happens frequently in tight spaces.

    2) The bumping code was broken. I don't know the specifics here, but it appears a change to some multiplayer sync function causes characters to no longer get out of each other's way in very tight spaces. This also means that if they end up occupying the same part of the map they get stuck.

    #2 can be mitigated with a single-byte patch to the exe.

  15. #1 definitely isn't viable. It would conflict with UI overhauls that drop-in replace UI.MENU, (like LeUI or Dragonspear UI++), and maintaining however many UI.MENU variants for each game / across versions would be hell.

    I agree that REPLACE_TEXTUALLY'ing everything is a bad idea. It's too fragile, and too easy to match wrong parts of the file.

    I vote for using #3 when possible — it has the least potential of conflicting with mods. #4 is a good backup when #3 isn't possible, however, depending on the edits mods may have to adjust around it. The one big gotcha here is that if we start patching UI.MENU, the only way to stay compatible with UI overhauls is to have them install before the FP, whatever caveats that may bring. (EET and its extensive UI.MENU patching may also be an issue here)

    It may also be the case that the FP won't support installing fixes on top of overhauls, I don't know where we would stand on that.

  16. 3 hours ago, jastey said:

    Is this also true after save/reload? Does "Player4" remain empty or do the PlayerX instances get shifted to Player6 empty after reload?

    On save+reload any gaps are removed. So if Keldorn was kicked out of Player4 and I immediately save+reloaded, the list would be:

    1. Charbase
    2. Jaheira
    3. Minsc
    4. Terminsel
    5. Valygar
    6. [Invalid]

    You can kind of think of it as save+reload updating Player[1-6] with Player[1-6]Fill.

  17. RE player objects: Like Cam says, Player[1-6] is usually filled based on join-order. So, if my party was stored like this in the GAM:

    1. Charbase
    2. Jaheira
    3. Minsc
    4. Keldorn
    5. Terminsel
    6. Valygar

    That is what Player[1-6] would return in-game. Now, say I kick Keldorn out of the party. The list becomes:

    1. Charbase
    2. Jaheira
    3. Minsc
    4. [Invalid]
    5. Terminsel
    6. Valygar

    Keldorn is gone, yet the list does not shift to fill the gap, (until you save / reload). Player[1-3] is valid, Player4 is invalid, and Player[5-6] is valid. Dealing with potential gaps is where the Player[1-6]Fill objects come in. They return the "nth" valid character. So Player[1-6]Fill in this scenario returns:

    1. Charbase
    2. Jaheira
    3. Minsc
    4. Terminsel
    5. Valygar
    6. [Invalid]

    If you actually want to target a specific portrait slot, use PartySlot[1-6].

    And note that characters joining the party fill the first open Player[1-6] object. So in the situation where Keldorn was kicked out of Player4, if I had Aerie join the party she would have become Player4. The order of the party member GAM entries directly reflects Player[1-6] — on load the order of the entries determines who is Player[1-6], and whoever is Player[1-6] on save determines the order of the GAM entries.

  18. There's only one difference between op12's "hostile" response and the "hostile" flag: the flag starts the combat song when its effects are applied, (which prevents saving).

    Given the list of spells op206'd by Minor Globe of Invulnerability (in BG2:EE v2.6.6.0):

    Trap-like:
    SPWI313 - Skull Trap
    SPPR304 - Glyph of Warding - (flagged as Hostile)
    
    Damaging:
    SPPR302 - Call Lightning
    SPPR314 - Unholy Blight
    SPPR313 - Holy Smite
    
    Crowd Control:
    SPPR105 - Entangle
    SPWI215 - Web
    SPWI213 - Stinking Cloud
    SPPR211 - Silence, 15' Radius

    Reasons why the above spells aren't flagged as hostile might be:

    * Maybe they didn't want friendly fire from trap-like spells to be enough to enter combat? As they are now, the response from an enemy script reacting to HitBy() and AttackedBy() would start combat. Note: I have no idea why Glyph of Warding is op206'd and has the hostile flag, (which is redundant), or why Call Lightning wouldn't be considered hostile.

    * Unholy Blight / Holy Smite use op177 to determine whether creatures should be hit. If these spells were flagged as hostile casting them on non-affected creatures would aggro / start combat. As they are now, the response from an enemy script reacting to HitBy() and AttackedBy() would start combat.

    * A good number of them are crowd control and shouldn't aggro / start combat if they snag a neutral creature, (Entangle, Web, Stinking Cloud, Silence 15' Radius).

    Honestly, I don't think there's an overarching pattern here, at least mechanically.

  19. 6 hours ago, Luke said:

    I'm sorry, but it's not working for me 😕 (I cast WIZARD_EYE via its scroll "scrla1.itm"...) I tested it on all the 3 games and still nothing...

    You've used 26818 as the String — the engine only allows 0xF00074 / 0xF00080 through, (it doesn't care that 26818 is what 0xF00080 eventually resolves to, it's not 0xF00080). op206's String parameter should look like this in Near Infinity, (right click => Edit as hex number):

    hruAG9M.png

  20. 1 hour ago, Luke said:
    4 hours ago, DavidW said:

    As for why you might cast Melf's Acid Arrow at yourself: because you cast it at an opponent with Spell Turning. (Though to be fair I'm not 100% sure whether spell turning resets the designated caster to the turner.)

    Yes, this is an interesting case. I think that @Bubb and @kjeron know the answer...

    Bounce effects correctly change the reflected effects' m_sourceId to that of the reflecting creature. So Minor Globe will catch a reflected Melf's Acid Arrow but not one cast at self. Another oddity: In BG2:EE Globe of Invulnerability has an op206 against Melf's, but not Minor Globe 🤔

  21. 7 hours ago, Luke said:

    Are you sure about this one...?

    Yes, how are you testing it? Take Wizard Eye (SPWI425.SPL), remove all spell abilities except the first one, and test op206 => String = 45909, 0xF00074, 0xF00080:

    Spoiler

    45909, (No message):

    R3qzqv7.png

    0xF00074, (Evades effects from Wizard Eye):

    Hj7B94u.png

    0xF00080, (Unaffected by effects from Wizard Eye):

    INO7HYu.png

     

  22. 9 hours ago, Luke said:

    So if I enable "Game Option: 3E Sneak Attack", the whole thing does not apply...?

    It still applies. Even though the backstab multiplier isn't used to calculate damage under "Game Option: 3E Sneak Attack", the creature still has its multiplier calculated / assigned.

  23. 6 hours ago, Luke said:

    Why is 101 instead of 100...?

    Because:

    Spoiler
    void CGameSprite::ContingencyCheck() {
        if (g_pBaldurChitin->m_pObjectGame->m_worldTime.m_active != 0 && this->m_nLastContingencyCheck > 0) {
            --this->m_nLastContingencyCheck;
        }
        else {
        	// Sets m_nLastContingencyCheck = 100
            CContingencyList::Process(&this->m_derivedStats.m_cContingencyList,this);
        }
    }

    Logging the current game tick on that CContingencyList::Process() yields:

    [Keldorn] Checking contingencies on tick: 5340435
    [Keldorn] Checking contingencies on tick: 5340536
    [Keldorn] Checking contingencies on tick: 5340637
    [Keldorn] Checking contingencies on tick: 5340738

    It appears to be an off-by-one error.

    6 hours ago, Luke said:

    OK, I see now. Technically speaking, it's row 250 since we need to take into account the header rows, but I got the overall point...

    I would use "lines" to refer to the actual line number in the file, and "rows" to refer to the main engine-read content of the 2DA, (the first row being index 0), but I'm just being pedantic at this point.

    7 hours ago, Luke said:
    On 3/25/2022 at 11:43 AM, Bubb said:

    Effect source = the creature that cast the spell

    Effect host = the creature the effect is attached to

    Could you provide a concrete example where this is relevant?

    It's not relevant for vanilla spells. If you change Globe of Blades, (SPPR725.SPL) like so:

    1. Spell ability 0 => Target = Living actor (1)
    2. And change all of the ability's effects to Target = Preset target (2)
    3. And change SPPR725D.EFF => Resource = SPWI112.SPL, Special = 2

    If you cast the spell on another creature, when the contingency is triggered the caster fires magic missile at the contingency host. Basically it lets you check the conditions on another creature yet take an action from the caster.

    7 hours ago, Luke said:
    On 3/24/2022 at 11:44 PM, Bubb said:

    2) New to v2.6, when:

    1. Starting Spell()
    2. And the resref being cast (first 7 letters) is a parent resource of an active op232 effect on the caster
    3. And the active op232 originated from op234 [param2=0/1] or was defined with [Extra | 1] or [param3 != 0]

           => Remove the active op232, (only remove one instance, even if more would have met the above)

    Could you also provide a concrete example of this?

    As kjeron says 🙂

  24. 10 hours ago, Luke said:

    Could you confirm that

    1. it's not restricted to thieves but to any CLASS/KIT listed in "backstab.2da" / "sneakatt.2da"
    2. the attacker's weapon does not need to be suitable for backstabs / sneak attacks (i.e., not necessarily usable by thieves)

    It's exactly this:

    Quote

    1) Attacker must have a backstab multiplier of 2 or higher
    2) Attacker must be STATE_INVISIBLE
    3) Attacker's weapon must not be RANGED

    class/kit doesn't matter, sneakatt.2da doesn't matter, weapon backstab suitability doesn't matter.

    Edit - And to clarify, RANGED is referring to Item ability -> Type = Ranged (2)

×
×
  • Create New...