Jump to content

Bubb

Modders
  • Posts

    199
  • Joined

Posts posted by Bubb

  1. 9 minutes ago, argent77 said:

    For some reason the result of this action was picked up correctly in the same script round in my tests.

    Never mind, I was only looking in the inventory, not the ammo slots. I misremembered the globals behavior  subsequent triggers in the current script pass can't see the results of actions, (as the actions haven't run yet), but subsequent actions obviously can.

  2. You need to install a ddraw fix. Popular ones include:

    They all have their own quirks based on your machine. Just run through them until you find one that works well.

    Also, the classic games weren't designed to run at huge resolutions. Running them at these resolutions will cause some amount of slowdown, even on modern machines.

  3. A "bypass op101" bit would be simple to implement. In fact, it's probably possible to make a binary patch to implement the bit on Mac and Linux as well. The engine only reads in the first two bits of the resist/dispel field, so for convenience’s sake an upper bit in the saving throw field would probably be best.

  4. Yes, they all use the same internal function that includes the op177-specific check. To my knowledge op337 is the only general-purpose opcode that includes the behavior – that's why I called it out. Probably should've mentioned the others. It's safe to say that when the engine goes to remove one / all instances of a specific opcode id, it will correctly handle op177.

  5. Not sure if I follow the entire conversation, but I can elaborate on how the EFF opcodes work.

    op177 - CGameEffectApplyEffect              - Checks for immunities to EFF         - Drives Effect
    op182 - CGameEffectApplyEffectEquipItem     - Does not check for immunities to EFF - Drives Effect
    op183 - CGameEffectApplyEffectEquipItemType - Does not check for immunities to EFF - Drives Effect
    op248 - CGameEffectMeleeEffect              - Checks for immunities to EFF         - Adds Effect
    op249 - CGameEffectRangeEffect              - Checks for immunities to EFF         - Adds Effect
    op272 - CGameEffectRepeatingApplyEffect     - Checks for immunities to EFF         - Adds Effect
    op283 - CGameEffectCurseApplyEffect         - Does not check for immunities to EFF - Drives Effect

    "Driving" the effect means that the .EFF is never actually added to the creature. The driving opcode directly invokes the driven effect's ApplyEffect() function. This means that .EFF files applied via "driving" opcodes cannot be seen by other opcodes, (as they don't exist on the creature). There is one, and only one exception to this: op337 has special casing that considers op177's driven .EFF file.

    op248, op249, and op272 add the .EFF to the target normal rules should apply.

    EEex can target driven effects of course. However, that's not a valid solution for the fixpack.

  6. 46 minutes ago, Graion Dilach said:

    Oh, does this mean it's also affected by the facing-changes-for-a-frame-preventing-disruption bug?

    Thankfully not. That bug is in the spell actions, this is just co-opting how the engine determines whether the creature has taken damage.

     

    Also, a correction: I missed a part of the Project Image handling. op12 specifically destroys the Project Image clone of the target, regardless of the character's resistances. To me this feels like a bug, since the clone's processing goes out of its way to check if its master "took damage," just for the nuance of that check to be ignored by op12.

  7. As of v2.6.6.0, HitBy() is triggered in three places:

    CGameEffect::OnAdd()
    
        Prerequisites: Spell / Item ability flagged as HOSTILE; effect has a source, and source isn't the creature being affected.
    
        cause = Source AI type
        specific = 0
    
    CGameEffectDamage::OnAddSpecific() - This runs after CGameEffect::OnAdd() if an effect defines it.
    
        Prerequisites: Effect has a source.
    
        cause = Source AI type
        specific = Damage type
    
    CGameSprite::Swing()
    
        Prerequisites: Weapon ability type != RANGED && <real attack> && CGameSprite::Hit() != 0.
    
        op120 is checked after calculating the hit rolls and can still block the attack after HitBy() has been triggered.
    
        cause = Attacker's AI type
        specific = 0

    Key points:

    • Mirror image / stoneskin resistances are processed in CGameEffectDamage::CheckSave(), before the effect is added to the target  meaning that when mirror image / stoneskin blocks an effect, it never goes through the OnAdd() / OnAddSpecific() stages. The same principle applies if the effect is blocked by any mechanism.
    • Event-based triggers, (those with IDs not in the 0x4000 range), match two things: the event cause, (the AI type triggered the event), and a "specific" int value. If a trigger checks a specific int value of 0, the engine treats that '0' to mean "any value."

    So, some takeaways:

    Triggers:

    • HitBy(<AI type>) and HitBy(<AI type>, 0) match any HitBy() event that matches <AI type>; i.e. <damage type> is ignored.

    Effects:

    • When added, any effect that inherited the hostile flag and that has a valid source triggers HitBy(<attacker AI type>, 0), (except when the target was source of the effect).
    • When added, any op12 that has a valid source triggers HitBy(<source AI type>, <damage type>).
    • When an effect is blocked by magic resistance, saving throws, mirror image / stoneskin (in the instance of op12), or by any other mechanism, the effect doesn't trigger HitBy().

    Swings:

    • Real attack swings that pass their hit roll trigger HitBy(<attacker AI type>, 0). For some reason weapon abilities with type = RANGED don't trigger HitBy() as part of the swing.
    • op120 can still block a hit even after HitBy() is triggered by a swing.

     

    Though, all of this is moot, because Project Image doesn't use the HitBy() trigger to determine when to destroy the image. There are two ways the image can get destroyed:

    1. The mechanism that determines whether spell casting is disrupted is triggered: either the creature was hit by an op12 with a damage type it didn't have 100%+ resistance to, (I'm glossing over some of the finer details here), or the creature went under op39.
    2. Any op12 is applied to the caster. I'm inclined to think this is a bug, since it overrides the nuance of the previous check. Why have two checks if the latter makes the former largely redundant? In this case, the only thing the previous check does is provide image destruction in reaction to op39.
  8. All good. I'm going to write down this next point mostly so I'll remember it later:

    On 5/29/2023 at 11:47 PM, marchitek said:

    and probably 4 and 7

    That's correct. Timing modes decay as follows:

    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 0 - Instant/Limited               | => 0x1000 - Absolute                 |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 1 - Instant/Permanent until death |                                      |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 2 - Instant/While equipped        |                                      |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 3 - Delay/Limited                 | => 6 - Limited after duration        | => 0x1000 - Absolute                 |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 4 - Delay/Permanent               | => 7 - Permanent after duration      | => 1 - Instant/Permanent until death |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 5 - Delay/While equipped          | => 8 - Equipped after duration       | => 2 - Instant/While equipped        |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 6 - Limited after duration        | => 0x1000 - Absolute                 |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 7 - Permanent after duration      | => 1 - Instant/Permanent until death |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 8 - Equipped after duration       | => 2 - Instant/While equipped        |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 9 - Instant/Permanent             |                                      |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 10 - Instant/Limited (ticks)      | => 0x1000 - Absolute                 |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+
    | 0x1000 - Absolute                 |                                      |                                      |
    +-----------------------------------+--------------------------------------+--------------------------------------+

    So timing modes 4 and 7 eventually turn into timing mode 1 => op39 sets the states in the cre.

  9. 1 hour ago, kjeron said:

    Unless it changed from v2.3, the engine will still display up to 256 HLAs, you just cannot take more than 24 per level-up (and taking any more than once counts for each time).

    You're right. The actual ability list is unbounded, but the engine keeps track of which abilities have been chosen with an array of 24 bytes that index into the ability list. So you can only distribute up to 24 points per level up, and the useful size of the list is capped at 256 since indices greater than that wrap around.

  10. The engine hardcodes the "24" limit as the size of an array that holds the available HLAs, and as the maximum number of points allowed at level up. Edit: The ability list is capped at 256 useful entries, it's the maximum number of selected abilities that is capped to 24 by a hardcoded array length.

    You can't bypass it without engine hacks, and EEex doesn't help in this instance. (Getting around array-based limits is difficult, as you can't simply expand the array without encroaching on surrounding fields).

    On 5/30/2023 at 12:46 PM, Jarno Mikkola said:

    1) Did you try installing EEex ? Or rather... https://github.com/mrfearless/EEexLoader/releases

    The correct repo is: https://github.com/Bubb13/EEex

  11. 13 hours ago, marchitek said:

    IESDP seems to be right about timing modes. I tested that on BGEE 2.6 with timing mode 1 and it indeed left STATE_HELPLESS even after applying cure sleep opcode. However with timing mode 9 it worked normally, STATE_HELPLESS was removed and character become again selectable.

    EDIT: It seems that op39 with timing mode 9 doesn't set STATE_SLEEPING and STATE_HELPLESS at all.

    Timing mode 1 sets STATE_SLEEPING and STATE_HELPLESS in the cre file and immediately terminates, so the states aren't maintained by the effect, but are truly permanent. You are correct that op2 removes STATE_SLEEPING from the cre, but not STATE_HELPLESS, so there's no way to remove STATE_HELPLESS once it has been applied by op39 with timing mode 1.

    Every other timing mode maintains STATE_SLEEPING and STATE_HELPLESS as part of the effect, and the states can be cleared by removing the effect. I'm not sure why you say timing mode 9 doesn't set the states.

  12. Looks like Near Infinity's "references to this file" system works via simple text search. It doesn't take into account context, just if the text it is searching for is present. For cre files it also searches for the script name, (if it isn't "" or "None"), so that's where the false hits are coming from. I guess it's a feature request for @argent77 to make the search a bit more selective.

  13. 3 hours ago, Guest Warren said:

    do you have a database of annotations and other comments that you wouldn't mind making public?

    I'm afraid I don't have much written down. At this point I just know the engine really well, so I can look things up quickly. If you haven't tried it out yet, I would recommend Ghidra to look at the engine internals. With Ghidra + the PDB you have a pretty good C-like view of the code, though the more complicated functions still need a fair amount of cleanup. And, unfortunately some of the core engine functions are really long, (like the sprite AI processing), which ends up giving Ghidra trouble.

  14. 6 hours ago, Guest Warren said:

    I did notice that yes. Damage (12) triggers AttackedBy() and apparently Sleep (39, no waking up on damage) or Pause target (165) too, given Color Spray (SPWI105) doesn't have the hostile flag. Do we have a detailed list of things that trigger AttackedBy() other than weapon attacks (including misses), and (on a failed save) Damage (12) or the hostile flag?

    The only engine functions that trigger AttackedBy() are:

    CGameEffect::OnAdd() - Hostile flag
    CGameEffectDamage::OnAddSpecific()
    CGameSprite::PickPockets() - PPBEHAVE.2DA->TURN_HOSTILE
    CGameSprite::Swing()

    SPWI105 has the hostile flag set in BG2:EE v2.6.6.0.

×
×
  • Create New...