@OlvynChuru: Opcode #406 (RenderOverride) is now in master!
Affected creatures will render above normal sprites, and will always render completely above the background.
The opcode has no functionality other than its presence - none of the parameter fields have meaning.
Extended Stat #302 enables the opcode's behavior. Opcode #401 (SetNewStat) can be used to set this stat directly if this is more convenient than using the dedicated opcode, (for some reason?). Note that only a value of "1" will trigger the stat's functionality.
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.
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.
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...
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!
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!
@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.
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).
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.
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.
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:
* 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.
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.
@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.
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?
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.
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.
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.
@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.
@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.
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?
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.
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.
@OlvynChuru: Wall of text incoming, brace yourself!
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:
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)
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:
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.
@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:
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?
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...)
Comments
Affected creatures will render above normal sprites, and will always render completely above the background.
The opcode has no functionality other than its presence - none of the parameter fields have meaning.
Extended Stat #302 enables the opcode's behavior. Opcode #401 (SetNewStat) can be used to set this stat directly if this is more convenient than using the dedicated opcode, (for some reason?). Note that only a value of "1" will trigger the stat's functionality.
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.
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!
@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:
to this:
should work.
Edit: This should now be fixed in master.
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.
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.
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:
* 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.
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?
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.
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.
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.
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():
This tells the engine that the originating Opcode #402 is finished, and to never run it again.
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.
By the way, here's a new spell I've made:
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
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.
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.
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:
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:
Note: EEex_GetGameTick() is a new function that I've just uploaded. Also,
is still required, as the effect doesn't expire on its own.
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.
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.
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:
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?
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...)