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
 
Wait, how do shub and chthon work? They have large (stationery) bboxes and players seem to collide against them correctly. 
 
yeah, the collision of other bboxes is fine.
it's the collision against the world that is messed up.

and that's the big problem:
movetogoal will function correctly vs other bboxes if you set the bbox correctly. 256x256x128 or whatever.

movetogoal will function incorrectly vs world when bbox is set to 256x256x128.

if you set bbox to standard hull2 size before movetogoal and reset to 256x256x128 after, monster will move inside player.

what i've done so far is left the bbox at 256x256x128 and simply 'offset' the func_clipModel so that sides that the mins hits are smaller than sides that the maxs hits.
kind of hard to explain, when i figure everything out, i'll probably make a blog post about it with pictures to explain it properly.

also, another interesting (and annoying) side effect: because of the way the standard ai_run code is sandwiched between func_clipModel toggles such that the clipModel is active when ai_run is called, things like the visible() function fail if the player is inside the clipModel since visible uses a traceline to determine if the monster can see it (and since the clipModel is solid during that period, the trace hits the clipModel and determines the player is not visible). 
Append 
another way to look at it is this:

for the purpose of bbox vs world, only the first hull2 coordinates starting at the mins are solid.

that is to say:
if the bbox was mins: -128 -128 -24, maxs: 128 128 64

world collision is done from -128 -128 -24 to -64 -64 64 (mins + VEC_HULL2_SIZE)
because VEC_HULL2_SIZE = 64 64 88 
Oh I See... 
if you set the bbox down to standard hull2 size, it's collision against other entities is wrong. If you leave the bbox alone, its collision box is offset from the correct location.

Two ideas:

1. before moving the oversized entity, make ALL other solid entities (or at least all entities within a findradius) larger by XYZ amount, to compensate.

2. have two entities, one oversize and one normal size, and move them both. If either one is blocked, set both entities to the location of the blocked entity.

Not sure if either of these are completely workable. 
 
1. could work, except it's just (potentially) a lot of entities to enlarge.

2. this is better, but the problem is that if one is blocked, and we reset the position, nothing happens for that frame and likely movetogoal will try the same thing next frame.

currently what i've done is use bboxes on all the func_clipModels. this means each brush needs to be a seperate entity and we can't have sloped/angled faces anymore (since everything is just a box now).
it works and doesn't seem too slow, but we loose a bit of brush flexibility since we can't have any angles any more. of course, you're really just blocking out the area, so this could actually work. i'll leave it this way for now and test it out for now. 
I'm Not Compensating. Really! 
http://necros.quaddicted.com/temp/dragon.jpg

the collision stuff has been working well without any further problems so far.

the really nice thing about it is that for monstrously large monsters like the dragon in that shot where you don't necessarily want the bbox to completely cover the entire model, all you have to do is expand the clipModel entities out further from the actual walls. 
 
Is that the DOE dragon? 
Yeah 
i rerigged him and animated him for actual combat and not just flying around path corners. 
Accelerating Back 
So I've just spent about a month without internet at home, shame I missed all that stuff about large bboxes, because that's a cool idea. If you're doing a landbound monster with a tall box, then you can create maps which can be bbox-blocked with greater ease. Low walls rarely need to be boxed in this case, just obstructions which are above the head height of regular sized entities but low enough for "the boss" to collide with.

Anyway, that's a digression. In my time away, I had lots of time to get on and finish projects. A fair few of them were quake related, and I'm posting the first one now. It's the tutorial I mentioned a dozen posts up about accelerating MOVETYPE_PUSH objects. Since it involves some formulae which I've done up in LaTeX and a few graphs, I've made a web page for it rather than just copy-paste it straight onto func.

Accelerating pushers
Also means I can fix typos and other problems, so post them here! 
 
i haven't really read your above post yet as accelerating movers isn't a pressing issue anymore, but i just wanted to mention: you should compile all your coding tips you've made in this thread and put them up on a site somewhere. some of them are quite useful and others are downright golden. 
No Longer Touching 
I have created a 'trigger' entity. It covers a large portion of the map and instigates certain continuous but randomly timed actions whilst the player is inside the trigger's area. Let's call it an area_trigger.

