Jump to content

Bubb

Modders
  • Posts

    204
  • Joined

Posts posted by Bubb

  1. No, I meant what I posted. EEex versions >= v0.9.0-alpha are for EE v2.6. The transition to a 64bit executable forced EEex to change significantly, which broke backwards compatibility with mods. So, if you want to play High Powers, which was designed for the "old" EEex, you have to downgrade to EE v2.5 and use the EEex version that goes along with it, as I linked.

  2. I'm not sure exactly what caused it, but I think Skie got interrupted executing:

    EscapeAreaMove("AR0700",3325,1240,SSW)

    (this is from a dialog)

    The engine uses opcode 186 to actually move the creature, and that effect got saved on her CRE entry in the SAV. On load the engine processes this opcode before the game has been fully initialized and it crashes. Edit: Or something like that; iirc when Skie attempts to move it crashes because she's not internally assigned to an area.

  3. You've already figured things out, but just to document what happens in the hardcoded effect: Opcode #157's visual effect switches to frame 0 of cycle 1 or 2 (randomly picked) when cycle 0 hits its end. The "immobile and scripts stop running" part should happen immediately, even before the animation finishes.

  4. Script actions always get queued up and executed after a complete pass of the script, (after the triggers have been checked), so it's impossible to change how triggers will evaluate during the current pass. The only exception I know of is in IWD2, where the introduction of SCRINST.IDS and DLGINST.IDS allows certain actions to execute during the middle of a pass. So, unfortunately the inverted-trigger mess masterpiece that you've settled on is the only way.

    Cutscenes are also special in that they have custom script handling. Here's some notable things, not all of them are relevant to your situation, but they might help prevent some confusion in the future:

    1) The first action in a block is treated as a placeholder — the engine uses the object it contains to execute all subsequent actions for that block. CutSceneId() is the convention for this first action, but it can be anything. Note that CutSceneId() is basically a renamed NoAction(), its position in the script is the only thing that matters. The first action is otherwise discarded and not run.

    2) Continue() is meaningless. Cutscenes process every available script block, even if it already entered one, (unlike object/creature/area/game scripts).

    3) There is no event handling, so all triggers with an IDS value less than 0x4000 auto-fail.

    There's probably more, but that's all I can think of at the moment.

  5. It is indeed an annoyingly unintuitive system. The reason summons can't become familiars is because they are controlled; the engine prevents such creatures from becoming familiars. You need to trick the engine into swapping out their status; I've found this works:

    IF
        Global("B3Once","LOCALS",0)
    THEN
        RESPONSE #100
            SetGlobal("B3Once","LOCALS",1)
            ChangeEnemyAlly(Myself,NEUTRAL)  // I shouldn't be controlled by the player.
            
            ApplySpellRES("B3EMPTY",Myself)  // Absolutely empty spell, with a useless effect:
                                             // (Opcode #0 with all fields 0'd, Target=Self, Probability 1=100)
                                             
            MakeGlobal()                     // Non-instant action, allows our spell to apply and gets the engine to realize 
                                             // I am no longer a controlled creature. Familiars must also be global.
                                             
            AddFamiliar()                    // Actually get the engine to promote me into being a familiar.
            ChangeEnemyAlly(Myself,FAMILIAR) // Remember that I am a familiar (see below).
            
            ApplySpellRES("B3EMPTY",Myself)  // AddFamiliar() is buggy and clears all of my ai-type values (except EA)
                                             // until I am updated, force update.
    END

    Note that AddFamiliar() screws with the creature's GENERAL in an unrecoverable way, so that value will be lost upon reload. Other than that, I believe the above works as intended.

  6. Triggers with IDS values < 0x4000 use an alternate event-driven system, which is buggy even for IE standards. A negated event trigger returns true if there are pending events that have yet to be processed, and if any of these pending events fails to satisfy the unnegated trigger.

    You can see this effect by putting !OnCreation() on a creature. The block will fire every time the creature receives an event other than the one that is defined, such as taking damage.

  7. Delay(intArg1) is just the total number script passes performed on the current creature, (including the one in progress), modulus intArg1. Very simple implementation in the engine:

    return m_nExpectedProcessPendingTriggersCalls % intArg1 <= m_nMissedProcessPendingTriggerCalls

    Unless an action specifically allows for itself to be interrupted, a currently executing action will block script passes for its duration, but not stop these blocked passes from being counted in m_nExpectedProcessPendingTriggersCalls.

    TL;DR: Delay() should really be called something like "Interval()" — and this "interval" can be missed if the script is preoccupied with something else when it would otherwise be hit, as is standard behavior with IE scripting.

    ---

    And yes, scripting uses the same random value to seed all RandomNum() triggers across a single tick; if you have multiple RandomNum() calls with the same parameters, they will all generate the same result.

  8. To be clear, Detect() has no problem detecting invisible creatures, as evidenced by it working "correctly" when using object selectors, (eg. [ANYONE]).

    NearestEnemyOf(), however, will not match an invisible creature unless the caller is under op193 (Invisible Detection by Script). I suspect this is true for all/most of the Nearest objects.

  9. There's three major problems I can see with the MDR INI files:

    1) They are using the wrong animation header, [multi_new], when it should be [monster_multi]. The engine ignores everything under an incorrect header, so nothing you input here will be considered without first correcting the section header.

    2) split_bams is hooked up to false_color internally, most likely a copy+paste error in the source. Meaning that if you correct the section header, it is impossible to designate split_bams=1, (the engine will execute this as false_color=1), and thus dragon animations (as they stand) will be in the wrong animation format and won't render.

    3) palette[1-5] requires an internal field, m_bNewPalette, to be flipped in order to function. It is impossible to set this field via the INI, and so palette[1-5] are effectively defunct.

    Note that the engine hardcodes all of these fields correctly for the different dragon animations if [monster_multi] isn't defined, so that's why everything works if you don't mess with the INI files.

  10. 1) Weapon abilities, (right-click quick weapon in inventory -> Abilities), or right-click weapon in actionbar, (F2-F5 depending on class). Only functions in a weapon's item ability header.

    2) Spell, (F7 in actionbar). Only functions in a spell's ability header.

    3) Item, (F8 in actionbar). Only functions in an item's ability header.

    4) Ability, (F12 in actionbar). Only functions in a spell's ability header.

    Other values do not function, and the associated ability will not appear anywhere. The only flexibility of this field is being able to change where spells appear, (Spell vs Ability) and, on weapons, differentiating between weapon abilities and item abilities. Otherwise, you are locked to the relevant type.

    So, pretty straightforward.

  11. I've looked into this, and, yep, it's bugged. Here's pseduocode of what the engine does if there's an enemy in sight of the character hiding, (using the nearest enemy):

    Spoiler
    
    rand = [rand 5-100, increments of 5]
    
    if (![Enemy DEAF])
    {
        enemyDiff = max( ([Enemy CREHIDEM.2DA->QUIETMOD] + [Enemy WISBONUS] + [Enemy CLASSLEVELSUM]) * 5, 0)
    
        // BUG: THIS CONDITION IS FLIPPED!
        if (enemyDiff + rand < MOVESILENTLY)
        {
            // Fail
        }
        else
        {
            // Succeed (continue checking)
        }
    }
    
    if (![Enemy BLIND])
    {
        enemyDiff = max( ([Enemy CREHIDEM.2DA->HIDEMOD] + [Enemy WISBONUS] + [Enemy CLASSLEVELSUM]) * 5, 0)
    
        if (enemyDiff + rand < HIDEINSHADOWS)
        {
            // Succeed (continue checking)
        }
        else
        {
            // Fail
        }
    }
    
    if ([Enemy DEAF] && [Enemy BLIND])
    {
        // lightMod is complicated; it includes both move silently, hide in shadows, 
        //     and a light mod (which itself partially includes CRELIGHT.2DA)
        // Note: This is also the check that is done if no enemies are around
        if (rand < lightMod) 
        {
            // Succeed    
        }
        {
            // Fail
        }
    }

     

    So, first off, it's extremely hard to hide while an enemy is around. You have to pass two insane checks, the only saving grace is that they can each be disabled if the enemy has the counter-condition, (being deaf or blind). From the pseduocode, the MOVESILENTLY check was inverted, so a lower(!) MOVESILENTLY skill value was better here. I've attached an exe-patch that fixes that condition, but it is still insanely hard to hide while enemies are nearby; by design, I guess?

    bubb_fix_hide.zip

  12. The engine is hardcoded to deactivate modals whenever the creature starts taking another action. There is a small list of (hardcoded) actions the engine will allow to be started without disrupting modals:

    0 NoAction
    18 Hide
    22 MoveToObject
    23 MoveToPoint
    63 Wait
    83 SmallWait
    84 Face
    89 Follow
    90 MoveToPointNoRecticle
    93 LeaveAreaName
    99 WAITINLINE (unlisted, defunct)
    215 FollowObjectFormation

     

  13. 1 hour ago, pjakub88 said:

    Unless donating is possible in Enhanced Edition...

    It appears IWD:EE lets you donate at temples:

    Spoiler

    donate.thumb.png.c3a0ac3b3d710f179869538798b75b31.png

    However, IWD:EE only lets you boost your reputation to 8 using donations, so it's pretty much a moot mechanic unless you need to salvage your reputation in case you killed the wrong person.

  14. CHASE.IDS is a bit complicated — there are many edge cases and hardcoded exceptions. When a creature is in an area that is being unloaded, (for example, by all party members leaving a non-master area, or transitioning to a new master area), the engine allows creatures to follow the party to the new area. In general, if a creature is executing an action from CHASE.IDS while the area it is in is being unloaded, it is allowed to follow its target through the transition.

  15. EEex is an exe hack and thus it doesn't work anywhere except the latest EE versions on Windows. Note some of the functionality could be added to GemRB easily, (the Opcode parameter changes especially), but the whole Lua-scripting and memory editing portions are totally platform / engine specific. It really wouldn't work.

  16. It's easier just to show the flaw in the trigger block evaluation code.

    if (hasNextTriggerObjectOverride)
    {
        if (nextTriggerObjectOverride != NULL)
        {
            // do eval stuff
            hasNextTriggerObjectOverride = false
        }
    }
    else
    {
        // do eval stuff
    }

    NextTriggerObject sets both hasNextTriggerObjectOverride=true, and if it can't find a valid object, nextTriggerObjectOverride=NULL. Once this happens, the engine gets stuck in a bad state. Since nextTriggerObjectOverride is NULL it never clears hasNextTriggerObjectOverride, and it forever skips evaluating further triggers, (in the current trigger block).

    Note though, while it's not evaluating the triggers, it is still running through all of them and doing janitorial actions. Meaning - every non-OR'd trigger still sets the whole block's result to false, the active OR count still ticks down - another NextTriggerOverride has the potential of fixing the deadlock, etc.

    The only way a trigger block can still succeed after NextTriggerOverride breaks is:

    1. The NextTriggerOverride was in an OR block
    2. There are no subsequent triggers after the OR block
    3. A trigger in the OR block, that is positioned above the NextTriggerOverride, already evaluated to true

    or, further in the same OR block, another NextTriggerOverride evaluates to a valid creature, and fixes the bad state. Once that happens, the trigger block will start evaluating normally again from that point onward. So, I suppose, one way to prevent NextTriggerOverride from breaking everything would be something like this:

    IF
        OR(3)
            TriggerOverride(Player6,Global("B3WEIRD","LOCALS",1))
            TriggerOverride(Myself,False()) // This will fix the deadlock
            // Whatever trigger here
    THEN

     

  17. 12 hours ago, lynx said:

    I think it disables all, but we don't understand it well in GemRB. My suspicion is that the stat works like a mask, so potentially you could disable specific hardcoded overlays.

    Most of the spell protection opcodes add an entry to an immunity list in the stats of a creature. Every time the effects list is evaluated on a creature the engine checks if one of these lists is populated, and if it is it adds a corresponding graphic to the creature. Pseudocode:

    Spoiler
    
    if (!m_bPreventSpellProtectionEffects && m_generalState & 0x800 == 0 && CGameObject::InControl(this))
    {
        if (m_cBounceProjectile.m_nCount == 0 && m_cBounceEffect.m_nCount == 0)
        {
            for (CSpellLevelDecrementing level : m_cBounceProjectileLevelDec)
            {
                if (level.m_bImmune != 0) goto SPTURNI2
            }
    
            if (   (m_cBounceSchool.m_nCount != 0) || (m_cBounceSecondaryType.m_nCount != 0) || (m_cBounceSpell.m_nCount != 0)
                || (m_cBounceSchoolLevelDec.m_nCount != 0) || (m_cBounceSecondaryTypeLevelDec.m_nCount != 0) 
                || (m_cImmunitiesSecondaryTypeLevelDec.m_nCount != 0) || (0 < m_nChaosShield) )
            {
                goto SPTURNI2
            }
        }
        else
        {
            SPTURNI2:
            // Apply spturni2
        }
    
    
        for (CSpellLevelDecrementing level : m_cImmunitiesProjectileLevelDec)
        {
            if (level.m_bImmune != 0) goto SPMAGGLO
        }
    
        if (   (m_cImmunitiesSchool.m_nCount != 0) || (m_cImmunitiesSecondaryType.m_nCount != 0)
            || (m_cImmunitiesSchoolLevelDec.m_nCount != 0) )
        {
            SPMAGGLO:
            // Apply spmagglo
        }
    
    
        for (CSpellLevelDecrementing level : m_cSpellTrapLevelDec)
        {
            if (level.m_bImmune != 0)
            {
                // Apply spmagglo
                break
            }
        }
    }

     

    Opcode #291 sets m_bPreventSpellProtectionEffects to Param2, (so it's an all-or-nothing deal). The list of Opcode graphics it blocks is as follows:

    // Blocks the below graphics
    m_bPreventSpellProtectionEffects -> Opcode #291 Param2
    
    // Applies SPTURNI2
    m_cBounceProjectile                -> Opcode #197
    m_cBounceEffect                    -> Opcode #198
    m_cBounceProjectileLevelDec        -> Opcode #200
    m_cBounceSchool                    -> Opcode #202
    m_cBounceSecondaryType             -> Opcode #203
    m_cBounceSpell                     -> Opcode #207
    m_cImmunitiesSecondaryTypeLevelDec -> Opcode #226
    m_cBounceSchoolLevelDec            -> Opcode #227
    m_cBounceSecondaryTypeLevelDec     -> Opcode #228
    m_nChaosShield                     -> Opcode #299 Param1
    
    // Applies SPMAGGLO
    m_cImmunitiesProjectileLevelDec -> Opcode #201
    m_cImmunitiesSchool             -> Opcode #204
    m_cImmunitiesSecondaryType      -> Opcode #205
    m_cImmunitiesSchoolLevelDec     -> Opcode #223
    m_cSpellTrapLevelDec            -> Opcode #259

    The IESDP description lists Opcode #199 as one of the opcodes with blocked graphics - but this is false, (that opcode has no associated graphics).

    So this is all to say, Opcode #291 doesn't discriminate which visuals it blocks; if you apply it to a creature with Param2 = 1, all the above opcodes will have their graphics suppressed for as long as the Opcode #291 stays in effect.

    If you put a permanent Opcode #291 on every creature, and manually add-back the graphics to all the affected spells, in theory you could customize how you want new spells to display without borking the vanilla ones.

  18. 1 hour ago, subtledoctor said:

    What I can do here, is decide which spells are affected by the Detect Illusions ability.  It only affects spells with a certain secondary type (ILLUSIONARYPROTECTIONS), so we can control which spells do and don't have that sectype.

    I think there was some confusion with what I said in the SR thread Detect Illusions uses Opcode #220, which is the full spell-school dispel. The secondary type isn't used, if a spell has Illusionist as its primary type it'll get dispelled. (The secondary-type dispel opcode is #221, I probably should have clarified which one I was talking about)

  19. 31 minutes ago, subtledoctor said:

    Yeah, the thief skill is wildly, ridiculously OP. It’s also horribly hard-coded and there’s mot much to be done about it. It occurs to me that we could make a new sectype, ILLUSIONARYPROTECTIONS2, and move the existing illusionary protections like MI to the new sectype. That might limit the thief skill to dispelling invisibility. If it works via sectype. (Maybe @Bubb knows?)

    The spell-dispelling part is just a mass-applied Opcode #220 with these params:

    • Param1 -> 9
    • Param2 -> 5
    • probabilityLower -> 0
    • probabilityUpper -> nDetectIllusion skill value
  20. TargetUnreachable() only returns true if an attacker, (either using weapons or spells), suddenly loses the ability to target what it is attacking - given some specific circumstances:

    Attack() - Attacker can't see invisible creatures, and target either went STATE_INVISIBLE or went under sanctuary.

    Spell() - Target is a sprite (creature), the caster isn't the target, caster can't see invisible creatures, spellFlags & 0x1000000 == 0, and target either went STATE_INVISIBLE, STATE_IMPROVEDINVISIBILITY, or went under sanctuary.

    UseItem() - Target is a sprite (creature), the caster isn't the target, caster can't see invisible creatures, abilityFlags & 0x4000000 != 0, and target either went STATE_INVISIBLE, STATE_IMPROVEDINVISIBILITY, or went under sanctuary.

    This basically mirrors Auto-Pause: Target Gone, (listed as "Character's Target Destroyed" in the Auto-Pause menu), except it doesn't trigger on target death.

  21. Normal specialist bonuses directly modify the save bonus of outgoing effects, but I think I've got an on-target method working:

    1) All spells that acknowledge this bonus should have an Opcode #326 (as the first effect) that invokes a subspell.

    2) The subspell ability projectile must be '0', else the engine uses an internal projectile that delays the effect application.

    3) Put one of the saving throw bonus opcodes (#33-37) in the subspell, using Param2 = 3. The IESDP doesn't list this parameter value, but it is an INCREMENT variant that allows the saving throw to be updated within the current spell application, (normally this cannot be done with stats).

    4) To prevent multiple spells that use this mechanism from stacking their modifications if they hit at the same time, another subspell needs to be applied at the end of the top-level spell which reverses the saving throw bonus, (using the same #33-37 opcode, just with an inverted value).

    The #326 and #33-37 opcodes should all be Instant/Limited, Duration=0. From my limited testing, the above seems to achieve the desired effect.

    Edit: I just checked - the IESDP lists Param2 = 3 for Opcode #325, but it also applies to the individual ones I listed.

  22. To start off, I have little to no experience with how the IE multiplayer functions. The problem I am currently up against is EEex - it currently only works correctly in singleplayer, becoming desynced in multiplayer when being run on anyone who isn't the host. I was going to dive into the problem blind, but I figured it might be beneficial to pick the wisdom of the community before I waste a couple of days. ;)

    So, some of my inquiries/assertions are as follows:

    • 1) I assume both the host and client(s) need identical game files, right?

      2) How do scripts behave while in multiplayer? I see actions such as MultiPlayerSync(), which leads me to believe that each player's game instance is running the scripts separately. Obviously the scripts should be 100% identical if this is what happens, (and some EEex stuff might only be safe to run host-side).

      3) How does the engine synchronize host-client state? Is the game run in its entirety on the host, only syncing to clients? Do both the host and the client run the game individually, frequently syncing up? The MultiPlayerSync() argument above would make me believe the latter to be the case, but I really have no idea. I'm not sure if this question is pertinent to normal modding, so I'm mentally preparing to go down an engine rabbit-hole to figure this one out.

      4) While I'm at it, are there any technical pitfalls to watch out for when making a mod with multiplayer in mind?

    Any insights would be appreciated! (And yes, I put my fake list under a bullet to get indentation, this formatting is killing me)

×
×
  • Create New...