my bulk commits didn't include include file additions. these are optional.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4256 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2013-03-17 12:09:59 +00:00
parent ab489b1680
commit 169f7edcf2
3 changed files with 607 additions and 0 deletions

104
specs/chunkeddownloads.txt Normal file
View File

@ -0,0 +1,104 @@
Establishment:
Useful defines:
#define PROTOCOL_VERSION_FTE (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24))
#define PEXT_CHUNKEDDOWNLOADS 0x20000000
QW: Extensions are established during the handshake.
CL: "\xff\xff\xff\xffgetchallenge\n"
SV: "\xff\xff\xff\xffc$challenge\0<binary key value pairs>"
CL: "\xff\xff\xff\xffconnect $ver $qport $challenge \"$userinfo\"\n%#x %#x\n", key1, value1
SV: "\xff\xff\xff\xffj\n"
See the 'both' part for additional info.
This handshake will be stripped by proxies. This is by design. It prevents qizmo and similar proxies from erroring on extensions.
NQ: This extension is not currently supported.
it can be implemented, but darkplaces' stream downloads may be more standard for the nq protocol.
Both:
The server will update the svc_serverinfo message to have this ordering:
byte svcqw_serverdata / svcnq_serverinfo
optional long PROTOCOL_VERSION_FTE
optional long flags=PEXT_CHUNKEDDOWNLOADS|etc
long PROTOCOL_VERSION
...
The protocol version should be parsed in a loop, stopping on unrecognised or non-extension-bits protocols.
The server must mask the extension bits with those that it supports, to avoid future extensions appearing active on servers that don't support them.
If the extension bits are not echoed from the server in the list, those extensions WILL NOT be used.
This mechanism allows the server to mask the client's extensions with the extensionbits that it supports, and to tell the client which protocol bits will actually be used for each map.
The server may kick the client if the client does not support extensions required by the server, just as a server sending an unsupported protocol version would result in a client getting kicked, but at least the server has the chance to run without.
Modified SVCs:
svc_download: completely rewritten, but uses the old svc value.
Takes three forms.
Startup/failure:
Sent reliably.
unsigned long chunknum < 0
long totalsize
string nameonserver
totalsize < 0 denotes an error.
-1 = file not found
-2 = server rejected download based upon name. file may or may not exist.
-3 = generic error, active download aborted.
nameonserver should match the filename the client requested. It is present to potentially allow replacement extensions, but any such replacements are subject to other extensions.
The client must ensure that the server does not have a way to write out files such as *.dll or *.so
Data:
Sent unreliably.
unsigned long chunknum
byte data[1024]
the start offset of the data is chunknum*1024
with the final chunk, the data sent is padded to 1024 bytes. the client must write only as many bytes as are in the file.
OOB Data:
Sent unreliably in its own udp packet.
string"\xff\xff\xff\xffn\\chunk"
long filenum
byte svc_download
unsigned long chunknum
byte data[1024]
Much like the in-band data chunk.
Remember to ensure that it came from the right source address.
The filenum value is a copy of the filenum argument of the nextdl client command.
Modified client stringcmds:
download "$filename"
Begins download.
Triggers a svc_download Startup or failure command.
otherwise unmodified.
nextdl $chunknum $percent $filenum
only the first argument is required.
the percentage is for display on the server only, and does not affect anything. If it is set to '-', servers should guess based upon the chunk number.
the third argument permits out-of-band file downloads, and is a cookie. increment the value each time a new file starts, and ignore oob chunks that do not match.
The client must periodically generate new chunk requests, sending it unreliably. If the third argument is present, multiple nextdl commands can be sent in a single client packet.
The server typically sends out a new svc_download data message upon receipt of each nextdl, either in band or out of band. The server must take care to rate limit oob messages.
The client will need to track which chunks have been received, which ones have not, and avoid requesting the same chunk too many times in rapid succession.
The client should take its requested netchan rate and its packetloss into consideration in order to avoid spamming itself off the internet.
You may wish to implement a slow-start algorithm or some such, and cut back if the packetloss rises.
The actual algorithm used to decide which chunk to request and when is up to the client implementor, but should generally be roughly sequential to avoid unnecessary cache thrashing on the server.
While unadvised, it is not an error to start at the last chunk and work backwards, though its generally not recommended to do so, and such implementations really should be explicit with the percentage hints.
The server should report an error if there is no active download or if the chunk is invalid (however, note ezquake incompatibilities).
stopdownload
Sent by the client whenever a download failed or finished. This allows the server to clean up resources.
The server will only allow one active download at a time, so this is not strictly required.
The server must not reply (except maybe with a print if there is no download currently active).
EZQuake's Incompatiblities:
ezquake clients do not send stopdownload. Instead it sends 'nextdl -1'. FTE servers print a warning and closes the download upon reception. Older versions reported error -3 due to this, but as ezquake cannot handle error conditions, servers that care about compatibility over correctness should not report error conditions if the chunk index is ~0.
mvdsv spams like fuck when it receives the stopdownload command.
mvdsv does not always report filenames in svc_download messages. Such messages can safely be ignored.
out of band downloads were not supported in the earliest versions, but do not present any compatibility issues.
Competing extensions:
darkplaces streamed downloads. more common with dp's own protocol.
quakeforge redirects. chunked downloads redefine the svc_download message, meaning quakeforge's redirects are incompatible.

