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
 
Some time recently, either in this thread or modeling help (I think), someone (probably preach) talked about ways to make an entity out of more than one alias model, and have them move in sync via quakec trickery.

It's not a hard problem at all - unless the entity is an AI being moved around by the navigation code. then movetogoal might not succeed for all parts of the collective entity because some of it will collide with something and the rest won't, and you'll get a discontinuity. Constant setorigin()s are laborious and not fully reliable, especially in the above case, and polling/setting velocity will lead to weird one-think lags after direction changes. Maintaining yaw is just as bad.

It might actually have been some CSQC or FTEQW extension? Nobody documents anything over there so it's impossible to just look it up.

My parameters are that I'm okay with one entity being the collision master and all the rest being nonsolid, but if the 'master' is still being driven by ai_forward()s and all that the 'slave' has to react accordingly. 
 
A better way to ask (with some forethought put into it):

movetogoal() in quakeC is called during the think cycle, but it's evaluated during physics/touching, correct? So if the master does an ai_forward(20) at an enemy 10 units away, physics will move it 10 units (until the boxes touch). If the slave is thinking in tandem (and it should be if it's also trying to match frames) then it will already have thunk by the time physics gets around to the master's SV_MoveToGoal(). It can't see that little bit into the future physics cycle to know how far its master is going to move. So, is it impossible? Or have I got this wrong? 
 
no, the 'physics' of the ai moves are done as they are called. this is how you can use walkmove and get a return value on if the walkmove was successful or not.

this means you can recursively or iteratively solve walkmoves in a single frame and you don't need to rely on child entities to think for themselves. you can have the master run all the thinking on its children so make sure everything is synchronized. 
 
btw: movetogoal does not return anything, but you can just check if the origin of the entity after you called the method is the same as before. if they are, then it failed... 
 
How do movetogoal and walkmove interact with model lerping? 
 
only seems to matter where the monster is at the end of the frame, not matter on how many times any of those methods were called. 
Setorigin Stuff 
If you do decide to go down the setorigin route, I recommend use the EndFrame function from

https://tomeofpreach.wordpress.com/2014/05/24/easily-fixing-the-endframe-function/

This ensures no QC which runs later in the frame can desync your models. 
 
