News | Forum | People | FAQ | Links | Search | Register | Log in
Coding Help
This is a counterpart to the "Mapping Help" thread. If you need help with QuakeC coding, or questions about how to do some engine modification, this is the place for you! We've got a few coders here on the forum and hopefully someone knows the answer.
First | Previous | Next | Last
WARNING: BAD CODE AHEAD 
THIS CODE IS PURE EVIL.

YOU MIGHT FIND IT USEFUL.

BUT DON'T GET FUNNY IDEAS.

So...I was working on the winner of the straw poll, which is the navigation entity stuff. Idea 3 did attract some attention so I might put up an article about naming in QC which would contain the "clever ideas" part of the event system I had in mind, so someone else with time on their hands would be free to do the footwork implementing it. Most of the effort would be in creating useful input and output on the various func_ entities, but at least it's a chance to exercise some creativity.

Anyway, I was trying to create some beautiful code involving callback functions (been reading too much ajax stuff recently) and managed to confuse the compiler enough for it to mistake a string field for a float. As you may or may not know string fields in QC are integer offsets into a big block of strings. So I came up with the following:

float INT_1 = 0.0000000000000000000000000000000000000000000014013;
.string tempstring;

void(.float stringfield) increment_string =
{
��float increment, stringvalue;
��increment = INT_1;
��if(self.stringfield < 0)
����increment = increment * -1;
��stringvalue = self.stringfield;
��do
��{
����stringvalue = stringvalue + increment;
����increment = increment * 2;
��}
��while(stringvalue == self.stringfield);
��
��self.stringfield = stringvalue;
}

void(void(.string floatfield) dispatch, .string fieldtype) strip_fieldtype =
{
��dispatch(fieldtype);
}


//then put the following code somewhere
{
��self.tempstring = self.model;
��while(self.tempstring != "")
��{
����strip_fieldtype (increment_string, tempstring);
����dprint(self.tempstring);
����dprint("\n");
����if(self.tempstring == ".bsp")
����{
������dprint("It's a bsp file!\n");
������break;
����}
��}
}

It's so hacky I don't even want to talk about why it works. 
Shamefaced 
In time maybe I'll see this as undoing a great blessing but: a correction:

float INT_1 = 0.00000 0000000000000000000 00000000000000 00000014013; but without the spaces. 
 
increment = INT_1 o.0

mike: i was referring to the command switch you use when launching quake, not the progs. the progs can't change the UI itself, unfortunately. if you put hipnotic progs in the id1 folder, you'll get hipnotic entities, but the UI will be default quake. 
Oh Btw 
on pathfinding...

if you're gonna implement a brand new system... should just go whole hog and do a star and just let the mapper plop down some nodes. that'd be insanely badass...

i'd totally do it, but i'm too dumb. ^_^ 
Necros 
I am not sure I understand. No, I am sure I don't understand.

I have two .bat files:

fitzquake085.exe -window -heapsize 40960 +gl_clear 1 -width 1024 -bpp 32

fitzquake085.exe -window -heapsize 40960 +gl_clear 1 -width 1024 -bpp 32 -game mynewprogs +skill 2 +map This_FMB_1_8c

The first runs the iD1 folder and this has no qconsole file and the same config file as the mynewprogs folder. The mynewprogs folder does not have a qconsole file either.

As far as I can see the only difference in the two folders is the progs.dat. Yet the first bat file game shows the runes lights as you pick them up but the other bat file game doesn't.

It does not actually affect the gameplay but as you need all four runes to create a certain effect, it would be useful for the player to able to see what he has already picked up instead of having to remember.

Any hints as to what could cause mynewprogs to apply different UI settings? 
Itemized 
So do you have .float items2 defined somewhere in your code? If it is defined anywhere then the serverflags info is not sent to the client - to save bandwidth that info is replaced by the items2 info instead. 
Fakepreach 
Funny you should say that. Yes, I do have .float items2 as I have the drole and vermis from Quoth.

//quoth -- items
.float items2; // bit flags for new items -- ran out of room on items...

I am not sure why they ran out room; perhaps I'll experiment with changing it to 'items' and see what breaks. 
Runes Can Exist In .items2 
As 32, 64, 128, and 256. If you're using .items2 for your own nefarious purposes, then leave those four bits for the runes. 
Note That 
sigil_touch() will need to be adjusted for this to work. The line

serverflags = serverflags | (self.spawnflags & 15);


should be followed by

other.items2 = other.items2 | ((self.spawnflags & 15) * 32);

