
EDuke32 Scripting "CON coding help"
#2611 Posted 28 October 2020 - 12:08 PM
#2612 Posted 30 October 2020 - 12:24 PM
Jimmy, on 28 October 2020 - 12:08 PM, said:
The issue is that I'm not sure how to do that.
#2613 Posted 20 November 2020 - 02:52 AM
1, i want to add new reload animations to a custom weapon wich replaces the chaingun. it behaves like the chaingun/ripper except it needs a 30 round magazine, and some reload animations
i have all the artwork in place, might need some tweaking, but i cant for the life of me figure out how to tell the game when to reload, where the artfiles are and how long the animation needs to last, tried looking at the wiki and use the 'manual reload' tutorial, but it just jiggles the gun infinetly.
i have the new reload animation, 7 frames long at tile 2582, and basically it is an oscillation of pulling out a mag, and sticking it back in.
for the weapon i use these gamevars.
// thompson
gamevar WEAPON3_WORKSLIKE 3 0
gamevar WEAPON3_CLIP 30 0
gamevar WEAPON3_RELOAD 60 0
// gamevar WEAPON3_FIREDELAY 1 0
gamevar WEAPON3_TOTALTIME 10 0 // was 10
// gamevar WEAPON3_HOLDDELAY 0 0
gamevar WEAPON3_FLAGS 73813 // 85
gamevar WEAPON3_SHOOTS CHAINGUN
// gamevar WEAPON3_SPAWNTIME 0 0
gamevar WEAPON3_SHOTSPERBURST 0 0
gamevar WEAPON3_INITIALSOUND 0
gamevar WEAPON3_FIRESOUND CHAINGUN_FIRE
gamevar WEAPON3_SOUND2TIME 0
gamevar WEAPON3_SOUND2SOUND 0
gamevar WEAPON3_SPAWN SHELL
2, i have an actor wich spawns at a certain point, goes through an AI routine, but i want this actor to litteraly keel over and die after X seconds, after wich i want the corpse to be removed after a while.
i have the routines down, and even the corpse cleanup code but the game refuses to use 'count' if it is going through other routines, or if it moves. i have a a cleanup state as follows
state cleanup
cstat 2
ifcount 330
{
cstat 514
}
ifcount 360
{
killit
}
ends
this kicks in after the actor has been shot and is dead on the floor ( aparently if movement is 0 it starts counting )
i trier adding something like
ifcount 1000 action GIDROPDEAD ( the actors falling dead animation )
strength 0
state GIDYING ( check the DYING state of the actor for ifhitweapon etc )
but because it keeps going through its routines and keeps moving it never reaches the count number, or, when it dies it keeps looping its dying animation.
#2614 Posted 20 November 2020 - 06:12 AM
another thing, how do i change the offset for the ejecting brass, right now it spawns in an incorrect location.
and since where on the topic of editing existing things, the muscle flashes used with the chaingun have three different offsets so they animate in front of each 'barrel' of the gun, i'd like to change this to one location for my new gun.
#2615 Posted 10 December 2020 - 09:33 AM
sqrt updatesector cansee setarray setvarvar (specifically pulling data from an array)
I'm trying to improve performance on my A* pathfinding, and I'm trying to identify which operations are very "expensive" that I should avoid or limit my usage of. I am calling these potentially thousands of times for every enemy on a given frame. There's not frequently frame drops and I am doing what I can to limit the frequency for these calls, but I'm curious which of these are the most heavy on CPU.
I am using "sqrt" for a simple Euclidian distance calculation, as dist / ldist takes actors and not X/Y positions:
defstate astardist { setvarvar ASTARDELTAX ASTARPOSX subvarvar ASTARDELTAX NODEPOSX abs ASTARDELTAX setvarvar ASTARDELTAY ASTARPOSY subvarvar ASTARDELTAY NODEPOSY abs ASTARDELTAY mulvarvar ASTARDELTAX ASTARDELTAX mulvarvar ASTARDELTAY ASTARDELTAY addvarvar ASTARDELTAX ASTARDELTAY sqrt ASTARDELTAX ASTARDIST } ends
Is there a cheaper method of getting the distance between 2 arbitrary points in space?
I'm using "updatesector" in conjunction with "cansee" to validate if 2 points in space are valid (IE not in the void):
// Ignore any point we don't have line of sight to getactor[THISACTOR].sectnum ASTARSECT updatesector ASTARPOSX ASTARPOSY ASTARSECT getsector[ASTARSECT].floorz ASTARPOSZ subvar ASTARPOSZ 6144 getactor[THISACTOR].sectnum NODESECT updatesector NODEPOSX NODEPOSY NODESECT getsector[NODESECT].floorz NODEPOSZ subvar NODEPOSZ 6144 cansee ASTARPOSX ASTARPOSY ASTARPOSZ ASTARSECT NODEPOSX NODEPOSY NODEPOSZ NODESECT ASTARCANSEE
I need to validate line-of-sight between 2 points, is there a less expensive method than this?
I don't have concise examples of using "setarray" and "setvarvar" for pulling values from an array, but suffice it to say that 2 separate arrays have their values checked and/or set potentially thousands of times during each loop. I know that this sucks, and I try to use a reference to the array value (rather than comparing it directly, IE ifvarvarl VAR ARRAY[INDEX]), but I am not sure I can improve things further. How much of a CPU hit could this be causing?
#2616 Posted 10 December 2020 - 11:27 AM
Reaper_Man, on 10 December 2020 - 09:33 AM, said:
Those calculations aren't super expensive but you are sure doing them a lot. It sounds like you are making your actors re-evauluate the path every single tic, which is excessive in my opinion. You can give each actor an interval and then have them do it every 10 tics or so. Stagger it so that only certain actors are doing it on certain tics (for example you could use the actors sprite ID to offset player_par and then mod 10 on the result to determine when to calcaulte). A lag of up to 10 tics on their reaction speed to changes in the maze won't be very noticeable but will reduce the calculations per second by nearly 90%
#2617 Posted 10 December 2020 - 12:10 PM
I would like to increase chaingun rate of fire by decreasing it's TOTALTIME. Like this:
gamevar WEAPON3_TOTALTIME 6 0
Original TOTALTIME was 12
After inserting this it wont display anymore chaingun firing tiles (the firing tiles contained cool muzzle blast) and it only "shakes" when shooting. Only main tile, which is nr 2537 appears but firing tiles 2538, 2539 and 2540 does not.
Any simple ideas how to fix this?
#2618 Posted 10 December 2020 - 12:30 PM
gamevar WEAPON3_FLAGS 73820 0
That will add the "fire every other" flag which I think should make it fire on almost every tic now (because it also has the fire on every third flag). You could also try removing the fire on every third flag and just have fire on every other which would give you flags 73804
But it's been a while since I messed with it so I could be mistaken about how these flags will affect things -- it's not always straightforward. These days I code the animation so I can change things more drastically.
#2619 Posted 10 December 2020 - 01:15 PM
Danukem, on 10 December 2020 - 11:27 AM, said:
No, the routine only runs basically once every animation cycle. It essentially looks like this:
ifactioncount 5 resetactioncount else ifactioncount 4 { state astar state facetarget action A_ASTAR move M_ASTAR geth getv break }
The reason there's so many calls is because of how the navigation nodes are built, which is basically just valid points on the grid. So if a hypothetical map takes up a 32768 x 32768 square, and if the nodes are built at 512 intervals, that's still 4096 potentially valid points. It's also possible that some points are evaluated multiple times by looking at a node's neighbors, so 4096 is actually the best case scenario because it assumes essentially a straight unobstructed line from corner to corner. Theoretically this count could go up to 28,672 in very bad scenarios, where nearly every node is re-evaluted for each of it's neighbors. (Of course the real-world execution will be much lower, outside of particularly bad maze-y designs, but I digress.)
Decreasing the navigation node density obviously improves things (512 to 768 for example), but then it becomes less useful in areas with tight navigation. It is possible to have areas of varying node density, but that is outside the scope of solving the core algorithm performance.
I've found a way to reduce the sqrt calls for checking distance, but they are still called up to 8 times per each neighbor, and the nature of the algorithm requires the rest of the culprits are all called if the distance check succeeds.
#2620 Posted 10 December 2020 - 01:51 PM
EDIT2: I'm guessing you are spawning in the nodes via code, which would explain why you have them so dense. If you hand place them in maps you can reduce the density a lot. That does mean that maps have to be hand edited to use the system but otherwise you need some very smart code for analyzing the map structure and placing nodes with the minimal density required in a particular area of a map.
This post has been edited by Danukem: 10 December 2020 - 02:11 PM
#2621 Posted 10 December 2020 - 02:36 PM
Danukem, on 10 December 2020 - 01:51 PM, said:
EDIT2: I'm guessing you are spawning in the nodes via code, which would explain why you have them so dense. If you hand place them in maps you can reduce the density a lot. That does mean that maps have to be hand edited to use the system but otherwise you need some very smart code for analyzing the map structure and placing nodes with the minimal density required in a particular area of a map.
To clarify, the nodes aren't actors, they are X and Y positions recorded in an array. The system actually doesn't spawn anything, which is why dist / ldist aren't useful and I have to use sqrt to do the distance calculation manually. This is also why manually placing waypoints isn't a viable solution. As I mentioned I do have some actors that can generate nodes in the X/Y arrays with a variable density in their vicinity, but that's more for things like doorways and other tight areas where mappers need granular control, rather than manually creating a navigation mesh for the entire map.
#2622 Posted 10 December 2020 - 03:03 PM
#2623 Posted 10 December 2020 - 07:00 PM
Danukem, on 10 December 2020 - 03:03 PM, said:
Right, I feel like I'm probably up against the wall with what I can do from CON. I can do away with the sqrt calls and use a more simplistic distance guesstimate, but it's the cansee calls I worry are the culprit. I don't know how updatesector works under the hood, and if I'm going overkill to validate the X/Y positions.
Thanks for your input as always, Dan!
#2624 Posted 12 December 2020 - 05:55 PM
// Search distance 1.5 times node graph size, cheap method of accounting for diagonal distance setvarvar ASTARMAXDIST NODEGRAPHSIZE mulvar ASTARMAXDIST 15 divvar ASTARMAXDIST 10 // Estimated square root distance for diagonal movement setvarvar ASTARSQRT NODEGRAPHSIZE mulvar ASTARSQRT 1414 divvar ASTARSQRT 1000 [ . . .] // ASTARPOSX/Y and NODEPOSX/Y are the X/Y position of the start and end points, respectively setvarvar ASTARDELTAX ASTARPOSX setvarvar ASTARDELTAY ASTARPOSY subvarvar ASTARDELTAX NODEPOSX abs ASTARDELTAX subvarvar ASTARDELTAY NODEPOSY abs ASTARDELTAY // If node within range ifvarvarl ASTARDELTAX ASTARMAXDIST ifvarvarl ASTARDELTAY ASTARMAXDIST { // Cheap square root approximation ifvare ASTARDELTAX 0 setvarvar ASTARDISTANCE NODEGRAPHSIZE else ifvare ASTARDELTAY 0 setvarvar ASTARDISTANCE NODEGRAPHSIZE else setvarvar ASTARDISTANCE ASTARSQRT
In this specific case, NODEGRAPHSIZE is 700 units, so ASTARMAXDIST becomes 1050 and ASTARSQRT becomes 989. Subtracting the start X or Y position from the ending X or Y position gives you the distance on one axis, and if both are less than the ASTARMAXDIST, then that means the distance is either going to be 700 or 989.
Anyway, my next question:
I am using several for loops, but often run into cases where I can/should exit out of them early. Is it possible to end a for loop early? The wiki makes it seem like you can't, and "break" doesn't seem to do anything either. Should I use while loops instead of for loops for cases where I know I will need to break them early? Is it faster to use a while loop in situations where I might break out of early, and for loops where I know I will need to iterate through every item?
This post has been edited by Reaper_Man: 12 December 2020 - 07:17 PM
#2625 Posted 13 December 2020 - 11:33 AM
#2626 Posted 13 December 2020 - 12:52 PM
jimbob, on 13 December 2020 - 11:33 AM, said:
Yes, the node graph is precalculated only once at map load, and I have a system to store and load the node graph on the disk and use that instead of generating it every map load, to speed things up further. But the node generation isn't a bottleneck, it's the various checks and iterations through the node graph. Consider a map that is 65536 units square, and the node graph density is 700 units, or 94 grid points per axis. That creates an array of only 8800 items. Searching through 8800 items is pretty fast. Searching for 8800 items 8799 separate times - or 77 million searches - is what is slow. Reducing that number, and reducing any other expensive calls within each of those iterations, is the goal.
The square root approximation method here works because every point is on a grid. Neighboring nodes are either straight or diagonal, and the distance is precalculated using the square root approximation for the diagonal points. Using the sqrt function isn't necessary because the distance is only ever going to be one length or the other.
I've been playing around with multiple search densities and I think I've come up with a system that balances speed/performance and search accuracy. It does 2 searches - a coarse grid search to find the nearest point, and then a fine local search to the nearest point. Think of this as searching for the path based on rooms and hallways, and then a second search to navigate the room you're in to get to the doorway. This cuts down the hypothetical 77 million searches down to only a few thousand.
#2627 Posted 13 December 2020 - 03:53 PM
Reaper_Man, on 12 December 2020 - 05:55 PM, said:
In situations where a while loop isn't appropriate I use a control variable that I set to a certain value, and only execute the code in the loop if the variable is not set to that value... Not quite the same as breaking the for loop but it works

