Danukem, on 14 December 2024 - 12:15 PM, said:
EVENT_GAME still has its uses because sometimes you need the context of the actor to do things, such as killit. There are also problems with certain things such as spawning if they are done from the world events. What I do is have the default case of my EVENT_GAME switch set the sprite to not process the event any more by setting the no events flag. What that means is, any sprite that doesn't have specific code for it in the event will only go through the event one time.
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
}
}
endevent
So 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.