This won't send the update to all players, but that's only relevant in coop. Also, impulse 13 won't update the hud properly, but a similar line will work in the function that handles it. 
Ohh 
thanks fakepreach and lardarse for clearing that up! i had no idea .items2 had any bearing on serverflags!

mike, since you're only using the drole, you could do a quick find/replace and just change 'items2' to something like 'drolevar'.
that way you can just leave the old serverflags variable alone. 
Thanks Necros, Fakepreach, Lardarse 
As I am not using any Quoth items, I can do away with the code relating to items2 without any detriment. It's just a couple of If/Then statements which don't apply anyway.

Onwards and upwards... 
 
don't forget to tone down the drole damage a lot. ^_^; 
Drole Damage... 
"Do you expect me to talk?"

"No, Mr Bond. I expect you to die!" 
Bbox Sizes 
so, going in the opposite direction, it's possible to make monsters with smaller bbox sizes, however you need to offset them by whatever amount smaller than 6 that you used.

now that you're down frowning in perplexity at that terrible sentence, this is what i mean:
say you made a skinny monster, '-6 -6 -24' to '6 6 40'
you need to setorigin(self, '-10 -10 0') the monster or, if you place it close to walls, it will get stuck (even though, when you turn r_showbboxes on, the bbox clearly is not in any wall).

but other than that, it seems to work fine, really lets monsters bunch up. would have worked good for the voreling had i known about it back then. 
Heh 
not only was that sentence awful, but there's a typo too.

by whatever amount smaller than 16 that you used.  
 
I did this with Floyd, didn't do the setorigin trick but instead made the in-editor box full shambler size so there's no risk of placing it in a wall. 
Fools Echo 
I'm still perplexed in making the Doomguy crouch.
Didn't want to start it over afraid of being a fool.

But the trick me was told to lower the bouncingbox so bullits would fly over it, and then raise the model again.
I don't understand. Then it would sink in the ground, but raising it would raise the bouncing box too? 
 
i'm not sure if we've covered this but when do touch functions get run when monsters are involved via movetogoal/walkmove?

are they run at the moment movetogoal is called?

ie:

void() someTouchFunction =
{
other.happy = 0
}

void() ai_run =
{
self.happy = 1
movetogoal(1)
if (self.happy)
cheer
}

so in this retarded example, while the ai_run function will make the monster happy, it will run a touch function right after movetogoal so that the if statement will fail?

i've been observing some screwed up behaviour and i'm trying to track it down... 
Touchy Subject 
Yeah, the touch functions are respected there - only pitfall to bear in mind is that if the move fails because the monster is blocked, then they don't move at all and so the monster doesn't touch.

So that doesn't really help you track down your problem, and it sounds like you've got a pretty solid minimal test there. So I'm just gonna take a peek at somet-
OH MY!

Oh dear.

Very long standing bug in quake coming right up...

PF_walkmove, the non-navigational monster moving code, has the following code in it, which I remember noting down for possible use with that JS quake engine idea

// save program state, because SV_movestep may call other progs
oldf = pr_xfunction;
oldself = pr_global_struct->self;

G_FLOAT(OFS_RETURN) = SV_movestep(ent, move, true);

// restore program state
pr_xfunction = oldf;
pr_global_struct->self = oldself;


Firstly a quick note, "PF" in front of a function in the quake source means that it's a "progs function" - a light wrapper around some C code called as a QC builtin. The snippit I quoted then calls SV_movestep - the real heavyweight movement function which other parts of the engine also use.

Around that call we have some clearly documented code which stores the current QC execution state and restores it after the call. This firstly tells us that touch functions are a possibility they have programmed for, and secondly eliminates a possible source of the buggy behaviour - that after calling the touch control never returns to the QC.

Except your QC doesn't use walkmove, it uses movetogoal. I went looking for PF_movetogoal and was surprised to find it missing. It turns out that the QC builtin in this case directly calls SV_movetogoal instead, and I'm sure some of you are ahead of the puchline here - it doesn't save the QC state.

I haven't gone away to do follow up QC tests, but worst case I can imagine is that none of the remaining QC in your function ever gets run if a touch function is encountered, and best case is that it's just self that gets obliterated. In the former case you are basically stuck with using walkmove instead. In the latter you can put the traditional

local entity oself;
oself = self;
movetogoal(1)
self = oself;

But yeah, genuine nasty bug there I think. Let me know whether it is that worst case scenario situation or what, I'd like to know... 
 
ohhh ok, yes! that explains some OTHER whacked out behaviour i've been seeing!

i've changed movetogoal into a wrapper that (among other things) does a movetogoal(1) and then calls x number of walkmove(directionItMoved, 1) to complete the amount of movement requested.
this allows a monster to continue to move forward, even if it's full movement would have failed allowing it to get through thin or cluttered areas better.