eg
set variable -1 for yadda yadda { ifn variable 1 { // some code that sets variable to 1 } }
Danukem, on 10 December 2020 - 03:03 PM, said:
Yup, I had a similar situation a while ago and I spread the workload out by giving each actor a random chance to execute its code depending on a tic count (that I reset back to 0 after calculating). Like first 30 tics have a 10% chance of calculating, next 30 have a 20% chance etc until finally at 120 tics the calculation is forced.
You could also account for player distance/visibility.. An easy performance improvement is to restrict the amount of times calculations are run against/by things that the player can't see.
But I don't know how compatible all of that would be with the A* stuff.
This post has been edited by Sangman: 13 December 2020 - 04:01 PM
#2628 Posted 13 December 2020 - 06:02 PM
Sangman, on 13 December 2020 - 03:53 PM, said:

eg
Thanks. I was doing basically this as well. I decided to swap them out for while loops, since I wanted to remove as many loop iterations as possible when possible. Even if the for loop is doing nothing, it's still using a non-zero amount of CPU time. Each node only has 8 neighbors, so after all 8 neighbors have been found, I know I can safely exit the loop. Under normal, sane conditions this wouldn't matter, but when I'm trying to reduce hundreds of thousands of total iterations on a given game tic, every CPU cycle counts.
Sangman, on 13 December 2020 - 03:53 PM, said:
You could also account for player distance/visibility.. An easy performance improvement is to restrict the amount of times calculations are run against/by things that the player can't see.
But I don't know how compatible all of that would be with the A* stuff.
The A* system is locked to a single actor each tic, partially for performance reasons, but also because gamearrays are global only and actors can't share the path data that is stored in the arrays. Also as mentioned previously, they aren't trying to run A* on every single tic, they only do it once every "occasionally", and they store their path data locally as well to reduce the number of calls they need to make to the expensive A* functions.
There's also a lot of other AI considerations and A* is just one part of their overall behavior - an AI doesn't need to pathfind if they have direct line-of-sight with the player, for example. It's not a magic bullet, it's just one tool in the toolbox, and is meant to replace any instance of "seekplayer" to keep them from getting stuck in rooms or running into walls and sprites more than anything else.
So anyway, after working on this the past few days, I've been able to drastically improve the performance of the search. I've cut the number of iterations down from 500k to 11k, and the number of expensive operations down from 258k to just over 1000. There's still some minor things I want to try and improve, and of course there's lots of real-world testing that needs to be done, but I think I've found just about all of the shortcuts that can reasonably be found. Thank you everyone for the feedback and suggestions!
This post has been edited by Reaper_Man: 13 December 2020 - 06:15 PM
#2629 Posted 18 December 2020 - 11:17 AM
You can theoretically hack "seekplayer" to navigate towards any other actor, due to how it interacts with HoloDuke. From VM_AlterAng:
int const spriteAngle = vm.pSprite->ang; int const holoDukeSprite = vm.pPlayer->holoduke_on; // NOTE: looks like 'owner' is set to target sprite ID... vm.pSprite->owner = (holoDukeSprite >= 0 && cansee(sprite[holoDukeSprite].x, sprite[holoDukeSprite].y, sprite[holoDukeSprite].z, sprite[holoDukeSprite].sectnum, vm.pSprite->x, vm.pSprite->y, vm.pSprite->z, vm.pSprite->sectnum)) ? holoDukeSprite : vm.pPlayer->i; int const goalAng = (sprite[vm.pSprite->owner].picnum == APLAYER) ? getangle(vm.pActor->lastv.x - vm.pSprite->x, vm.pActor->lastv.y - vm.pSprite->y) : getangle(sprite[vm.pSprite->owner].x - vm.pSprite->x, sprite[vm.pSprite->owner].y - vm.pSprite->y);
This bit of code sets goalAng to the angle to face the target, where the target is either the player or the HoloDuke, specifically the actor stored on "holoduke_on" player member. Meaning you can do this:
// holoduke_on resets to -1 when holoduke_amount hits 0, so always keep the holoduke_amount at full setplayer[THISACTOR].holoduke_amount 2400 ifaction 0 { findnearsprite 100 32768 ACTOR_TARGET ifvare ACTOR_TARGET -1 killit setplayer[THISACTOR].holoduke_on ACTOR_TARGET action A_ACTOR move M_ACTOR seekplayer break }
This actor now is using "seekplayer" to seek out something other than the player.
Unfortunately, the catch is the "cansee" check, meaning that this only works if there's already line of sight between the actor calling this and the faked HoloDuke, rendering this mostly useless.
#2630 Posted 19 December 2020 - 07:16 PM
For example, I have a text element that I want to take up 70% of the screen width. In 4:3 this would be ~716 pixels, but widescreen this would be ~896 pixels. What am I looking for to run that math?
Or a more simple example, I have some HUD icons that I want to appear at the left or right edge of the screen. A value of 0 for x in screentext/rotatesprite will properly draw it at the edge of the screen in 4:3 aspect ratios, but in widescreen it's floating in the middle of the screen (where the edge of the 4:3 space would be).
What am I missing?
#2632 Posted 20 December 2020 - 02:12 PM
#2633 Posted 20 December 2020 - 02:48 PM
#2634 Posted 20 December 2020 - 03:18 PM
Reaper_Man, on 18 December 2020 - 11:17 AM, said:
You can theoretically hack "seekplayer" to navigate towards any other actor, due to how it interacts with HoloDuke. From VM_AlterAng:
int const spriteAngle = vm.pSprite->ang; int const holoDukeSprite = vm.pPlayer->holoduke_on; // NOTE: looks like 'owner' is set to target sprite ID... vm.pSprite->owner = (holoDukeSprite >= 0 && cansee(sprite[holoDukeSprite].x, sprite[holoDukeSprite].y, sprite[holoDukeSprite].z, sprite[holoDukeSprite].sectnum, vm.pSprite->x, vm.pSprite->y, vm.pSprite->z, vm.pSprite->sectnum)) ? holoDukeSprite : vm.pPlayer->i; int const goalAng = (sprite[vm.pSprite->owner].picnum == APLAYER) ? getangle(vm.pActor->lastv.x - vm.pSprite->x, vm.pActor->lastv.y - vm.pSprite->y) : getangle(sprite[vm.pSprite->owner].x - vm.pSprite->x, sprite[vm.pSprite->owner].y - vm.pSprite->y);
This bit of code sets goalAng to the angle to face the target, where the target is either the player or the HoloDuke, specifically the actor stored on "holoduke_on" player member. Meaning you can do this:
// holoduke_on resets to -1 when holoduke_amount hits 0, so always keep the holoduke_amount at full setplayer[THISACTOR].holoduke_amount 2400 ifaction 0 { findnearsprite 100 32768 ACTOR_TARGET ifvare ACTOR_TARGET -1 killit setplayer[THISACTOR].holoduke_on ACTOR_TARGET action A_ACTOR move M_ACTOR seekplayer break }
This actor now is using "seekplayer" to seek out something other than the player.
Unfortunately, the catch is the "cansee" check, meaning that this only works if there's already line of sight between the actor calling this and the faked HoloDuke, rendering this mostly useless.
lol that's cool - but it also sounds like something unintended that's definitely going to break in some future eduke32 version

#2635 Posted 20 December 2020 - 03:23 PM
Sangman, on 20 December 2020 - 03:18 PM, said:

Why wait, it's already pre-broken because seekplayer is garbage and the hack also depends on line of sight.
#2636 Posted 20 December 2020 - 06:25 PM
Danukem, on 20 December 2020 - 03:23 PM, said:
seekplayer is garbage. For anyone who has never looked in the source code, all it essentially does is change the angle of the actor by a random value between -128 and +128 degrees to the nearest direction of the target every few tics. That's it. Doom's target search was even more advanced than this, where enemies would walk until they hit a wall, and then trace the walls in the direction nearest the target. This is why Doom's enemies can manage to get themselves out of rooms that Duke's can't. I highly recommend a custom approach for target search when writing your own enemy AI, and use seekplayer only as a worst case fallback.
This post has been edited by Reaper_Man: 20 December 2020 - 06:27 PM
#2637 Posted 21 December 2020 - 12:31 AM
Danukem, on 20 December 2020 - 02:48 PM, said:
I have a machinegun, wich is stationary that shoots bursts if the player is close and in its line of sight, but this works on its own and looks silly as a result, so i want an enemy to 'operate' it meaning that the machinegun only fires if the enemy is close and alive. If it was the player i could use ifpdistl and ifpalive or something but afaik there is no such command for enemies.
I could make it into one large sprite and cactor enemy if the machinegun is destroyed but that would make the sprite very large and cause clipping issues
#2638 Posted 21 December 2020 - 12:37 AM
#2639 Posted 21 December 2020 - 12:50 AM
#2640 Posted 21 December 2020 - 06:35 AM

Make a small sector behind the machine gun and put a stayput version of the enemy in that sector. Then use the findnearactor feature in the gun actor so that if the enemy gets killed first it is no longer found and the gun stops shooting. And if the gun gets "killed" first have that cactor the enemy into the non-stayput version. The only problem is if you need the gun to fire in any direction and not just mostly forward. It would still work but the enemy won't follow the gun direction. Also you would have to tag this enemy somehow different from the rest so they don't all get affected. Maybe a different pal.
This post has been edited by Mark: 21 December 2020 - 06:50 AM