The events are called by using the trigger's touch function, as in self.touch=do_these_things, so that when the player leaves the area, do_these_things no longer gets called. If he re-enters, the events start again. So far, so good.

I now want to 'enhance' this effect so that when the player leaves the area, a separate single event takes place, and this event takes place each and any time he leaves the area, which could be one or more times throughout the game. This event must not take place at any other time.

Is there a way to read when the player stops 'touching' the trigger so that I can call this exit event. I maybe could set up multiple triggers around the area_trigger and have them switched on by the area_trigger so that the call to the exit event is operated on a one-way basis but I am hoping there is an easier way.

Any views from the coding gurus? 
Not Easily 
A better solution is to have triggers at the entrances to the area. Two in fact, separated a little. The inside one calls the enter function, the outside one calls the exit function. The functions are coded so that they only actually do something on a state change. 
 
actually, it is easy.

on your trigger_area touch function:

self.nextthink = time + 0.1;
self.think = DO_THIS_WHEN_PLAYER_LEAVES;

since touch is called every frame, nextthink will always be set higher than time, so .think will never be called. the minute the player steps out though, and .touch isn't called anymore, nextthink will expire in the next 0.1 seconds and run the .think function.

also, since i believe .touch functions are run first (before .think functions), even if you were getting less than 10 frames per second (such that the next .touch might be AFTER 0.1 seconds) the touch will be run first, thereby resetting nextthink anyway. 
Append 
first: i forgot to mention in your .touch function, remember to store 'other' in self.enemy or somewhere so that your DO_THIS_WHEN_PLAYER_LEAVES function will know how to have as the activator (just add activator = self.enemy;)


if your trigger_area already needs to have a nextthink and think set for whatever reason, just spawn in a relay entity and do the trick on that entity instead:

if (self.owner == world)
{
self.owner = spawn();
self.owner.owner = self;
}

self.owner.nextthink = time + 0.1;
self.owner.think = DO_THIS_WHEN_PLAYER_LEAVES;


then:

void() DO_THIS_WHEN_PLAYER_LEAVES =
{
...........some code here............

self.owner = world; //break link with the relay entity and the trigger

self.nextthink = time + 0.1;
self.think = SUB_Remove; //delay remove
};


we set up .owner so we have an easy way to get access to the trigger_area's .enemy field, target and whatever else might be needed and we manually break the .owner connection when removing the entity because there's like a 2 second delay before entities are removed in quake. 
Crap 
void() DO_THIS_WHEN_PLAYER_LEAVES =
{
...........some code here............

self.owner.owner = world; //break link with the relay entity and the trigger

self.nextthink = time + 0.1;
self.think = SUB_Remove; //delay remove
};


otherwise your only erasing self's owner (the relay entity) and not the .owner belonging to the trigger. sorry. :P 
Not Sure If That Works 
Because when you're in a trigger, I don't think the touch function is called every frame. 
Two Stage 
If that is a problem(it certainly would be if you need to detect monsters)can use force_retouch to help you out there. The simplest way would be to set force_retouch = 2 in the touch function as soon as you've touched the trigger. This would end up polling the heck out of all the triggers in your map while someone touched the middle one, but it would fix that problem.

I think the above is the only way to make the trigger responsive within a single frame (assuming you shrink the nextthink time lots). If you're willing to have your trigger less responsive (a minimum of two frames, but the values we pick here will be 0.2 seconds) then we can end up only using force_retouch once in 0.2 seconds.

We need the trigger_detector to have three states:

STATE_EMPTY: no player inside
STATE_READY: detected a player recently
STATE_POLLING: checking if there is still a player

If a player comes into an empty trigger we go from STATE_EMPTY to STATE_READY and wait for 0.1 seconds. After that time expires we go into STATE_POLLING for 0.1 seconds. If a player touches the trigger within that 0.1 seconds, we go back to STATE_READY, otherwise we go to STATE_EMPTY (and trigger the leaving event).