i was seeing some weird stuff where a monster would touch a trigger and then not move (it was moving by 1, but not completing the walkmoves).

i guess it never mattered before because movetogoal was solely called at the very end of a qc function.

anyway, i've added in your suggested change and will test it today. :) 
Binary Chop 
If you like, there's a trick you can apply there to only call ~log(x) steps in the worst case rather than x for moving x distance.

The trick is try to walkmove(x) first - being optimistic!

Check the return value for failure, and we can finish if we succeeded.

If we failed, replace x by with x * 0.5. We then try to walkmove(x) with the new x.

We keep repeating the above step(in italics) until x is smaller than 1.

Note that after the first iteration we don't need to check for success.

Worked example:
Suppose we want to walk forward 32 units but only have space to move 21 units.

First time through walkmove fails
x=32, moved=0

Second time through we succeed
x=16, moved=16

Third time round we fail as we are 16 forward and another 8 would take us past 21
x=8, moved=16

Fourth round we succeed
x=4, moved=20

Fifth fails
x=2, moved=20

Sixth succeeds
x=1, moved=21

Since x has reached 1 we terminate.

I'm making the numbers quite friendly by choosing x as a power of 2. It also lets me gloss over the edge cases of when to reject x and stuff. Nonetheless the method is sound and a fairly simple loop. Probably the hardest idea would be proving that it always works, I've always been fond of Proof by Single Worked Example though... 
Yeah 
i've been meaning to do that. :)

i wasn't sure if there was a point where doing it linear was faster or if it was always slower and i just haven't been in the mood to try to figure it out. ^_^;

it looks like you're saying it's always faster?
also, while we're talking about walkmove and such...
is it ok to call a walkmove/movetogoal larger than bbox size?
ie: walkmove(yaw, 256) as opposed to looping walkmove (16) 16 times.
not sure if this messes up collision if the bbox is displaced by huge amounts... 
Empirical Answers 
Yeah, the binary chop should always reduce or equal the number of calls to walkmove in the linear algorithm. The nice thing about it is getting twice as high precision only costs you 1 more call to walkmove, although I doubt anyone needs monsters THAT much more finely tuned than 1 unit.

I tried to pick through the engine code to decide if you could get away with long distance walkmoves. But it's the densest part of the engine code and although my suspicion was that collisions would get skipped I wouldn't be able to answer with confidence.

So I bashed away through the latter half of Lewis and made a test mod. The emperical conclusion: no.

You can make a monster walkmove 256 units and completely skip a 64 unit trigger. So yes, limit yourself to width of a bbox per iteration. You can be assured that you won't ever skip through walls of the game world. BSP entities don't count though... 
 
You can be assured that you won't ever skip through walls of the game world.

this is more what i was asking. mainly the use for super long walkmoves would be to check for charging/leaping abilities like fiends instead of just checking if they can see the player.

a walkmove check of even just 16 or so units will stop a fiend from doing that psycho leap 'bug' around doorways.
otoh, it will also cause it to fail more often as a jump that would hit a wall yet allow it to bounce towards the player (and hit him) would fail unlike with just plain visibility checks.

you could possibly make a 'robust' walkmove that, when failing to move, would tweak angles by a small amount and try again.

hmmm... 
Rollback 
I was about to warn you about using walkmove checks(where you want to reserve the right to rollback the movement and do something else) too freely. What if you walkmove into a rocket during a 'check'. Luckily you are largely protected from this kind of thing. This is because the only thing that monsters will collide with during a walkmove or movetogoal is SOLID_TRIGGER entities.

So really all you need is to watch your triggers which respond to monsters when you're doing that kind of thing. In standard quake that's only really telefrag and trigger_hurt - which you could guard against with a quick change of takedamage.

If you really get into that kind of thing you can go down the route of having a 'proxy' entity instead. I was trying to think about what you'd need to do to get the proxy perfect. The think you'd need to get right is .owner, which would take 3 transformations:

1)Set the owner of monster's owner to the proxy.
2)Set the owner of proxy to the the monster.
3)Set the owner of everything else in the world currently owned by monster to proxy.

You also need to store enough information to reverse all these changes.

This is of course absurd levels of effort. Just set the proxy's owner to the monster, and accept that occasionally it'll be blocked by an entity that owner settings would have let the monster walk through. Never realistically a problem. 
First | Previous | Next | Last
You must be logged in to post in this thread.
Website copyright © 2002-2024 John Fitzgibbons. All posts are copyright their respective authors.