EDuke32 Scripting "CON coding help"
#3623 Posted 14 December 2024 - 09:06 AM
Note: repeated message, due to a problem when loading this site.
This post has been edited by eniojr: 14 December 2024 - 09:08 AM
#3624 Posted 14 December 2024 - 11:23 AM
#3625 Posted 14 December 2024 - 12:15 PM
EDIT: I've never done it before but I wonder if you could safely delete a sprite from within a WORLD event with changesrpitestat SPRITENUMBER 1024
EDIT2: Not sure about that, and apparently it isn't what the CON command does
vInstruction(CON_KILLIT):
insptr++;
vm.flags |= VM_KILL;
return;
The flag for killit seems to be listed here at the end: https://wiki.eduke32.com/wiki/Htflags as SFLAG_QUEUEDFORDELETE?
But I think the flag listed in the wiki is for the sprite deletion queue which is different (the queue for decals). So I'm still not sure.
This post has been edited by Danukem: 14 December 2024 - 01:02 PM
#3626 Posted 14 December 2024 - 01:05 PM
Now I just need to know how to activate this specific switch.
Before, I was trying to set "case ladder" as "state ladder", but that wasn't the right way to do it. What I need to know is to understand how the mod developer planned all this, why he made "case ladder" inside a game event instead of a separate "state ladder".
Another thing is about the order of states and events. Since they are in different files, it's hard for me to understand how to put everything in a single .CON file related to the effects I added in Legacy.
I put them in the following order in the script: gamevars and defines > state pythagoras > eventloadactor JUMPAD > useractor notenemy JUMPAD 0 > eventloadactor LADDER > state laddercode > state checkwall > onevent EVENT_GAME (with switch picnum and case ladder inside) > onevent EVENT_MOVEFORWARD > onevent EVENT_MOVEBACKWARD > onevent EVENT_TURNAROUND > onevent EVENT_JUMP > onevent EVENT_PROCESSINPUT.
I don't know if there's something else missing related to the files in Dukeplus. It seems not.
Of these, only the JUMPAD effect worked.
I'm no longer interested in mantling and dodge, so I discarded the codes related to them, leaving only the ones for jumpad and ladder. I did this to avoid possible conflicts.
I noticed that the events related to movement (forward and backward) would be related to the staircase effect, just as there is something in event_processinput that would also be related, since it seems to me that the staircase effect depends on the activation of an input, unlike the jumpad which would be independent of the use of directional keys.
This was the context I understood so far.
This post has been edited by eniojr: 14 December 2024 - 01:09 PM
#3627 Posted 14 December 2024 - 01:08 PM
This post has been edited by eniojr: 14 December 2024 - 01:08 PM
#3628 Posted 14 December 2024 - 02:54 PM
#3629 Posted 14 December 2024 - 06:31 PM
This post has been edited by eniojr: 14 December 2024 - 06:31 PM
#3630 Posted 14 December 2024 - 07:01 PM
#3631 Posted 14 December 2024 - 07:31 PM
Quote
Thanks a lot! First I'll finish with Dukeplus. If I have no luck, I'll try AWOL.
#3632 Posted 14 December 2024 - 08:06 PM
Mark, on 14 December 2024 - 07:01 PM, said:
Remember back in February when I refactored and simplified the DP ladder code for your mod? Good times.
#3633 Posted 14 December 2024 - 10:28 PM
Danukem, on 14 December 2024 - 12:15 PM, said:
EDIT: I've never done it before but I wonder if you could safely delete a sprite from within a WORLD event with changesrpitestat SPRITENUMBER 1024
EDIT2: Not sure about that, and apparently it isn't what the CON command does
vInstruction(CON_KILLIT):
insptr++;
vm.flags |= VM_KILL;
return;
The flag for killit seems to be listed here at the end: https://wiki.eduke32.com/wiki/Htflags as SFLAG_QUEUEDFORDELETE?
But I think the flag listed in the wiki is for the sprite deletion queue which is different (the queue for decals). So I'm still not sure.
So the vm.flags property is different than the htflags and friends we have exposed to CON. We don't have access to it. It also doesn't really get used much.
To mimic killit functionality, you can set the sprite statnum to STAT_MISC and set the .xrepeat to 0. This is also great because it works on non-actor sprites, which mostly comes in handy if you are using custom statnums to run code on sprites but you don't want them to be full-blown actors. We'll come back to that in a sec.
Anyway, any sprite with the VM_KILL flag runs VM_DeleteSprite(), which runs A_DeleteSprite(), which is down in the BUILD part of the game engine and actually deletes the sprite. This is also where EVENT_KILLIT happens. I'm pretty sure there you can catch the sprite's statnum and xrepeat to verify the change.
More importantly, internally hardcoded actors don't use VM_KILL at all. There is no internal "killit". For example, all of the internally flagged enemies run this code to delete themselves if they are tagged on a higher difficulty or monsters are turned off:
if ((pSprite->lotag > ud.player_skill) || ud.monsters_off == 1)
{
pSprite->xrepeat=pSprite->yrepeat=0;
changespritestat(newSprite, STAT_MISC);
goto SPAWN_END;
}That being said, you're right that there are a few things that you need to "run as the actor" to work properly. ifcansee and ifpdistl are the 2 I've run into most recently (and for Reasons, I couldn't use canseespr and regular dist). So there are times where EVENT_GAME is the right tool for the job. But depending on what stuff you're doing, you may have less overhead without a loss of functionality by running it in EVENT_WORLD instead.
Lastly, another thing you can do if you really need to "run code as the actor" is to simply... become the actor, as far as the script is concerned. This is what the userdef property vm_sprite is used for. It doesn't just turn THISACTOR into a different sprite, you literally begin running code as that sprite. This is how we use custom statnums.
Basically, a sprite with the statnum STAT_ACTOR is, in literal terms, what lets a sprite be a defined actor and run code from actor/useractor blocks in a CON script. But it also does a lot of other stuff internally - physics, running game events, so on - so if you need something (like say an effect particle) to run some code but don't need the overhead of it being a whole actor, you can use a custom statnum. This is really cool because you can do stuff like this (simplified for this example):
/*
// Handle game system state machines
*/
appendevent EVENT_WORLD
{
// Process AI Director
for WORLDI sprofstat STAT_AIDIRECTOR
{
setu .vm_sprite WORLDI // Become the sprite we are iterating on
setu .vm_player myconnectindex // Update which sprite the Player is
ife GM_ACTIVE TRUE
state ai_director
}
endevent
/*
// AI Director state machine
*/
defstate ai_director
{
[ an actual ton of code ]
}
ends
/*
// Init system effectors
*/
appendevent EVENT_ENTERLEVEL
{
for ACTORI allsprites
{
// Init AI Director
ife sprite[ACTORI].picnum AI_DIRECTOR
seta[ACTORI].statnum STAT_AIDIRECTOR
}
}
endeventSo what we're doing here is setting the sprite AI_DIRECTOR to the statnum STAT_AIDIRECTOR when the map loads. Then, every gametic in EVENT_WORLD, it does a sprofstat loop for all sprites with statnum STAT_AIDIRECTOR, and then steps into that sprite and runs code as that sprite. In other words, we're running code like we're an actor without being an actor.
This is a poor example of how to use it but it's what I have open right now. Instead imagine doing this for things like particle visual effects, or non-actor sprites that need to run a little bit of code but don't need to be full-on actors. We have something like 40 custom stats in AWOL because basically everything that isn't an enemy but runs code in some way is a custom statnum. Enemy corpses are a custom statnum, because that way you can stop them from being actors (which again comes with a ton of overhead) while also checking for nearby explosions to gib them.
I have to credit Mblackwell for turning me on to this, a while ago he was like "hey you should look at vm_sprite". EVENT_WORLD and custom statnums is this insanely powerful tool, it seems like overkill but it lets you run tons of code on non-actors and shed an enormous amount of overhead from the performance budget.
#3634 Posted 14 December 2024 - 10:40 PM
Also that's good info about .vm_sprite and .vm_player -- I will probably use that at some point to get around the limitations of not having the individual context in world events
#3635 Posted 14 December 2024 - 11:12 PM
// Save ourself
set HOST_ID THISACTOR
for ACTORI sprofstat STAT_SOMETHING
{
setu .vm_sprite ACTORI
[ execute remote code ]
}
// Return to ourself
setu .vm_sprite HOST_IDOr you can imagine using this with like .htmovflags to run remote code on a sprite we bump into for example.
#3636 Posted 15 December 2024 - 09:43 AM
Also wouldn’t adding that no events sprite stat to the default case in a switch while in EVENT_GAME like Dan said take care of the actors that aren’t called in EVENT_GAME? I didn’t see a response to that.
The custom stats is something I definitely need to look into. My mod is heavy in gibs and debris and effects. Anything to further optimize frame rate is a must. Thanks for pointing that stuff out.
#3637 Posted 15 December 2024 - 10:31 AM
The reason you use a for loop in the case of custom statnums is so that you can search through a smaller pool of sprites. Rather than searching through allsprites, or even STAT_ACTOR. That is where and why you're getting the performance savings.
In the case of custom debris and effects, again what we did in AWOL was have a defined statnum for each particle type, and then each of those statnums would be looped through in EVENT_WORLD and the code for that particle type would be run on those sprites. Here's an excerpt from awol_vfx.con:
// Perform particle logic
appendevent EVENT_WORLD
{
for PARTICLE_I sprofstat STAT_EMBER
{
setu .vm_sprite PARTICLE_I // set our "THISACTOR" to the current id
setu .vm_player myconnectindex // set our player to our currently connected player
state particle_ember
state particle_deleteme
}
for PARTICLE_I sprofstat STAT_SMALLFIRE
{
setu .vm_sprite PARTICLE_I
setu .vm_player myconnectindex
state particle_smallfire
state particle_deleteme
}This continues on for a while for all of our particle types. The state particle_deleteme is where we do checks if we need to delete the particle early - too far away, not visible, and so on - and it just does the .xrepeat 0 / .statnum STAT_MISC thing mentioned before to "mark for deletion". Also I'm not sure if I was clear about that before. Setting the statnum to STAT_MISC stops the sprite from executing code (as an actor, at least), and setting .xrepeat to 0 is most explicitly what makes the sprite get deleted. At the end of the gametic, after CON scripting has completed, the engine looks for anything with an invalid sprite size (IE .xrepeat 0) and deletes it with A_DeleteSprite() down in the BUILD part of the engine.
Anyway. I'm not sure what use case you had in mind for a switch statement here. If you want to post your code we can take a look. But there's nothing inherently wrong with using a switch statement in this sort of context. The for loop is what says "run this code on every sprite with statnum STAT_PARTICLE", for example. If you had all of your particle types sharing the same statnum, and then wanted to run separate code on them based on their picnum, you could use a switch like that maybe? To adapt the previous code to this method:
appendevent EVENT_WORLD
{
for PARTICLE_I sprofstat STAT_PARTICLE
{
setu .vm_sprite PARTICLE_I // set our "THISACTOR" to the current id
setu .vm_player myconnectindex // set our player to our currently connected player
// Remember, we are now running as the sprite PARTICLE_I so we need to check our own picnum instead of "sprite[PARTICLE_I].picnum"
switch sprite[].picnum
{
case SOME_PARTICLE
state particle_ember
break
case OTHER_PARTICLE
state particle_smallfire
break
default
[ some failsafe code ]
break
}
endswitch
state particle_deleteme
}
}
endeventI don't see why this wouldn't work in theory, but personally I would just use a separate statnum for each particle type. That would be more robust, easier to read, and statnums are free so there's effectively no limitation to them.
#3638 Posted 15 December 2024 - 11:24 AM
I messed around with this already and there's a lot of drawbacks that I'm already seeing. Like with corpses, I gave them a custom stat and I have the world event call on statements to make them react to getting hit and what not. But they no longer react to explosion damage correctly, and they don't fall to the floor as they should. If the ground moves, they just hover. Same goes for gibs or debris actors that are given their own custom stats. May there be too many limits when using the WORLD event? Even with these issues aside I'm not seeing a big performance increase and i've got gibs, debris, and corpses using custom stats.
#3639 Posted 15 December 2024 - 11:53 AM
#3640 Posted 15 December 2024 - 12:38 PM
#3641 Posted 15 December 2024 - 01:05 PM
You can make your corpses interact with the physics engine still by forcing them to move. The problem is that if you set them to a new statnum, they stop being actors and therefore stop doing movement stuff. Here's how we handle it in AWOL:
// Process logic for corpses without making them actors
appendevent EVENT_WORLD
{
for ACTORI sprofstat STAT_CORPSE
{
setu .vm_sprite ACTORI
setu .vm_player myconnectindex
fall
action 0
move 0
state enemy_elite_display // Ignore for this example, this handles their palette and custom sprite display
}
}
endeventSo because they're now a non-actor statnum, you have to tell them to do the same sort of actor things like movement, have a new "ifhitweapon" block, and so on.
#3642 Posted 15 December 2024 - 01:21 PM
What would be a good way to go about keeping lots of gibs and debris from spawning at the same time? I’ve got code that checks current frame rates before spawning random amounts of gibs and debris but when lots of stuff is spawned at once this doesn't help as much.
#3643 Posted 15 December 2024 - 08:05 PM
How are you checking the framerate exactly, and how do you use that to determine spawns?
#3644 Posted 15 December 2024 - 09:15 PM
With jibs and debris I recommend going for more quality over quantity. For example, if you have the jib sprite include blood flying off of it on its animation, then that's a tremendous savings versus spawning little blood sprites from it as a trail. But if you really think you need a lot of those sprites, you can spread out the spawn over several tics -- the jibbing actor doesn't have to delerte immediately, it could turn invisible and spawn say 25% of them per tic over 4 tics and then delete.
#3645 Posted 16 December 2024 - 06:25 AM
I tried having the enemies spawn a separate actor that calls on statements that spawn the gibs so that I could have them do exactly what you said. Spawn gibs over 4 or 5 tics and then delete itself but no matter what I do they never run through the entire list of gibs that are to be spawned. Usually the gibs that are to be spawned at like 4 or 5 tics don’t spawn at all no matter how long I have the gib spawning actor wait to kill itself. And in general the amount of gibs that are spawned with this method are lackluster. However I do have gibs that create trails of blood and so that’s something I can look into for sure. It’s not really noticeable anyways so it’s not needed. I understand quality over quantity but in this case I really want as big of a quantity as possible. I don’t think I’ve seen this many gibs and debris in a duke mod before and what I’ve got going I’m very happy about. Thanks for the tips.
#3646 Posted 16 December 2024 - 07:53 AM
Another way you can get around the "spawning is expensive" problem by recycling debris/gibs. This is a concept known as object pooling. Say you have a pool of 100 persistent debris objects, another pool for dibs, and so on. When it comes time to spawn one, you would check how many objects are in your pool. If you have room for more (IE less than 100) you spawn in a new one like normal. But if you have a full pool of objects, rather than spawning a new one, you'd find the oldest debris object and recycle it. Move it into position, re-initialize its animation, movement, etc. just like you would for a new one. This way you skip all of the on-spawn overhead, and you have a finite amount of those objects.
As far as it not spawning enough, add some logging to output something every time it's supposed to be spawning something. It's impossible to say what the issue is there without more context of the entire project or what else is going on.
#3647 Posted 16 December 2024 - 08:15 AM
What happened is that I ended up doing a deep investigation of all the variables related to the ladder code, and in one of the Dukeplus files I ended up finding all the codes that were still missing, so I put them inside a state that I made myself (state ladderinteraction) and put the call to this state inside onevent EVENT_GAME, at the end of the block. I also realized that what was missing for the player to be able to interact with the ladder was "getactor[THISACTOR].picnum picnum", which I put right at the beginning of the EVENT_GAME block.
But then I realized that when I interacted with JUMPAD first, it kept preventing me from interacting with the ladder later. So I had to make some adjustments to the JUMPAD timers, and now it no longer interferes with the interaction with the ladder.
Oh, and I found these missing codes for interacting with the ladder through manual research on my own, without using the AI assistant.
Attached File(s)
-
LADDER.CON (20.88K)
Number of downloads: 133
This post has been edited by eniojr: 16 December 2024 - 08:15 AM
#3648 Posted 16 December 2024 - 08:57 AM
Reaper_Man, on 16 December 2024 - 07:53 AM, said:
Another way you can get around the "spawning is expensive" problem by recycling debris/gibs. This is a concept known as object pooling. Say you have a pool of 100 persistent debris objects, another pool for dibs, and so on. When it comes time to spawn one, you would check how many objects are in your pool. If you have room for more (IE less than 100) you spawn in a new one like normal. But if you have a full pool of objects, rather than spawning a new one, you'd find the oldest debris object and recycle it. Move it into position, re-initialize its animation, movement, etc. just like you would for a new one. This way you skip all of the on-spawn overhead, and you have a finite amount of those objects.
As far as it not spawning enough, add some logging to output something every time it's supposed to be spawning something. It's impossible to say what the issue is there without more context of the entire project or what else is going on.
That pool idea sounds like something that I can work towards. Only allow so many to spawn within a given pool allowance. Thanks for the great tips guys. I’ll try and put this to use and work at it some more.
#3649 Posted 16 December 2024 - 12:16 PM
VGames, on 16 December 2024 - 06:25 AM, said:
So in other words your code doesn't work as intended and you don't know why. You don't seem to know how to debug it.
#3650 Posted 16 December 2024 - 02:27 PM
onevent EVENT_GAME
{
getactor[THISACTOR].picnum picnum
switch picnum
{
case LADDER
{
ifvare monstflags 1
{
ifvare monstatus 0
{
setvar monstatus 1
setvar temp 0
whilevarn temp 16384
{
getactor[temp].picnum picnum
ifvare picnum LADDER ifvarvarn temp THISACTOR
{
getactorvar[temp].monstflags tempb
ifvare tempb 1
{
getactorvar[temp].hitag tempc
ifvarvare hitag tempc
{
setvarvar myspawner temp
setvar monstatus 1
setvar temp 16383
getactor[THISACTOR].z z
getactor[myspawner].z mz
ifvarvarg z mz
{
setvar mtype 1
setvarvar topladder mz
}
}
}
}
addvar temp 1
}
}
ifvare mtype 1 ifvare dodge 0
{
getplayer[THISACTOR].posz mz
ifp pducking subvar mz 4096
getactor[THISACTOR].z z
addvar z 1024
ifvarvarg mz topladder ifvarvarl mz z
{
getplayer[THISACTOR].i target
ldist xydist THISACTOR target
ifangdiffl 384 ifvarl xydist 384
{
ifvare onladder 0
{
getplayer[THISACTOR].posx lastladderx
getplayer[THISACTOR].posy lastladdery
}
setvar onladder 3
ifvarn lotag 0 setvarvar laddersound lotag else setvar laddersound -1
}
}
}
}
break
}
case APLAYER
{
ifvarg onladder 0 state ladderinteraction
break
}
}
endswitch
}
endeventAnd here were the missing codes for the effect to work:
state ladderinteraction
{
// Lógica de interação com a escada
ifvarg fpress 0 subvar fpress 1
ifvarg bpress 0 subvar bpress 1
ifvarl onladder 0 addvar onladder 1
ifvarg onladder 0 state laddercode
ifp ponground setvar nofalldamage 0
ifvarn nofalldamage 0 setplayer[THISACTOR].falling_counter 0
ifvarg bhold 7 { setvar btap 0 setvar bhold 0 }
ifvarg fhold 7 { setvar ftap 0 setvar fhold 0 }
ifvarg lhold 7 { setvar ltap 0 setvar lhold 0 }
ifvarg rhold 7 { setvar rtap 0 setvar rhold 0 }
ifvarvarn timer fpresstime setvar fhold 0
ifvarvarn timer bpresstime setvar bhold 0
setvarvar tempb timer
subvarvar tempb fpresstime
ifvarg tempb 4 setvar ftap 0
setvarvar tempb timer
subvarvar tempb bpresstime
ifvarg tempb 4 setvar btap 0
setvarvar tempb timer
subvarvar tempb lpresstime
ifvarg tempb 4 setvar ltap 0
setvarvar tempb timer
subvarvar tempb rpresstime
ifvarg tempb 4 setvar rtap 0
}
endsThese codes that I put in the "state ladderinteraction" were inside "onevent EVENT_GAME", but were outside the "case LADDER". I had to do this, put them inside a separate state, otherwise I wouldn't have been able to make it work. And the effect is working just like in the Dukeplus mod. My modification now uses 3 specific sounds for when the player climbs the ladder, which gives a bit more realism.
It's true that my codes don't have a more simplified format or shortcuts yet, but at the moment I'm more concerned with knowing the basics of how to add new effects and other things to the game. As I learn more about the CON language and become more familiar with it, I'll feel more confident in dealing with the game's performance.
This post has been edited by eniojr: 16 December 2024 - 02:28 PM

Help
Duke4.net
DNF #1
Duke 3D #1