So my approach should be
- give each model the same origin for sanity's sake
- make sure the attachment thinks second, presumably by having the master itself spawn() the attachment so it's a higher numbered entity
- have the master be aware of how far it actually moved each think and notify its slave(s)
- maintain the same ideal_yaw and yawspeed on all parts
- do a final setorigin() in an end of frame function if necessary (wouldn't having the master setorigin() the slaves at the start of its own think have the same outcome?)

didn't ijed do something like this in RRP? with destroyable gun arms or something? 
Nope 
Those were just more animation frames with the arms hidden inside the body and a value keeping track of the destruction state. 
 
I've only used multiple entities for a single enemy when I wanted them to move independently - to help with navigation, provide effects of one type or another or not move locked to the master monster's origin.

FTEQCC allows you to have multiple meshes used by a single monster if needs be, removing that blocker.

What are you aiming for? 
 
Mostly saving vert precision on the important core of a model without sacrificing any to its giant tentacles.

FTEQCC allows you to have multiple meshes used by a single monster if needs be

Yeah, how? It's not documented anywhere. 
 
FTEQCC doesn't really provide anything that couldn't otherwise be achieved with perverse unreadable QC (like arrays).

the qcc really doesn't have anything to do with multiple meshes - anything along those lines are engine extensions rather than qcc ones.

in order for an entity's interpolation to match another, it should (depending on the client in question) update its origin at the same times, its movetype, its angles, its .frame, and its onground flag. If any of these differ at the end of a frame (this includes independant player physics partial frames) then a client may see a desync.
the client may also see a desync caused by packet overflows (or equivelent) also (although often that would just manifest as completely invisible ents).
MOVETYPE_FOLLOW(if the follower has a higher entity number which is fine if you manually spawn the second entity within the first entity's own spawn function), customizeentityforclient, setattachment, or csqc can all be used to easily keep a 'sub' entity tracking a parent. you could also give the follower a nextthink=time for a similar effect and limitation as movetype_follow. or you can use EndFrame (either the extension or preach's hack).
In QuakeWorld, other players are predicted. There is no way to guarentee that an entity can exactly follow a player other than with setattachment or csqc with quakeworld protocols (non-players are not affected by this limitation, and in fact vanilla qw has no non-player interpolation at all, but that's just vanilla being lame).

setattachment is the easiest and most reliable, but requires dp or fteqw in order to work (on the plus side, it can also work with md3/iqm tags - use an empty tagname for formats with no tags/bones and it'll attach to the entity itself). It was made for this sort of thing.
Note that the subentity's origin is typically set to '0 0 0', which can be a little weird. 
Well 
To use multiple different meshes from the same creature, back in RMQ/Schism, I added this:

$framevalue 0

After each listing of animations as $frame for each model.

They still need precaching of course and setting to self.model = thenewone when you want to swap things about.

Here's the Ogre Qc from back then as reference:
http://pastebin.com/JiDMsbSL 
As To 
The whys and wherefores, I bow to Spike's superior knowledge - and Gb who figured out how to use this back in the day.

If memory serves, it was that other compilers would explode when trying to parse the additional animations rather than spit out a usable progs.dat 
Bump Around 
I'm forced to confront an issue I've noticed out of the corner of my eye a lot - monsters whose yaw angles will seem to spaz back and forth while meleeing, specifically on melee frames that use ai_charge().

ai_charge leads to SV_MoveToGoal() in sv_move.c, which eventually winds up calling SV_NewChaseDir() under certain conditions. SV_NewChaseDir() rounds ideal_yaw to the nearest 45, tries some navigation stuff, and if none of it is successful, overwrites the entity's ideal_yaw with the rounded value. I can't really tell why.

The outcome of this is that if a monster is in melee frames (when it's really close to the player) which call ai_charge() (which they nearly all do, since most monsters' melee attacks have some forward movement to keep the player close), every couple of frames its yaw is pulled hard to one side or the other, then immediately lerps back as the monster yaws back to its enemy. I've verified this is the case with shotgun bprint debugging, and I've verified it happens in id progs 1.06 too.

What can I do? I'd rather not disable a monster's ability to yaw by taking out the ai_charge()s. Can anyone who understands SV_MoveToGoal() better than I do suggest anything brilliant? 
Dull Idea 
To me it seems that the original intention there is to just have the monster stay close, so why not stick with that.

The method of aiming the monster at the player and then pushing it forward makes it wobble, as you say, so why not add a new ai_propel(); which does it the opposite way round - move the monster along the shortest point between it and the player and then give it the ideal yaw.

Potentially this could make it move sideways if something weird happens but that's nothing you couldn't account for by testing the facing beforehand and discounting the propel if the yaw is too wacky - possibly reverting to good 'ol ai_charge(); if that's the case.

There are probably better, coder ideas to be had, but this could be an interesting thing to mess about with. 
 
That doesn't solve the problem. c changes the yaw to something and the model renders and you see it twerk left or right for a few render frames, regardless of what order the qc moves and yaws before or after that happens. 
Lazy Idea 
Adding

self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);

straight after movetogoal (d); in ai_charge is certainly a straightforward way to eliminate the wobble. What you might ask is how much difference it makes elsewhere. I mean, we're overwriting the change SV_NewChaseDir makes, and while we've identified a case where it makes an irritating change, does it sometimes make a beneficial change instead? You might imagine that on occasions when it finds a direction the monster can walk in, we may want to retain that information.

I'd argue that no, almost every time it makes no difference besides the visual glitch. My reason is that the preceding line in ai_charge is ai_face ();. This actually resets ideal_yaw in the same way our added line of code does (I straight copy-pasted it from ai_face). So any time an ai_charge is followed by another ai_charge next frame*, the information from SV_NewChaseDir is discarded without being used. Keeping it will make little to no difference.

Ah, if only they could all be one-liners...

*Here by "next frame" I mean next monster animation frame, not engine frame. 
Duh 
I guess that's kind of what ijed was getting at too. Just reset it immediately. i'm tired. :p

Will test soon. 
 
...or use walkmove? i never use the ai_charge method for exactly the reason you stated. 
 
the ijed/preach method actually makes it WORSE. the monster spins like a ballerina.

the necros method works like a charm. walkmove is all or nothing, though, so this
if ( !walkmove(self.ideal_yaw, d) )
walkmove(self.ideal_yaw, d * 0.5);

serves as a good functional compromise.

ai_charge_side uses walkmove, and now that I understand all this I really don't know why ai_charge didn't already.

Thanks, guys! 
 

do
{
����if (!walkmove_builtin(yaw, stepDist))
��������stepDist = stepDist * 0.5;
����else
��������stepCounter = stepCounter + stepDist;
} while (stepCounter < step && stepDist > 1 && stepDist < (step - stepCounter));


Preach clued me in on this gem. This will always walk as much as possible but due to some mathyness, will only every iterate a maximum of 6(?) times. 
 
actually, come to think of it, i wrapped movetogoal in a method that essentially replaced movetogoal with walkmove. call movetogoal with distance of 1, reset position back to where it was before but record the direction it moved in, then use walkmove to do the actual move with the proper distance.
this solves the problem with monsters not willingly walking inside other bboxes, even if they are non-solid (and thereby not generating touch events). 
Piroette 
I can't understand how your monster spins so much after the change I suggested, certainly when I played around with the ogre it didn't cause such a difference. I will admit that it doesn't fix the issue - in my testing I wasn't reliably replicating the glitch. Luring the ogre to a wall on a corner makes it occur regularly, both with and without the change.

However, I think I may understand better why it's happening. I don't think it's due to the failure mode of SV_NewChaseDir you spotted, because that only changes ideal_yaw, and ideal_yaw is reset before the monster is turned towards it next (barring any functions above ai_charge calling ChangeYaw).

Instead, what if all the functions are behaving correctly in isolation, but the result is strange. Imagine that the monster is essentially trapped near the player, and movetogoal succeeds in moving the monster only occasionally. On those occasions, it's likely to move the monster away from the obstructing player. Then next frame there is space for the the monster to move in the correct direction and it returns to where it was. The net result is that the monster shuffled sideways for one frame.

But tragically, this actually could cause the one frame jump in angles! The reason is that ai_face calculates the angle for the monster to face based on the relative origins of the pair. So a one frame shift in origin has a corresponding one frame shift in facing. It might be a little hard to reliably establish this is how it works though.

Replacing movetogoal with walkmove would certainly eliminate this issue, as the monster would only ever succeed in changing origin if they had moved straight towards the player, but it does mean that you're losing the ability for a monster to slip round a barely obstructing obstacle while charging. It's not about how far the monster is moving (both functions are all or nothing), it's about which directions they try.

If this is the right explanation of the glitch, then I think it boils down to: monster angle changes should result from the player moving round the monster, but not from the monster moving round the player (because the latter tends to be back and forth and glitchy). So something like:

���self.ideal_yaw = vectoyaw(self.enemy.origin - self.origin);
���movetogoal (d); // done in C code...
���if(approx_equal(vectoyaw(self.enemy.origin - self.origin), self.ideal_yaw))
������ai_face ();

Prior to movetogoal, you only change the direction you'd like to walk , not your actual angles. Then after you move, you check to see if the angle is still more or less the same. If you moved straight towards the player or not at all, then it will be (in the former case I suspect it may vary slightly so this comparison should be approximate). If you shuffled sideways then it differs, and this frame you refuse to change angles. 
 
but it does mean that you're losing the ability for a monster to slip round a barely obstructing obstacle while charging

That will rarely happen anyway though since it would need several consecutive frames for it to successfully walk around even the smallest of obstacles. 
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.