Jump to content

Bubb

Modders
  • Posts

    198
  • Joined

Everything posted by Bubb

  1. Some of your spells have invalid opcodes. You can check which spells are crashing using a recent release of Near Infinity:
  2. That's a cool idea, though I don't think the engine can use an item while it's inside a bag. Would be an interesting thing to investigate.
  3. 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.
  4. Remember that SetGlobalRandom()'s result is only visible on the next script pass. As it is now the block needs to run twice.
  5. That assert is tripped when the engine decodes an invalid opcode. You can try using Near Infinity's advanced search to check if all .SPL, .EFF, .ITM, and .CRE files have valid opcodes. In vanilla oBG2 0-60, 62-259, 261-311, and 313-317 don't trigger the assert. ToBEx might add 318 as well.
  6. EEex can already do this: EEex_LuaAction("EEex_Action_NextSpellToPoint()") SpellNoDecRES("SPWI304",PartySlot1) // Fireball Though a dedicated set of actions that mirror the usual ones might be convenient.
  7. You need to install a ddraw fix. Popular ones include: http://bitpatch.com/ie_ddrawfix.html http://bitpatch.com/ddwrapper.html https://github.com/FunkyFr3sh/cnc-ddraw 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.
  8. 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.
  9. 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.
  10. 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.
  11. 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.
  12. 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: 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. 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.
  13. All good. I'm going to write down this next point mostly so I'll remember it later: 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.
  14. 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.
  15. 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). The correct repo is: https://github.com/Bubb13/EEex
  16. 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.
  17. 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.
  18. 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.
  19. Correct. The engine only checks if the hostile flag was present when an opcode first goes into effect. This also means delayed timing modes do not trigger the hostile response when they are added, but when their delay is over.
  20. 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.
  21. The engine automatically executes .lua files with the "M_" prefix, though remember that all engine files need to be 8 characters or less before the extension. So, try a shortened version of "M_DbfTest.lua".
  22. Depends when you want your code to decide which method it is using. You can detect if EEex is installed in a .tp2 with: MOD_IS_INSTALLED ~EEex.tp2~ ~0~ In the game's Lua environment, you can use: if EEex_Active then -- EEex method else -- Non-EEex method end
  23. UI.MENU has Infinity_PressKeyboardButton() to emulate keypresses, but I think it only accepts "escape", "space", and single-character strings. So, no sending a "CTRL" character. The LockScroll() action sets the same internal field as CTRL+P. The difference is that LockScroll() locks the viewport to the creature that executes the action, and CTRL+P uses whatever creature is currently under the cursor. C:Eval() appends its action to the action queue of a creature, normally the creature currently under the cursor, though if a second parameter of [0-5] is given, it instead adds the action to the player in that portrait index. ActionOverride() clears any actions on the target creature that weren't from another ActionOverride(). Emulating CTRL+P with EEex is as simple as: function LockScroll() local id = EEex_GameObject_GetUnderCursorID() if not EEex_GameObject_IsSpriteID(id) then id = -1 end EngineGlobals.g_pBaldurChitin.m_pEngineWorld.m_scrollLockId = id end
×
×
  • Create New...