Skip to content

[MOD] EEex (v0.10.2-alpha)

1192022242548

Comments

  • OlvynChuruOlvynChuru Member Posts: 3,075
    @Bubb That's fine!
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited July 2019
    @Bubb Thank you! :)

    Here's another request, which could be useful to more people than just me. Could you make an opcode that casts a spell on the target when the effect expires or is removed?

    This could do several things that a delayed Cast Spell effect couldn't. We could set the timing to 10 (limited, ticks) and have it cast the spell after a certain number of ticks (there is no delayed, ticks option normally). Also, it could be put on a piece of equipment to have an effect that occurs when the item is unequipped (though we would have to deal with the problem of the effect also triggering when the game is loaded).

    The parameters would be the same as the Cast Spell opcode, except if special is 1 then it will cast the spell at a point.
    Post edited by OlvynChuru on
    GrammarsaladswitCrevsDaaklolien
  • OlvynChuruOlvynChuru Member Posts: 3,075
    Bubb wrote: »
    Are these extra modes required? It always targets the already-affected creature, and since the spell is fired instantly there isn't a need to target the ground, is there? I can add in the variants of course - I'm just ignorant of basic IE functionality since I haven't worked with them in a while...

    No they aren't. Don't worry about that for now. What you have already sounds good! :)
  • OlvynChuruOlvynChuru Member Posts: 3,075
    Currently the biggest thing I'd like for EEex is that EEex_MatchTarget trigger, which figures out the nearest creature that satisfies the stated conditions. That would make writing AI scripts so much quicker!
    Bubbswitlolien
  • BubbBubb Member Posts: 998
    edited July 2019
    (regarding your pull request on GitHub)

    @OlvynChuru: The bug appears to be that the type field is stored as a byte - not a whole dword, as we are reading it as. Interesting that values are being populated in that space though, because IDA thinks it should be unused. Might be erroneous assembly writing a dword instead of a lower register into the type field, populating it with junk data.

    No matter the cause, changing this:
    return (EEex_ReadDword(EEex_GetActorShare(actorID) + 0x4) == 0x31)
    

    to this:
    return (EEex_ReadByte(EEex_GetActorShare(actorID) + 0x4, 0) == 0x31)
    

    should work.

    Edit: This should now be fixed in master.
    Post edited by Bubb on
    OlvynChuruCrevsDaak
  • CrevsDaakCrevsDaak Member Posts: 7,155
    OlvynChuru wrote: »
    Currently the biggest thing I'd like for EEex is that EEex_MatchTarget trigger, which figures out the nearest creature that satisfies the stated conditions. That would make writing AI scripts so much quicker!
    Why not write your scripts with SSL and use that so you don't have to do it for each Target manually? There's a thread with documentation in the Modding Howtos and tutorials section in the G3 forums. Implementing something that'd replicate this existing functionality inside the game will probably need more resources than what the game already uses for processing scripts, and work to be done for that to actually function in the first place.

    That said it'd be pretty cool to have something like that, particularly if you can check the farthest creature as well and swap PlayerX tokens on TriggerOverride()s. This way you could make a near perfect script for using AoE damage spells with minimal work (I think you could even do this with SSL but I'm not certain, although SSL is still better in the regard that it lets you edit scripts by hand after it's done processing them).
  • OlvynChuruOlvynChuru Member Posts: 3,075
    CrevsDaak wrote: »
    OlvynChuru wrote: »
    Currently the biggest thing I'd like for EEex is that EEex_MatchTarget trigger, which figures out the nearest creature that satisfies the stated conditions. That would make writing AI scripts so much quicker!
    Why not write your scripts with SSL and use that so you don't have to do it for each Target manually? There's a thread with documentation in the Modding Howtos and tutorials section in the G3 forums. Implementing something that'd replicate this existing functionality inside the game will probably need more resources than what the game already uses for processing scripts, and work to be done for that to actually function in the first place.

    That said it'd be pretty cool to have something like that, particularly if you can check the farthest creature as well and swap PlayerX tokens on TriggerOverride()s. This way you could make a near perfect script for using AoE damage spells with minimal work (I think you could even do this with SSL but I'm not certain, although SSL is still better in the regard that it lets you edit scripts by hand after it's done processing them).

    Learning how to use SSL, writing SSL code for all the scripts I'm using, writing WeiDU code to compile the SSL, and debugging it, may be more work than using EEex_MatchTarget, depending on how easy EEex_MatchTarget ends up being to use.
  • BubbBubb Member Posts: 998
    Regarding the EEex_MatchTarget trigger, should it iterate every single creature in the area, regardless of state?

    Object selectors skip over creatures in certain events, like if they are dead or the source is immune to the target's creature type... I'd also assume it skips over inactive creatures.

    Should the trigger reveal these eventualities to the Lua filter, or should it stay within the confines of what a selector deems "valid"?

    And on the talk of speed, the execution of Lua isn't slow enough to cause a slowdown in AI scripts. One thing to consider is that the whole UI is being pinged 30 times a second, and it is using the Lua engine, the same as EEex. The only time I've ever experienced a real slowdown using Lua is when I tried to externalize some rendering functionality.
    OlvynChuruswitCrevsDaaklolien
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited July 2019
    Bubb wrote: »
    Regarding the EEex_MatchTarget trigger, should it iterate every single creature in the area, regardless of state?

    Object selectors skip over creatures in certain events, like if they are dead or the source is immune to the target's creature type... I'd also assume it skips over inactive creatures.

    Yes, it should probably skip over creatures like those that shouldn't be selectable.

    It also should not be limited to creatures the character can see or creatures within a certain range, because those could be replicated by putting a See or Range trigger as one of the matching conditions.

    By the way, I found a bug: EEex_GetActorStat often returns 0 rather than the actual value of the stat if it's called more than once for the same stat frequently. For example, I gave Garrick a bow that sets stat 633 to 1 and stat 634 to 2 while equipped. I then put some print statements in the looping Lua function I use to change a creature's height. This was what happened:

    rkben8cdceoc.png

    * The same thing happens if the stats are set with a permanent effect.
    * This does not have to do with the EEex_IsSprite check I put in the EEex_GetActorStat function. As a test, I made the EEex_GetActorStat function print "ugu" if the EEex_IsSprite check returned false, but it never printed that.
    Bubblolien
  • kjeronkjeron Member Posts: 2,367
    Bubb wrote: »
    Object selectors skip over creatures in certain events, like if they are dead or the source is immune to the target's creature type... I'd also assume it skips over inactive creatures.
    They also skip over sleeping/unconscious/prone creatures, for whatever reason.
    BubbCrevsDaak
  • BubbBubb Member Posts: 998
    @OlvynChuru: How and when is the Lua function being invoked? It most likely has something to do with the function being called before the stats have been copied over to temp, but I need to know the exact circumstance to figure out what's going on.
  • fortysevenfortyseven Member Posts: 96
    Hi Bubb great to see you are still working on this mod with so much energy and enthusiasm!

    There have been so many changes since I last read this thread with quite a bit of it going way above my head.

    Back in the day I made a request to make "disarm trap" into an opcode like Knock. That way we could make spells and abilities that would allow for trap disarming without having to have a thief action bar on one of our party NPCs. With all the additional changes over the last few months is that something that would be do-able now?
    CrevsDaakBubblolien
  • OlvynChuruOlvynChuru Member Posts: 3,075
    Bubb wrote: »
    How and when is the Lua function being invoked? It most likely has something to do with the function being called before the stats have been copied over to temp, but I need to know the exact circumstance to figure out what's going on.

    The function is being invoked in a loop. The function ends by applying an Invoke Lua effect calling the same function; that effect has a timing of 3 (delayed, limited) and a duration of 0, which usually makes it trigger one tick later (sometimes this causes problems, but that's not important right now).

    I made a much simpler test function to make sure it's not some weird problem with my complicated height function.
    function METEST(effectData, creatureData)
    	local targetID = EEex_ReadDword(creatureData + 0x34)
    	Infinity_DisplayString("Stat 633: " .. EEex_GetActorStat(targetID, 633) .. "; Stat 634: " .. EEex_GetActorStat(targetID, 634))
    	Infinity_DisplayString("New iteration...")
    	EEex_ApplyEffectToActor(targetID, {
    ["opcode"] = 402,
    ["target"] = 2,
    ["timing"] = 3,
    ["resource"] = "METEST",
    ["source_target"] = targetID,
    ["source_id"] = targetID
    })
    end
    

    I had this function be applied to Garrick while he was wielding a bow that set stat 633 to 1 and stat 634 to 2.

    lp278a98e3nq.png
    BubbCrevsDaak
  • BubbBubb Member Posts: 998
    @OlvynChuru: Thanks, I was able to find the issue - it is now fixed in master.

    Also, I'll look into that nasty infinite loop situation when attempting to move items around the inventory with the loop running...
    OlvynChuruCrevsDaaklolien
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited July 2019
    Bubb wrote: »
    Also, I'll look into that nasty infinite loop situation when attempting to move items around the inventory with the loop running...

    I've already banged my head against the infinite loop problem a lot while I was developing my height-changing function, so here's what I know so far.

    There are some situations where any effect that has a delayed timing and a duration of zero will have no delay whatsoever (normally it would have a delay of 1 tick). These situations include:

    * Occasionally when the game is paused and unpaused
    * When the game is reloaded
    * When you move items in the inventory (as you found)

    This causes problems with my looping functions, as well as with spells that cast themselves after a 1-tick delay. When this situation happens, the function goes to the next iteration instantly rather than waiting 1 tick, and it loops too fast for the game to handle, causing a freeze.

    Not only does the function get called instantly, it can get called multiple times when it should only be called once.

    I managed to solve these problems for my height-changing function, but the solution was pretty ugly and wouldn't be easy to port to other functions.
    CrevsDaakBubb
  • BubbBubb Member Posts: 998
    @OlvynChuru: Ok, the infinite loop is crazy:

    1) CGameEffect::ResolveEffect() invokes Opcode #402, which then calls METEST()
    2) METEST() calls EEex_AddEffectToActor(), which calls the engine's CGameSprite::AddEffect()
    3) CGameSprite::AddEffect() calls CGameSprite::ProcessEffectList()
    4) CGameSprite::ProcessEffectList() calls CGameSprite::HandleEffects()
    5) CGameSprite::HandleEffects() calls CGameEffectList::HandleList()
    6) CGameEffectList::HandleList() calls CGameEffect::ResolveEffect()
    7) goto 1...

    The gist of it is that adding an effect to an actor sets in motion a whole chain of functions that evaluate every effect on the creature. The originating Opcode #402 is triggering this evaluation before it is marked as "done", so it gets invoked again, and again, because every time it is called it is triggering a new evaluation pass... without fully finishing its own.

    Workaround: put this before you call the looping EEex_AddEffectToActor():
    EEex_WriteDword(effectData + 0x110, 0x1)
    

    This tells the engine that the originating Opcode #402 is finished, and to never run it again.
    OlvynChurufearlessCrevsDaak
  • OlvynChuruOlvynChuru Member Posts: 3,075
    @Bubb It works! Thank you! I spent hours and hours trying to fix this bug (I eventually succeeded, but that line of code is a much simpler solution).
    CrevsDaakBubblolien
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited July 2019
    @Bubb I'm wondering, is there anything in the creature data that determines the height projectiles aim for when they target the creature? I do know this height can be changed by editing the creature animation's height above the center point in the BAM file. But I'm wondering if it's more directly determined by something in the creature data.

    When a spell with a projectile is cast at a creature, what does it look at to determine the height to head towards? Is it something in the creature data, or does it look straight at the data for the animation? I'm wondering because if it's possible to change the projectile-target height of a creature, I could harness that for my height-changing function, which currently doesn't have a good way to do this.
  • BubbBubb Member Posts: 998
    @OlvynChuru: It's tied solely to the creature's animation. I could attempt to make Opcode #406 (RenderOverride) compensate for the differing position by enabling the animation to account for the posZ, if you want.
    OlvynChuru
  • UlbUlb Member Posts: 295
    Hey, it's been a while and I've only followed this thread loosely. So, apologies if this has recently come up/been answered and I've missed it:
    Is there a way yet to prevent characters from gaining spells on creation/level up?
  • OlvynChuruOlvynChuru Member Posts: 3,075
    Ulb wrote: »
    Hey, it's been a while and I've only followed this thread loosely. So, apologies if this has recently come up/been answered and I've missed it:
    Is there a way yet to prevent characters from gaining spells on creation/level up?

    So far, no, unless there's some way to do it with UI modding.

    @Bubb There's another problem. The fix of
    EEex_WriteDword(effectData + 0x110, 0x1)
    

    doesn't stop the infinite loop if the looping function is called by a different function, even if the other function also has that line of code. Here's the test function I'm using, which causes a creature to accelerate upward.
    function METEST(effectData, creatureData)
    	local targetID = EEex_ReadDword(creatureData + 0x34)
    	EEex_WriteDword(effectData + 0x110, 0x1)
    -- An effect I use to set the creature's acceleration
    	EEex_ApplyEffectToActor(targetID, {
    ["opcode"] = 401,
    ["target"] = 2,
    ["timing"] = 9,
    ["parameter1"] = 3,
    ["parameter2"] = 1,
    ["special"] = 643,
    ["parent_resource"] = "METEST",
    ["source_target"] = targetID,
    ["source_id"] = sourceID
    })
    -- This effect calls the looping function
    	EEex_ApplyEffectToActor(targetID, {
    ["opcode"] = 402,
    ["target"] = 2,
    ["timing"] = 3,
    ["resource"] = "MEHGTMOD",
    ["parent_resource"] = "METEST",
    ["source_target"] = targetID,
    ["source_id"] = targetID
    })
    end
    

    If I pause the game at the same time that this function is called, the game freezes. Otherwise, it works fine. The game also freezes if I pause the game at the same time a creature would be hit by one of my spells that calls the looping function.
    CrevsDaak
  • BubbBubb Member Posts: 998
    @OlvynChuru: Wall of text incoming, brace yourself! :p

    Understanding why pausing the game makes a 0-duration delay evaluate instantly (in certain situations) requires knowledge of the different timing modes, and how they are processed. The following steps though what happens to our looping effect every time it is resolved:

    1st Effect Resolve:
    - duration type is 3
    - convert to duration type = 6 ; duration = gameTime + (duration * 15)

    2nd Effect Resolve:
    - duration type is 6, check if (gameTime >= duration) ?
    - Yes: convert to duration type = 0x1000 ; duration = gameTime + (duration * 15) ; execute effect
    - No: do nothing, remain on this step until above is satisfied

    3rd? (or higher) Effect Resolve:
    - duration type is 0x1000, check if (gameTime < duration) ?
    - Yes: execute effect
    - No: flag effect for deletion and instantly return

    From the above, we can see that a 0-duration delay effect will execute the tick it is applied if it is resolved more than once. Turns out the whole "runs on the next tick" functionality is a fluke of the engine, not reliable design.

    As it happens, CGameSprite::AddEffect() can resolve the effect twice in one call:
    1. If the immediateResolve arg is true it will resolve the effect before even adding it to the creature's effect list, (I have this set in EEex_ApplyEffectToActor)
    2. If the game is paused the function additionally resolves all of the effects currently on the creature, including the one we just added...

    So, that's why pausing the game causes an infinite loop. In essence, we are playing a game of trying prevent the effect from being resolved twice in the same tick, and that's just ridiculous. We don't have, nor should have, any say over when the engine decides to go over the effects list.

    Solution: Don't use the unreliable 0-duration mechanic. Instead, construct a duration type = 6 effect manually. Here's a looping METEST() that I've never seen get stuck:
    function METEST(effectData, creatureData)
    
        local targetID = EEex_ReadDword(creatureData + 0x34)
        EEex_WriteDword(effectData + 0x110, 0x1)
    
        local currentTick = EEex_GetGameTick()
        local targetTick = currentTick + 1
    
        Infinity_DisplayString("Tick: "..currentTick)
    
        EEex_ApplyEffectToActor(targetID, {
            ["opcode"] = 402,
            ["target"] = 2,
            ["timing"] = 6,
            ["duration"] = targetTick,
            ["resource"] = "METEST",
            ["parent_resource"] = "METEST",
            ["source_target"] = targetID,
            ["source_id"] = targetID
        })
    
    end
    

    Note: EEex_GetGameTick() is a new function that I've just uploaded. Also,
    EEex_WriteDword(effectData + 0x110, 0x1)
    

    is still required, as the effect doesn't expire on its own.
    OlvynChurufearlessCrevsDaak
  • OlvynChuruOlvynChuru Member Posts: 3,075
    edited July 2019
    @Bubb Thank you! :)

    Another thing: could you share the function you used to change a creature's height (B3SetPosZ)?

    The way my function changes a creature's height is really awkward: I need to add one new creature animation to the game for each possible height the creature could be set at (for the range of heights shown in that reversed gravity GIF I posted, I needed to add 1000 new animations to the game). I was wondering if you had an easier way.
    BubbCrevsDaak
  • BubbBubb Member Posts: 998
    edited July 2019
    @OlvynChuru: The function isn't anything special. It's just a sanity wrapper for this:
    EEex_WriteDword(share + 0x10, posZ)
    

    Note that setting posZ will invoke the bounce mechanics, so I'm not sure if it will help you much without a hook that disables that.
    CrevsDaak
  • OlvynChuruOlvynChuru Member Posts: 3,075
    @Bubb That works! My old way of changing a creature's height also invoked the bounce mechanics, but the strategy is to keep changing the creature's height so frequently that it doesn't look like they're bouncing.

    My old way also had another problem: it basically paralyzed the creature, so it wouldn't be good for a flying mechanic. By changing the height your way, the flying creature can continue doing things:

    5o7zer5krcjk.gif

    One issue with this method: when I throw a creature into the air multiple times, each time they don't go as high as the previous time. It seems like the game doesn't reset the creature's falling speed to 0 when the creature hits the ground, and after their height gets changed too many times, they fall really fast. Where in the creature data does it store the creature's vertical speed?
    BubbCrevsDaak
  • BubbBubb Member Posts: 998
    @OlvynChuru: posZDelta is stored at offset 0x2D00; I believe that controls the falling speed.
    OlvynChurufearlessCrevsDaak
  • _Luke__Luke_ Member, Mobile Tester Posts: 1,535
    OlvynChuru wrote: »
    Ulb wrote: »
    Hey, it's been a while and I've only followed this thread loosely. So, apologies if this has recently come up/been answered and I've missed it:
    Is there a way yet to prevent characters from gaining spells on creation/level up?

    So far, no, unless there's some way to do it with UI modding.

    Yeah, that would be great... In particular, it'd be nice to apply it to single kits (and not just to the class they belong to...)
Sign In or Register to comment.