We don't actually need to explicitly track these states, they are just to understand what is going on. We have a think function called trigger_detect_startpolling along the lines of

self.nextthink = time + 0.1;
self.think = trigger_detect_fire;
force_retouch = 2;

When our .think is trigger_detect_startpolling, we are in STATE_READY. When our think is trigger_detect_fire we are in STATE_POLLING. When we don't have a nextthink in the future, we are in STATE_EMPTY.

Finally to hook all of this up, we need the touch function to set

self.nextthink = time + 0.1;
self.think = trigger_detect_startpolling;

which both moves us from STATE_EMPTY to STATE_READY when the player first touches, and back from STATE_POLLING to STATE_READY when the player retouches.


On another topic, force_retouch has an important effect on touch/think order which I hadn't considered before. When set, players will touch things they are in contact with before their think functions run (and then possibly touch things AGAIN after physics has run). So anyone who was intending to exploit the order that functions run had better be careful.

Also, I'm gonna go see if things really can run touches multiple times in a single frame. If that is the case, then it really will be important to make touch functions idempotent. This is a wonderful term from mathematics for a function which doesn't do anything else when you keep applying it to it's output. For example rint(x) is idempotent - once you get a whole number out, applying rint to that whole number just gives you the same whole number.

I'm using idempotent in a slightly weird sense here, the idea being that one of the parameters to our touch function is the value of "time", the frame that we are in. Most of the original touch functions have if(self.nextthink > time) type guards in place to achieve this, but it's something important to think about if you're writing a trigger which can be touched every frame - does it matter if you trigger it many times a frame? 
 
i don't know why it has to be so complicated, preach... it works fine the way i said. :P no need for force_retouch or anything. 
Monsterous 
It's needed in the case of non-player entities which don't link to the world unless they move. If you were trying to detect a monster, you would need the force_retouch. It is more than what mike asked for though, the simple suffices there... 
Oh Right 
i missed that you were checking for monsters as well. 
Necros & Preach 
Thanks. 
Findradius Vs Find Vs Nextent 
are there any differences in how these three work?

ie: is findradius really just doing
nextent(e)
if (distance of e < dist), add to .chain
or is it faster?

for example, if i did a findradius(org, 64) where i'm checking only a small radius, is it faster than if i findradius(org, 1024) or is it the same speed?

and if smaller radii are faster than larger ones, at what point does it become better to use nextent rather than a large findradius?
i guess that kind of thing would also depend on the total # of entities as well... 
Finding 
ie: is findradius really just doing
nextent(e)
if (distance of e < dist), add to .chain
or is it faster?

It turns out that it does something extra I'd never known about - it skips any entity that's SOLID_NOT(*). Other than that, the algorithm is as you describe, but because it's written in c skipping to the next entity is a single instruction to increment a pointer, etc. So it does run a lot faster than the QC equivalent, but it doesn't do any culling of the entity list based on the bsp tree or anything fancy.

for example, if i did a findradius(org, 64) where i'm checking only a small radius, is it faster than if i findradius(org, 1024) or is it the same speed?

They are the same speed if they contain the same number of entities. Otherwise the cost of adding more things to the chain is incurred, although that's fairly light compared to the rest of the loop.

(*) Also worth noting: the distance is measured to
origin + (mins + max) * 0.5;
not just the origin as you might guess. 
 
It turns out that it does something extra I'd never known about - it skips any entity that's SOLID_NOT

haha yeah, i figured that out the hard way. drove me insane for a while. -_-

Also worth noting: the distance is measured to
origin + (mins + max) * 0.5;
not just the origin as you might guess.


didn't know about this bit though. wouldn't have much of an impact unless you had some kind of weird offset bbox but good to know regardless. it does make getting precise findradius distances annoying though. if i did a findradius from one monster origin looking for other monsters, the find would have been completely accurate if it was going from origins and not bbox centers. oh well. :S 
How Difficult 
is it to find out what map a given savegame file is for?

Can someone post code / relevant savegame spec? :P 
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.