236
specs/replacementdeltas.txt Normal file
View File

@ -0,0 +1,236 @@
Establishment:
Useful defines:
#define PROTOCOL_VERSION_FTE (('F'<<0) + ('T'<<8) + ('E'<<16) + ('X' << 24))
#define PEXT_FLOATCOORDS 0x00008000
#define PROTOCOL_VERSION_FTE2 (('F'<<0) + ('T'<<8) + ('E'<<16) + ('2' << 24))
#define PEXT2_REPLACEMENTDELTAS 0x00000008
QW: Extensions are established during the handshake.
CL: "\xff\xff\xff\xffgetchallenge\n"
SV: "\xff\xff\xff\xffc$challenge\0<binary key value pairs>"
CL: "\xff\xff\xff\xffconnect $ver $qport $challenge \"$userinfo\"\n%#x %#x\n%#x %#x\n", key1, value1, key2, value2
SV: "\xff\xff\xff\xffj\n"
See the 'both' part for additional info.
This handshake will be stripped by proxies. This is by design. It prevents qizmo and similar proxies from erroring on extensions.
NQ: At connection, the server will stuffcmd 'cmd pext\n'.
This must be explicitly parsed clientside, and effectively replaced with:
va("cmd pext %#x %#x %#x %#x\n", PROTOCOL_VERSION_FTE, 0|PEXT_FLOATCOORDS, PROTOCOL_VERSION_FTE2, 0|PEXT2_REPLACEMENTDELTAS)
You are free to add additional/private extensions there, but they MUST be sent as KEY VALUE pairs. The value may be quoted and freely contain whitespace as required, but the key MUST be hex.
See the 'both' part for additional info.
Both:
The server will update the svc_serverinfo message to have this ordering:
byte svcqw_serverdata / svcnq_serverinfo
optional long PROTOCOL_VERSION_FTE
optional long flags
optional long PROTOCOL_VERSION_FTE2
optional long flags
long PROTOCOL_VERSION
...
The protocol version should be parsed in a loop, stopping on unrecognised or non-extension-bits protocols.
The server must mask the extension bits with those that it supports, to avoid future extensions appearing active on servers that don't support them.
If the extension bits are not echoed from the server in the list, those extensions WILL NOT be used.
This mechanism allows the server to mask the client's extensions with the extensionbits that it supports, and to tell the client which protocol bits will actually be used for each map.
The server may kick the client if the client does not support extensions required by the server, just as a server sending an unsupported protocol version would result in a client getting kicked, but at least the server has the chance to run without.
I've included PEXT_FLOATCOORDS above as an example of stating more than one set of extensions. You should remove those tokens if you do not wish to support that extension. FTE servers may very well not utilize it on every map, and it will only be active if sv_bigcoords 1 is set.
Updated network primitives:
Entity numbers:
Entities are no longer coded as just a short.
Instead, they are coded as:
short. bit15=remove flag. bits14to0=entity number bits 14 - 0.
optional byte. bits0to7=entity number bits 22-15.
bit 22 should never be set.
This coding gives compatibility with mods using writeshort up to ent 32767.
Certain QuakeWorld mods already use writeshort hacks to pass entities less than 0. This practise should be discouraged. If you want a railgun effect, use TE=17 or a call to trailparticles.
Entity deltas:
The entity index is not considered part of the delta. It generally does not use the entity coding above, but reserves an extra bit as a remove flag, which is why bit 22 of the entity number should always be 0.
In the information below, the number in brackets denotes the bit value of the named flag. predinfo has its own 8bit set of bits, otherwise its the main set of 32bit bits.
byte - bits&0xff
UF_EXTEND1(7): byte - bits&0xff00
UF_EXTEND2(15): byte - bits&0xff0000
UF_EXTEND3(23): byte - bits&0xff000000
UF_FRAME(0): updated frame index:
UF_16BIT: short
otherwise: byte
UF_ORIGINXY(1): coord - origin[0]
UF_ORIGINXY(1): coord - origin[1]
UF_ORIGINZ(2): coord - origin[2]
UF_ANGLESXZ(3): angles[0], angles[2]
UF_PREDINFO: shortangle, shortangle
otherwise: angle, angle
UF_ANGLESY(4): angle[1]
UF_PREDINFO: shortangle
otherwise: stdangle
one of: entity effects
UF_EFFECTS(5)|UF_EFFECTS2(29): long
UF_EFFECTS2(29): short
UF_EFFECTS(5): byte
otherwise: nothing
UF_PREDINFO(6):
the bits listed here are stored within the predbits byte.
movetype and weaponframe are the only parts that are deltaed. the other fields all reset to 0 if their bit is not set.
WARNING: at this time, only players will have this set. a future version will enable this for rockets/nails also.
this means that nq engines will need to support MOVETYPE_FLY as a simple VectorMA projection, but should otherwise be able to use interpolation for all other movetypes.
byte - predbits
UFP_FORWARD(0): short, otherwise 0 - +forwards/+back speed
UFP_SIDE(1): short, otherwise 0 - +right/+left speed
UFP_UP(2): short, otherwise 0 - +moveup/+movedown speed
UFP_MOVETYPE(3): byte - .movetype
UFP_VELOCITYXY(4): short, otherwise 0 - .velocity[0]*8, .velocity[1]*8.
UFP_VELOCITYZ(5): short, otherwise 0 - .velocity[2]*8.
UFP_MSEC(6): byte, otherwise 0 - how long since the server received an update from this player. Clients should internally add half of their own latency to this value.
UFP_WEAPONFRAME(7): byte - bits 0-6 of .weaponframe. If it has bit 7 set, read another byte for the high bits
optional byte - the high bits
UF_EXTEND1(7): nothing here - already sent. listed for completeness.
UF_RESET(8): nothing - if bit is set, reset from baseline before parsing data. First two updates containing this entity will always contain this bit set.
UF_16BIT(9): nothing itself - merely extends the size of other bits of info (model, skin, frame)
UF_MODEL(10): modelindex. bit 15 enables full ragdoll, and should otherwise be ignored.
UF_16BIT: short
otherwise: byte
UF_SKIN(11): modelindex
UF_16BIT: short
otherwise: byte
UF_COLORMAP(12): byte - normally the 1-basedentity player slot of owning player, or 0 for world. treat-as-0 if invalid.
UF_SOLID(13): short - encoding of bbox size.
UF_FLAGS(14): byte - some misc flags, yay. Flags are consistant with the DP5+ protocol.
1 = stepping.
2 = glowtrail. leaves a trail the same colour as the glowmod stuff.
4 = viewmodelforclient was set
8 = exteriormodelforclient was set
16 = low precision.
32 = specific colourmap. UF_COLORMAP byte's low 4 bits are pants. high 4 bits are shirt.
64 = act as if part of world.
128 = complex animation. If set, there are more bytes following. This is not implemented by FTE at this time.
UF_EXTEND2(15): nothing here - already sent. listed for completeness.
UF_ALPHA(16): byte - 0-255 expresses effective range of 0-1. 0 is invisible. 255 is fully visible and default.
UF_SCALE(17): byte - default value 16 is equivelent to scale=1. 0 means scaled by 0.
UF_UNUSED(18): not used
UF_DRAWFLAGS(19):
byte - various flags to affect scale, abslight, etc. required for hexen2 compat.
optional byte - abslight. sent+used if (drawflags&7)==7.
UF_TAGINFO(20):
entityidx - tagentity. 0 = disable.
byte - tagindex. 0 = use tagentity's origin
UF_LIGHT(21):
short - red light scale
short - green light scale
short - blue light scale
short - light radius
byte - light style.
byte - light flags.
UF_TRAILEFFECT(22): short - particleffect num to attach and use as this entity's particle trail. the effect index value is compatible with DP_SV_POINTPARTICLES.
UF_EXTEND3(23): nothing here - already sent. listed for completeness.
UF_COLORMOD(24): discolours the entity. default byte value 32 is equivelent to 1.0.
byte - red
byte - green
byte - blue
UF_GLOW(25): gives a 'flashblend' type effect.
byte - size/4
byte - colour
byte - red, default byte value 32 is equivelent to 1.0.
byte - green
byte - blue
UF_FATNESS(26): byte - how many qu/16 to move expand models by (move them outwards along their normal)
UF_MODELINDEX2(27): second modelindex to use for the entity. for visible weapon models or some such. Note: uses the same frame as the normal entity. For compatibility, if qw vweaps were sent then this uses those instead (and changes the normal modelindex to a weapon-less player as appropriate too).
UF_16BIT: short
otherwise: byte
UF_GRAVITYDIR(28): both bytes=0 correlates to normal gravity.
byte - ((pitch/360) * 256) - 192
byte - (yaw/360) * 256
UF_EFFECTS2(29): nothing here - sent combined with UF_EFFECTS. mentioned for documentation.
UF_UNUSED(30): not used.
UF_EXTEND4(31): not used.
FIXME weirdness:
predicting non-players? yes please.
player angles are a huge wtf. this protocol uses view angles then divides the angle by 1/3rd post-prediction for MOVETYPE_WALK/STEP entities.
there is no information regarding the nextthink of entities. It *should* be possible to guess well enough without it.
Either way it is rare enough that engines already need to interpolate well enough without this info, making it somewhat redundant, imho.
the first 7 bits are the bits that are normally going to be encountered every update.
the 6 bits following those are often used only when an entity first becomes visible. the 16bit flag allows for more modelindicies etc and is used as a general 'whoa dude' flag.
the 2 bits after that are generally considered extensions (solid+flags), although the 'stepping' hint is useful.
either way, these bits are likely to be present in a vanilla progs, and common during entity setup.
the following 2 bits are required for many fairly generic custom maps now, that target fitzquake (alpha).
the 13 bits after those are much more rarely used. You can justifyably get away with not implementing them (assuming you error if you do receive any), though its prefered to parse and ignore the results.
the final 2 bits are reserved for future expansion. yay.
The information is sent in the unreliable channel. QuakeWorld clients have always acknowledged the server's unreliable sequence with every single packet.
This means that the server can directly detect packets which are not mentioned. Such unmentioned packets are assumed to be dropped.
The server keeps a log of the entities and updated bits that were sent with each outgoing packet. Once a packet has been detected as dropped, the ents+bits in that packet are folded back into the current outgoing state (taking care of added/removed entities). If too many packets are dropped, the server cannot track the entities which were sent and will drop ALL entities, starting from scratch.
When a bit is resent, current information is used, rather than the stale information that was previously sent, thus its possible to not see an update.
This all means that the server needs to track one set of previous state (maximum visible at once), and 64 or so sets of ent+bits (maximum updatable in a single packet). This can be used to keep memory use down when there are 4 million potential ents on the server.
Modified/Added SVCs:
svcfte_spawnstatic2=21
Instead of the standard baseline info, the packet will contain only a delta from the null entity state. This allows transparent entities etc.
svcfte_spawnbaseline2=66
Instead of the standard baseline info, the packet will contain an entity number followed by a delta from the null entity state. Yay for transparent ents.
Otherwise identical to the regular svc_spawnbaseline in purpose.
svcfte_updateentities=86
NQ: Replaces fast updates, svc_time.
QW: Replaces svc_[delta]packetentities, svc_playerinfo
The contents of this packet are:
NQ: inputack (this is an ack of the client's input sequence. for QW, use the netchan's ack sequence instead).
servertime of update, as a float32.
optional short=0x8000. (ie: when the entity index reads as 0 with the remove flag set) If present, remove ALL entities.
[
entity index.
short. bit15=remove flag. bit14=largeentity. bits13to0=entity number bits 13 - 0.
optional byte. bits0to7=entity number bits 21-14.
This coding gives max 22 bit entity indicies (about 4 million ents max, and up to 16384 using just shorts).
newdelta.
only present if the entity index does not contain the remove flag.
otherwise contains the changed fields.
if the ent was not previously known, copy it from its baseline before deltaing and hope it contains a model update...
beware of the special handling for entity number 0 required by
]
short=0 (ie: when the entity index reads as 0)
Unlike svc_deltapacketentities, there is no specific previous frame. The previous state to be used is merely the last state received.
svcfte_csqcentities=76
If supported, the entity index coding in this packet matches that of svcfte_updateentities.
Modified/Added CLCs:
clcfte_ackframe=50 (note: same clc+data as dp)
Message data consists of a single 32bit integer, which is the entity frame that is being acked.
Frames not mentioned are assumed to be dropped. Acks MUST arrive at the server in ascending order, and should be sent unreliably to reduce latency (even though this is more likely to result in resending the frame).
You may ack multiple entity frames within a single packet.
QuakeWorld packets contain an ack sequence in the packet header itself. This sequence will be acked automatically after any acks within contents of the packet, and does not need an explicit ackframe inside.
Because of this fact and typical packet sequences, QuakeWorld clients can omit sending these entirely.
If frame ~0u is 'acked', the server will reset and resend all ents
Information:
Player prediction is implemented by building a log of all the input frames clientside.
Each video frame, all input frames from ack+1 to latest are applied to the player's entity, according to its movetype+velocity, etc.
(as an optimisation, you instead apply new input frames since last video frame to the previous video frame's results, so long as you still rewind when a new entity state is received).
QW: the client's outgoing packet sequence specifies the 'latest' input frame available. the server's outgoing sequence specifies the last input frame applied as well as the entity frame to ack.
Conflicts:
PEXT_SPAWNSTATIC2 defines both svc_spawnstatic2+svc_baseline2. It defines these as containing a QW entity delta.
PEXT2_REPLACEMENTDELTAS states that these svcs exist, and that use replacement deltas.
PEXT2_REPLACEMENTDELTAS takes precidence, due to practicality.
MSG_WriteEntity's index coding is updated (and separated from writeshort).
PEXT_CSQC's entity index coding is updated.
PEXT_FLOATCOORDS is a separate extension that changes the network's primitives. Specifically that angles become 16bit and coords become 32bit floats. The two extensions do not otherwise interact.
PEXT2_REPLACEMENTDELTAS thus obsoletes PEXT_SCALE, PEXT_TRANS, PEXT_ACCURATETIMINGS, PEXT_FATNESS, PEXT_HULLSIZE, PEXT_MODELDBL, PEXT_ENTITYDBL, PEXT_ENTITYDBL2, PEXT_COLOURMOD, PEXT_SPAWNSTATIC2, PEXT_256PACKETENTITIES, PEXT_SETATTACHMENT, and PEXT_DPFLAGS. Not bad. :s
Things not updated:
NQ stats are not changed, and cannot exceed the value 256. FTE might send an svc_updatestat to force it, but this would be needlessly spammy.
Soundindex limits are not changed. That's an independant extension.
Hack Zone:
FTE servers will not wait for modellist/soundlist/prespawn commands to be acked before sending the next part of data, other than to correctly parse the map checksum. FTE servers will only burst all this information if ReplacementDeltas is supported. It was found that certain clients (namely FTE) was triggering some code to detect messed up ordering - messed up ordering that is fixed having the server track progress instead of relying on echos.
This extension is simply a handy check to see if the client can cope and without spamming the console.
Vanilla QuakeWorld clients appeared to be able to cope with this, but there are indeed certain ways it can fail, like if the client tries to pause for downloads.

267
specs/skeletal.txt Normal file
View File

@ -0,0 +1,267 @@
Supported skeletal formats:
FTE supports:
iqm (prefered)
psk (.psa is loaded automatically, slow to load)
md5mesh (animationless mesh-only models)
md5anim (meshless animation-only models, each one is a single framegroup)
zym (supported only for compat. single-weighted)
dpm (supported only for compat. lacks framegroups)
With the exception of md5 models, all of the above can be used as a drop-in replacement for regular .mdl files.
As you may have noticed, md5anim models contain no mesh, making them pointless, right?
Well, kinda.
Using the skeletal objects extension, you can actually 'build' the animation data from one model into a skeletal object, and then use that skeleton to render an entity with the actual md5mesh model instead.
This can be used to share a single set of animation data between X different mesh models (this feature is not specific to md5anims, but the various models do need to have the same bones+bone order, and similar enough stature for it to not look absurd).
The ragdoll extension builds upon skeletal objects, where physics bodies+joints are instanciated+moved from the animation data within a skeletal object, and fed back to later update that skeletal object after physics is run.
Because you can control which bodies are 'animated' as opposed to 'simulated', you can simulate a pony tail for instance, and use animation data for the head itself. Doing so will result in the pony tail following the head naturally, adhering to angle+position changes of the entity itself.
To use a skeletal object, you must first create the skeletal object with skel_create, and store the object handle in the entity's skeletonindex field. This will cause rendering to use the bone data from the skeletal object instead of the entity's frame info (it will still use the entity's model as normal, so the bone counts must be correct).
An uninitialised skeletal object is generally invalid, so after creating the skeletal object, you must fill its bone data with info from the animation model. You can do this with skel_build.
So for csqc:
void() mycsqcpredrawfunction =
{
if (!self.skeletonindex)
self.skeletonindex = skel_create(self.modelindex);
self.frame1time = time;
self.frame2time = time;
self.frame = 0;
self.frame2 = 1;
self.lerpfrac = 0.5;
skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
};
Be sure to call skel_delete when the entity is removed. The engine will not do this for you.
The code above will create a skeletal object for the entity, and fill it with animation data (firstbone, lastbone set to 0 means 'all bones').
The animation data used will be blended between framegroup 0 and framegroup 1 at a ratio of 50% vs 50%, and will animate with time. Note that you can set frameXtime to be dependant upon distance traveled. If your model animates a run speed of 320 qu per second, then self.frame1time += distancemoved/320; will keep your legs moving in sync with the ground.
The lerpfrac field is a bit useless, of course. This should generally be used for blending between separate animations. You can of course ditch framegroups entirely and use only frame+frame2 to animate your model, resetting lerpfrac 10 times a second or whatever.
Separation of torso+legs can also be achieved, by calling skel_build multiple times with separate bone ranges.
If your model is arranged with the legs as the early bones, and the torso as the later bones, you can use skel_find_bone to find the lower spine bone, and animate the two bone groups separately by updating the animation fields for torso, then for legs.
Note that you can simply pass something other than 'self' for the legs, and it'll use the animation fields from a different (invisible) entity instead.
You can blend forward+back+right+left animations together smoothly by calling skel_build with retainfrac=0 for the first animation, and with retainfrac=1 for all the others. If addfrac for all skel_build calls add up to 1 for each bone, you will have a valid animation that can completely avoid the need for messing with lerpfrac/frame2. Calling skel_build with addfrac not adding up to 1 will result in the model getting rescaled weirdly.
Aiming:
The easiest way to make the head aim at some fixed place separately from the gun, is to have the bone set directly straight forward in your modeling program, and to call skel_set_bone_world in the QC after making all your skel_build calls. The forward/right/up vectors should be the v_forward/v_right/v_up vectors after calling makevectors(vectoangles(target - org)); or some such.
Spinning:
To have a bone spin 1 rotation every second since time=0, you can use the following code:
{
if (!self.skeletonindex) self.skeletonindex = skel_create(self.modelindex);
skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
makevectors([0, time*360, 0]);
skel_mul_bone(self.skeletonindex, skel_find_bone(self.skeletonindex, "spine"), '0 0 0', v_forward, v_right, v_up);
}
There's nothing special about the skel_create or skel_build calls here, and skel_find_bone should probably be cached or something.
Note that the skel_build is not strictly required every frame. You could change 'time' to 'frametime' and move the skel_build into the skel_create's if-statement block, and the result would be the same.
Because skeletal objects are (generally) relative, rotating the spine like that will also rotate every single child bone along with the 'spine' bone.
A small note: skel_get_boneabs is in model space, not world space. You should use gettaginfo in order to get the world-space position of the bone (bones are equivelent to tags).
skel_set_bone will generally set the position of the bone relative to its parent, ignoring that bone's current rotation+position. skel_mul_bone on the other hand will rotate/move without destroying previous rotations/movements. Typically the children of that bone will also move.
Ragdolls:
To use ragdolls, you must have:
a) a skeletal model.
b) a skeletal object.
c) a body/joint (doll) definition.
While the entity is still alive, you will be calling skel_build on the skeletal object as normal, but also calling skel_ragupdate afterwards.
Here's some uncompiled example code.
{
float alive = self.frame != itsdeadjim;
if (!self.skeletonindex)
self.skeletonindex = skel_create(self.modelindex);
if (alive || self.oldalive != alive)
{
//you should update these fields properly...
self.frame1time = time;
self.frame2time = time;
self.frame = 0;
self.frame2 = 1;
self.lerpfrac = 0.5;
skel_build(self.skeletonindex, self, self.modelindex, 0, 0, 0, 1);
}
if (self.oldalive != alive)
{
if (alive)
skel_ragupdate(self, "animate 1", 0);
else
skel_ragupdate(self, "animate 0", 0);
}
skel_ragupdate(self, "doll foo.doll", 0);
self.oldalive = alive;
}
There's a few noteworthy things in the code above.
Setting the animate value to 1 means the doll will begin animating the doll as normal. This will give some sort of inverse kinematics (BUG: the current version forces positions rather than updating velocities, meaning the bodies will just spaz out as the joints break).
Setting the animate value to 0 means that the doll will ignore all animation values, thus the current info in the skeletal object is ignored and completely overwritten, thus removing the need to call skel_build. You can still call it, it just won't do anything useful.
skel_ragupdate will change the skeletal object to an absolute-skeletal-object (the bones are specified in modelspace rather than relative to their parents).
skel_build can only update relative-skeletal-objects, and will throw an error if given an absolute skeleton. If retainfrac=0 then it'll just silently update the skeletal object to relative as all data will be overwritten anyway.
The third argument to skel_ragupdate can be used as an animation source. This allows you to retain a set of animation data without destroying it. Specifically, if this is set to 0, the bones without bodies will snap back to their base pose, which can be rather ugly when it dies if the preliminary death animations do not revert them first. This animation source argument must be a relative-skeletal-object (ie: filled in with skel_build).
For portability/sanity reasons, don't call skel_get_bonerel+skel_set_bone+skel_mul_bone if ragupdate was called more recently than build.
Commands that skel_ragupdate accepts are:
enablejoint $jointname $truth
Can be used to disable(truth=0)/enable(truth=1) joints.
Disabling joints when the entity is killed allows things like dismemberment. You should probably stop calling skel_build for the affected bones if the entity is still alive.
animatebody $bodyname $strength
Can be used to override/change whether a single body is animated or not. The skeletal object must be valid/built for the bones of all animating bodies.
By setting and animating a single bone's world position, you can drag the entity around by that bone, even if all other bones are in their non-animated simulate-only state, before reverting that body setting when released.
animate $strength
Sets/clears the animate flag on all bodies to default.
Used to easily enter ragdoll state or reset the bodies to an alive state.
doll $dollfilename
Loads a doll file from disk and applies it to the current model.
See below for a vauge description of the file format.
Multiple instances of the same model will use the same internal representation of the file, but the file also be instanciated with separate models.
dollstring\n$dolltext
Can be used if you wish to embed the doll text within the qc, or generate it yourself.
Each skeletal object will have its own copy of the doll.
cleardoll
Can be used to uninstanciate the ragdoll. The skeletal object returns to being a regular non-ragdoll skeletal object.
Note that skel_delete will do this as required, so this is not really needed.
Loading a doll with one of the above commands will instanciate the model using the current pose. Make sure you have called skel_build appropriately before doing so.
Attempting to update a ragdoll skeletal object with no command specified will update the skeletal object to follow the ragdoll. If there is no current ragdoll, the default ragdoll for the skeletal object's initial model will be instanciated (the one that would be used from ssqc).
The default doll can be precached.
Differences between csqc and ssqc:
SSQC is visually crippled at this time. While the various skeleton builtins are available, it is not possible to render them onscreen. They can be used for hitmodel support and the like, but that is all. Other than this severe limitation, ssqc is fully functional. If you wish to test, FTE's csqc and ssqc modules share access to valid skeletal objects - THIS WILL ONLY WORK IN SINGLE PLAYER - thus it is possible to pass the skeletal object index to csqc for debug rendering, and ONLY debug.
Practical use is limited to either only hitmodels (to mirror csqc's positions), or replicating bones via csqc. It should be enough to network just the positions, and to allow the joints to correct the angles.
As a special exception for ssqc, if the gamecode uses a file named eg 'foo.iqm', the engine will also attempt to load 'foo.doll'. If the frame is set to something rediculous (try 65535), the skeletal object will ignore all 'animate 1' bodies, treating them all as 'animate 0'. This will allow you to activate ragdoll part way through a death animation, but also means you don't have control over the initial frame if the entity enters pvs late. You will likely want to use PVSF_NOREMOVE to mitigate this issue somewhat.
There is no functionality to copy player ragdolls to bodyqueue corpses, and it is not possible to reassign players to other entity slots. The only way around this is to have the ragdoll entity spawn at the start of the death animation, or be permanantly tracking the (invisible) player with exteriormodeltoclient set.
Ragdoll description files:
Note that the bones are all specified by name. The same .doll description file can be used with multiple models with different bone ordering without an issue.
to create a body:
body BODYNAME BONENAME
ATTRIBUTE ARGUMENTS
...
attributes are:
shape - one of:
box
sphere
cylinder
capsule
animate - 1 means uses the animation data instead of the body (and drives the body to match, so other bones follow)
size X [[Y] Z] - the z is the length of cylinders/capsules.
orient [BONE] - orient the body such that the top tip touches the body's bone, and the bottom touches the named bone. if BONE is omitted, uses the body's bone's parent.
to create a joint:
joint JOINTNAME BODY1 BODY2
ATTRIBUTE ARGUMENTS
...
attributes are:
type - one of:
fixed
point
hinge
slider
universal
hinge2
ERP,ERP2 -
CFM,CFM2 - should be kept low.
FMax,FMax2 -
HiStop,HiStop2 - the maximum angle allowed.
LoStop,LoStop2 - the minimum angle allowed.
axis,axis2 X Y Z - the hinge axis, a direction vector, around which the hinge will bend, spring will move, etc.
pivot BONE X Y Z - the pivot point of the joint will be created relative to the current position of the given bone. the xyz is relative to the angles of the bone. cannot be used on the default joint.
offset X Y Z - the pivot point relative to the parent bone, which can be used on the default joint.
QC definitions for the associated extensions:
.float skeletonindex;
Specifies the skeletal object to be used to render the entity. The named object overrides the frame etc fields but not the modelindex of the entity.
float(float modlindex) skel_create = #263;
Creates a new empty skeletal object to be used to animate the given model.
Every skeletal object you create MUST be deleted again - the remove builtin will _NOT_ do this for you.
float(float skel, entity ent, float modelindex, float retainfrac, float firstbone, float lastbone, optional float addfrac) skel_build = #264;
Fill a skeletal object with animation data from a model. model must be compatible with the skeletal object's model.
retainfrac+addfrac can be used to blend between simultaneous animations. The gamecode should ensure that the final result aproximates a total strength of '1' for each bone.
A firstbone of 0 means 'start with bone 1'.
A lastbone of 0 means 'include the last bone'.
You should use different bone regions if you wish to separate torso+legs.
ent determines which entity's fields to use. frame, frame2, lerpfrac, frame1time, frame2time are all used.
CSQCONLY: baseframe, baseframe2, baseframe1time, baseframe2time, baselerpfrac, basebone fields still apply, as they would without skeletal objects.
float(float skel) skel_get_numbones = #265;
Returns the number of bones in the model. as bone indicies are 1-based, the last bone is actually (skel_get_numbones(sko)+1)
string(float skel, float bonenum) skel_get_bonename = #266;
Retrieves the name of the bone.
float(float skel, float bonenum) skel_get_boneparent = #267;
Retrieves the index of the given bone's parent. 0 means no parent.
float(float skel, string tagname) skel_find_bone = #268;
Finds a bone by name. Use this for easily splitting a model at the waste or so.
vector(float skel, float bonenum) skel_get_bonerel = #269;
Gets the matrix transformation of the bone relative to its parent.
the offset is in the return value. v_forward, v_right, v_up contain the angle transform.
vector(float skel, float bonenum) skel_get_boneabs = #270;
Gets the matrix transformation of the bone relative to the entity that it is attached to.
the offset is in the return value. v_forward, v_right, v_up contain the angle transform.
vector(entity ent, float tagindex) gettaginfo = #452;
Gets the matrix transformation of the bone in world space.
the origin is in the return value. v_forward, v_right, v_up contain the angle transform. Use vectoangles(v_forward, v_up) to get the required angles for an entity to be positioned on that bone.
void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_set_bone = #271;
Sets the relative transform of the bone relative to its parent.
Also affects the child bones.
This can be used to remove the coord changes from certain md5anim files, for example.
void(float skel, float bonenum, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bone = #272;
Rotates/moves the bone relative to the parent.
Also affects the children.
An easy trick is to use:
makevectors('0 15 0');
skel_mul_bone(skel, bone, '0 0 0');
To rotate the given bone 15 degrees around its parent.
void(float skel, float startbone, float endbone, vector org, optional vector fwd, optional vector right, optional vector up) skel_mul_bones = #273;
Rotates/moves a range of bones individually. Useful for character spines where you want to affect each rib. Note that the angle is applied separately to each bone, so the angle used should be =(totalangle/numbones).
void(float skeldst, float skelsrc, float startbone, float entbone) skel_copybones = #274;
Copies the bones directly from one skeletal object to another. If the parent bones still differ then the visible result can still differ.
void(float skel) skel_delete = #275;
Deletes a skeletal object. The skeletal object is still valid and usable until the end of the frame. This allows you to add entities using that sko and delete them as part of the same frame.
void(entity ent, float bonenum, vector org, optional vector angorfwd, optional vector right, optional vector up) skel_set_bone_world = #283;
Edits the skeletal object attached to the entity to position+rotate the bone to the exact worldspace coordinate/angles specified, for things like eyes tracking victims etc.
float*(float skel) skel_mmap = #282;
Returns a pointer to the bonematrix of the skeletal object. Starting at bone 1, each bone takes 12 floats, ordered: fwd_x, lft_x, up_x, org_x, fwd_y, lft_y, up_y, org_y, fwd_z, lft_z, up_z, org_z.
The pointer is valid for as long as the sko is.
float(entity skelent, string dollcommand, float animskel) skel_ragupdate = #281;
Creates, modifies, or animates the physics bodies attached to the ent's sko, and copies the results back into the sko bone data.
dollcommand can be blank or for example "DOLL models/test.doll" to specify the ragdoll definition to use.
For performance reasons, the skeletal object will be defined using matricies relative to the entity rather than the parent bones. This will give weird results with the skel_set_bone skel_mul_* builtins, which can be fixed only with skel_build with retainfrac=0 refering to all bones.
If animskel is set: bones without bodies will use the information in the given sko.
If animskel is 0 and skel_build was called more recently than skel_ragupdate: bones without bodies will use the sko bone data already in the object.
If animskel is 0 and skel_build was not called recently: bones without bodies will snap to the base pose (relative to parent).
.float pvsflags;
Modifiers to control how the PVS affects entities.
#define PVSF_NORMALPVS 0
Culled by sv_cullentities_trace. This is the tighest PVS possible.
#define PVSF_NOTRACECHECK 1
Uses PVS only, and will not be subject to sv_cullentities_trace, like vanilla Quake.
#define PVSF_USEPHS 2
The entity will be visible if any sounds it makes could be heard. Basically a fatter pvs.
#define PVSF_IGNOREPVS 3
The entity will be globally visible.
#define PVSF_NOREMOVE 128
If set in pvsflags, the entity will not be hidden from the client simply because it is no longer visible, yet still not globally visible.
Should probably always be used on ssqc ents