Jump to content

Bubb

Modders
  • Posts

    199
  • Joined

Posts posted by Bubb

  1. 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
  2. You should be able to check the local variables directly. Note that mazed creatures can only be targeted via script name or a static-ish OBJECT.IDS reference. Actions that "target" creatures (i.e. make the target's marker change) are specifically disallowed from seeing mazed creatures. ActionOverride() should be able to get a mazed creature to do an instant action. Triggers seem to evaluate normally, e.g. the following works:

    TriggerOverride(PartySlot1,Global("B3MAZE","LOCALS",1))

    The reason why spell states can't be detected during maze / imprisonment is that the engine stops applying opcodes to the creature, except for #212 (0xD4) Protection: Freedom, #213 (0xD5) Spell Effect: Maze, and #233 (0xE9) Stat: Proficiency Modifier.

  3. The engine names the two fields as:

    • .CRE [+0x46] - m_armorClass
    • .CRE [+0x48] - m_armorClassBase

    I see no evidence that m_armorClass is used in any meaningful way. It is accessed by:

    • op74 (State: Blindness)  duration type = 1  modifies m_armorClass.
    • The LoB upgrade / downgrade routines modifies m_armorClass.
    • Action 370 (ChangeStat)  Stat = 2 modifies m_armorClass.

    Notice how the engine never actually uses it.

  4. 43 minutes ago, jmerry said:

    It looks like an underflow error to me; that stat is treated as an unsigned integer and capped above at 20. Subtract from it without guarding properly, and if flips over to the other end.

    Correct. The nightmare upgrade subtracts 5 from the saving throw bytes in the .CRE, which are unsigned. The engine reads in the underflowed value as some large value, applies its [-20-20] guard to the stat, and now the creature's save stat is 20. And due to what I said above, a stat of 20 always fails.

  5. I observed the following saving throw bugs in BG2:EE v2.6.6.0, though I suspect they exist in all variants of the engine.

    * Serious: The engine doesn't display the "Save vs. Type" feedback if a creature would have failed the saving throw before the following modifiers are applied:

    1) .EFF [+0x44] - Effect save bonus          // Every spell with a positive save bonus has a chance of being silently saved against!
    2) Mage specialist bonus vs. incoming spells // Every specialist mage has a chance of silently saving against spells of their school!
    3) op346
    4) op219
    
    5) (v2.5) Legacy of Bhaal                    // Under LoB, every creature with an EA >= GOODCUTOFF has a chance
                                                 //   of silently saving against spells!
                                                 //   EDIT: Fixed in v2.6; only in v2.5.
    Spoiler

    Minsc with Save vs. Spell stat of 10. Blindness spell modified to have save bonus of +10. Notice how both casts are saved against, but only the first displays a message.

    jtNOlHK.gif

    * Multiple saving throw types can be selected simultaneously in the relevant effect bitfield. In this case, the engine uses the target's lowest saving throw stat to determine which of these saving throw types to roll against. Improved invisibility's bonus is applied as a -4 bonus to the target's saving throw stat during rolling, and this throws off the selection process. If improved invisibility is active and multiple saving throw types are selected, in order for the engine to switch to using a better saving throw type, the more effective type needs to be -4 better than the previously selected stat. This bug isn't a big deal since no vanilla spells use the multiple-saving-throw-types mechanic, though this might be relevant to mods.

    * A saving throw stat value of 20 causes the engine to ignore that saving throw type while checking for a save. If no other saving throw type is used by the effect (with the relevant stat < 20), the engine uses a roll of 0 instead of the usual [1-20], almost guaranteeing the save will fail. If the creature has +20 in modifiers to make the nearly impossible save, it fails to display a success message. This "bug" might actually be intentional as an "always fail" value  though, from the limited Googling I have done, it seems 20 is a valid value in the normal AD&D 2E ruleset.

  6. On 2/7/2023 at 9:48 AM, argent77 said:

    There is a glaring engine bug in the SetGlobalTimer() script action in PST:EE.

    The behavior of SetGlobalTimer() appears intentionally altered when engine_mode=3:

    if (actionID == 0x73) {
        /* SetGlobalTimer */
        if (CChitin::ENGINE_MODE == 3) {
            time = g_pBaldurChitin->m_pObjectGame->m_gameTime;
        }
        else {
            time = (g_pBaldurChitin->m_pObjectGame->m_worldTime).m_gameTime;
        }
        time = time + (this->m_curAction).m_specificID * 0xf;
    }

    This effectively makes SetGlobalTimer() behave as RealSetGlobalTimer(). I don't know why though the corresponding triggers aren't altered to check real time.

  7. 52 minutes ago, Guest Furlong said:

    unfortunately ActionOverride only appears to let SetGlobal reach the creature's local scope in EE.

    It works fine in oBG2. For example, putting the following at the top of AR0602.BCS:

    IF
        Global("B3SetLocal","GLOBAL",1)
    THEN
        RESPONSE #100
            SetGlobal("B3SetLocal","GLOBAL",0)
            ActionOverride("Imoen",SetGlobal("B3Test","LOCALS",1))
            Continue()
    END

    And executing the following in the console:

    CLUAConsole:SetGlobal("B3SetLocal","GLOBAL",1)
    -- Wait for Imoen to execute the action
    CLUAConsole:GetGlobal("B3Test","LOCALS") -- While hovering cursor over Imoen

    Are you sure that the block with the ActionOverride() is actually being executed, that Imoen is available to execute the action, and that her action queue isn't being cleared by something else, (player commands, ClearActions())?

  8. The problem is that op120 operates during the swing of the weapon, not during the transferral of effects. If op120 blocks a ranged attack, what it actually does is strip all effects, (including ones generated by the engine), from the projectile.

    So yes, it's a code level issue. Once op120 occurs the projectile is reduced to being visual-only.

  9. oIWD's op284 does the following:

    If the target creature has Race = 108 (GHOUL), 115 (SKELETON), or 167 (UNDEAD), the chance of being destroyed is based on the target creature's LEVEL1:

    • Level < 4: 100%
    • Level = 5: 95%
    • Level = 6: 80%
    • Level = 7: 65%
    • Level = 8 or Level = 9: 50%
    • Level = 10: 35%
    • Level > 10: 20%

    Can you see the bug? Hint: Level = 4 isn't handled, and thus has a 0% chance of being destroyed.

    Race = 164 (TANARI) has a flat 5% chance of being destroyed.

    I see no mechanism that enables the mace's "Double damage against undead and outer planar creatures." claim.

  10. I'm late, but here's all the PROJECTL.IDS values that are hardcoded in PST:EE, so you can avoid using their ids:

    Spoiler

    0
    21
    23
    24
    25
    38
    44
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    96
    109
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    188
    190
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    215
    216
    217
    218
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    258
    259
    260
    262
    263
    264
    265
    267
    268
    269
    277
    278
    279
    280
    286
    287
    288
    299
    300

    Add one to these values to get the projectile ids used in spells.

  11. The answer to "why doesn't an opcode do X" is pretty much that the devs never intended for it to be used that way. And yeah, I can confirm that op45 has no Special field functionality; it will continuously apply STATDESC.2DA["BAM_FILE", 55] while the target is alive.

    Edit: And I don't think you can clear that cell to remove the icon. The engine falls back to [STATES.BAM, sequence=row+65, frame=0] when it encounters a default value.

  12. 1 hour ago, polytope said:

    This is multiplicative with the probability of a storm in the first place right?

    You're right, it's modified by [+0x50] of the area header, "Lightning probability." This field only observes four different tiers:

    • 0 => Disabled.
    • [1, 32] => 2% chance to attempt a lightning strike every 150 + [0, 999] ticks.
    • [33, 65] => 2% chance to attempt a lightning strike every 100 + [0, 699] ticks.
    • [65, +] => 2% chance to attempt a lightning strike every 45 + [0, 199] ticks.

    There's also some randomness in how intense the storm is and how loud the thunder sound is. The engine only attempts a lightning strike when it decides the thunder's volume should be above 85%. So, realistically, it's a lot rarer than I initially thought.  

    • Areas have a 2% chance each tick (default 30tps) to attempt a lightning strike, (approximately 1 strike attempt every ~2.2 seconds).
    • It randomly selects a creature from m_lVertSort, which includes all the non-flying living creatures.
    • If the selected creature is currently on-screen, the chance to strike it is determined by their armor's animation:
      • (Anything) => 30%
      • Chain mail => 65%
      • Plate mail => 100%

    So lightning can pretty much hit any creature as long as it's on-screen.

  13. local nMaxDamageSlot = -1
    local nMaxDamageAbility = -1
    
    local nMaxDamageSeenSoFar = 0
    
    for nCurWeaponSlot = 35, 38 do
    
        for nCurAbilityIndex, ability in ipairs(<every weapon ability>) do
    
            if ability.type == 1 then -- [+0x0], MELEE
    
                local damageDice      = ability.damageDice  -- [+0x16]
                local damageDiceCount = ability.diceSize    -- [+0x18]
                local damageDiceBonus = ability.damageBonus -- [+0x1A]
    
                if (ability.flags & 5) ~= 0 then -- [+0x26], Strength bonus on "Add strength bonus(0)" or "EE: Damage strength bonus(2)"
                    damageDiceBonus = damageDiceBonus + STRMOD.2DA["DAMAGE", this.m_nSTR] + STRMODEX.2DA["DAMAGE", this.m_nSTRExtra]
                end
    
                for abilityEffect in <every ability effect> do
    
                    if abilityEffect.m_effectId == 318 or abilityEffect.m_effectId == 324 then
                        break
                    end
    
                    if abilityEffect.m_effectId == 12 then
                        damageDice      = abilityEffect.m_diceSize     -- [+0x20]
                        damageDiceCount = abilityEffect.m_numDice      -- [+0x1C]
                        damageDiceBonus = abilityEffect.m_effectAmount -- [+0x4]
                        break
                    end
                end
    
                local nTotalDamage = damageDice * damageDiceCount + damageDiceBonus
                    + STYLBONU.2DA["DAMAGE_RIGHT", <resolved style prof level>]
                    + WSPECIAL.2DA["DAMAGE", <resolved weapon prof level>]
                    & 0xFFFF
    
                if nMaxDamageSeenSoFar <= nTotalDamage then
                    nMaxDamageSlot = nCurWeaponSlot
                    nMaxDamageAbility = nCurAbilityIndex
                    nMaxDamageSeenSoFar = nTotalDamage;
                end
            end
        end
    end
    
    local nSlotToSelect
    local nAbilityToSelect
    
    if nMaxDamageSlot == -1 and nMaxDamageAbility == -1 then
        nSlotToSelect = 10 -- SLOT_FIST
        nAbilityToSelect = 0
    else
        nSlotToSelect = nMaxDamageSlot
        nAbilityToSelect = nMaxDamageAbility
    end
    
    this:SelectWeaponAbility(nSlotToSelect, nAbilityToSelect, true, true)
    • The first damage effect on an item ability overrides the ability's base values.
    • Hitting either op318 or op324 stops the search for a damage effect on an item ability.
    • The strength bonus is thrown away if it finds and uses a damage effect.
    • Ties are broken by the item with the highest slot index.
  14. 54 minutes ago, Endarire said:

    My understanding is that this happens because Bubb didn't yet fully update certain functions from 2.5 to 2.6 compatibility.

    That's only applicable to the "attempt to call global" errors, and, like I noted earlier in the thread, other problems can also cause that type of error.

    "EEex not active" is a low-level problem that means EEex_Main.lua didn't finish executing. Either the game wasn't started with InfinityLoader.exe, InfinityLoader.exe failed to inject EEex, or there was a Lua error in EEex_Main.lua. Running InfinityLoader.exe through the command prompt should show any errors that were logged.

  15. It looks like LOCALS survive export. There's a bug though - if you:

    1. Set a LOCALS on a creature
    2. Don't save the game since the LOCALS was set
    3. Export the character

    => The LOCALS is not saved on the exported character.

    Using op187 directly with timing mode 9, special=1 will probably work.

×
×
  • Create New...