/* ================== SV_CheckTimeouts If a packet has not been received from a client for timeout->value seconds, drop the conneciton. Server frames are used instead of realtime to avoid dropping the local client while debugging. When a client is normally dropped, the client_t goes into a zombie state for a few seconds to make sure any final reliable message gets resent if necessary ================== */ void SV_CheckTimeouts() { guard(SV_CheckTimeouts); int droppoint = svs.realtime - appRound(timeout->value * 1000); int zombiepoint = svs.realtime - appRound(zombietime->value * 1000); int i; client_t *cl; for (i = 0, cl = svs.clients; i < sv_maxclients->integer; i++, cl++) { // message times may be wrong across a changelevel if (cl->lastmessage > svs.realtime) cl->lastmessage = svs.realtime; if (cl->state == cs_zombie && cl->lastmessage < zombiepoint) { cl->state = cs_free; // can now be reused continue; } if ((cl->state == cs_connected || cl->state == cs_spawned) && cl->lastmessage < droppoint) { SV_DropClient(cl, "timed out"); cl->state = cs_free; // don't bother with zombie state } } unguard; }
static void CalcVrect() { float frac = scr_viewsize->Clamp(40, 100) / 100.0f; scr_vrect.width = appRound(viddef.width * frac); scr_vrect.height = appRound(viddef.height * frac); scr_vrect.width &= ~1; // align(2) scr_vrect.height &= ~1; // align(2) scr_vrect.x = (viddef.width - scr_vrect.width) / 2; scr_vrect.y = (viddef.height - scr_vrect.height) / 2; }
// Used for spatializing channels and autosounds static void S_SpatializeOrigin(const CVec3 &origin, float master_vol, float dist_mult, int *left_vol, int *right_vol) { if (cls.state != ca_active) { *left_vol = *right_vol = 255; return; } // calculate stereo seperation and distance attenuation CVec3 source_vec; VectorSubtract(origin, listener_origin, source_vec); float dist = source_vec.NormalizeFast(); dist -= SOUND_FULLVOLUME; if (dist < 0) dist = 0; // close enough to be at full volume dist *= dist_mult; // different attenuation levels float d = dot(listener_right, source_vec); float lscale, rscale; if (dma.channels == 1 || !dist_mult) { // no attenuation = no spatialization rscale = 1.0f; lscale = 1.0f; } else { rscale = 0.5f * (1.0f + d); lscale = 0.5f * (1.0f - d); } // swap left and right volumes if (s_reverse_stereo->integer) Exchange(lscale, rscale); float scale; // add in distance effect scale = (1.0f - dist) * rscale; *right_vol = appRound(master_vol * scale); if (*right_vol < 0) *right_vol = 0; scale = (1.0f - dist) * lscale; *left_vol = appRound(master_vol * scale); if (*left_vol < 0) *left_vol = 0; }
void S_Update_() { guard(S_Update_); if (!sound_started) return; SNDDMA_BeginPainting(); if (!dma.buffer) return; // Updates DMA time GetSoundtime(); // check to make sure that we haven't overshot if (paintedtime < soundtime) { Com_DPrintf("S_Update_ : overflow\n"); paintedtime = soundtime; } // mix ahead of current position unsigned endtime = soundtime + appRound(s_mixahead->value * dma.speed); // mix to an even submission block size endtime = Align(endtime, dma.submission_chunk); int samps = dma.samples >> (dma.channels-1); if (endtime - soundtime > samps) endtime = soundtime + samps; S_PaintChannels(endtime); SNDDMA_Submit(); unguard; }
void SaturateColor4b(color_t *c) { float sat = r_saturation->value; if (sat != 1.0f) { float r = c->c[0]; float g = c->c[1]; float b = c->c[2]; float light = (r + g + b) / 3; SATURATE(r,light,sat); SATURATE(g,light,sat); SATURATE(b,light,sat); c->c[0] = appRound(r); c->c[1] = appRound(g); c->c[2] = appRound(b); } }
// Project 3D point to screen coordinates; return false when not in view frustum bool ProjectToScreen(const CVec3 &pos, int scr[2]) { CVec3 vec; VectorSubtract(pos, vp.view.origin, vec); float z = dot(vec, vp.view.axis[0]); if (z <= gl_znear->value) return false; // not visible float x = dot(vec, vp.view.axis[1]) / z / vp.t_fov_x; if (x < -1 || x > 1) return false; float y = dot(vec, vp.view.axis[2]) / z / vp.t_fov_y; if (y < -1 || y > 1) return false; scr[0] = appRound(vp.x + vp.w * (0.5 - x / 2)); scr[1] = appRound(vp.y + vp.h * (0.5 - y / 2)); return true; }
static void SetLight_f(bool usage, int argc, char **argv) { if (usage || argc != 3) { appPrintf("Usage: setlight <style> <value=0..2>\n"); return; } if (!cls.cheatsEnabled) return; int style = atoi(argv[1]); if (style < 0 || style > 255) { appWPrintf("bad style: %d\n", style); return; } char str[2]; str[0] = appRound(atof(argv[2]) * ('m' - 'a')) + 'a'; str[1] = 0; CL_SetLightstyle(style, str); }
void USkeletalMesh::ConvertWedges(CSkelMeshLod &Lod, const TArray<FVector> &MeshPoints, const TArray<FMeshWedge> &MeshWedges, const TArray<FVertInfluence> &VertInfluences) { guard(USkeletalMesh::ConvertWedges); struct CVertInfo { int NumInfs; // may be higher than NUM_INFLUENCES int Bone[NUM_INFLUENCES]; float Weight[NUM_INFLUENCES]; }; int i, j; CVertInfo *Verts = new CVertInfo[MeshPoints.Num()]; memset(Verts, 0, MeshPoints.Num() * sizeof(CVertInfo)); // collect influences per vertex for (i = 0; i < VertInfluences.Num(); i++) { const FVertInfluence &Inf = VertInfluences[i]; CVertInfo &V = Verts[Inf.PointIndex]; int NumInfs = V.NumInfs++; int idx = NumInfs; if (NumInfs >= NUM_INFLUENCES) { // overflow // find smallest weight smaller than current float w = Inf.Weight; idx = -1; for (j = 0; j < NUM_INFLUENCES; j++) { if (V.Weight[j] < w) { w = V.Weight[j]; idx = j; // continue - may be other weight will be even smaller } } if (idx < 0) continue; // this weight is smaller than other } // add influence V.Bone[idx] = Inf.BoneIndex; V.Weight[idx] = Inf.Weight; } // normalize influences for (i = 0; i < MeshPoints.Num(); i++) { CVertInfo &V = Verts[i]; if (V.NumInfs == 0) { appPrintf("WARNING: Vertex %d has 0 influences\n", i); V.NumInfs = 1; V.Bone[0] = 0; V.Weight[0] = 1.0f; continue; } if (V.NumInfs <= NUM_INFLUENCES) continue; // no normalization is required float s = 0; for (j = 0; j < NUM_INFLUENCES; j++) // count sum s += V.Weight[j]; s = 1.0f / s; for (j = 0; j < NUM_INFLUENCES; j++) // adjust weights V.Weight[j] *= s; } // create vertices Lod.AllocateVerts(MeshWedges.Num()); for (i = 0; i < MeshWedges.Num(); i++) { const FMeshWedge &SW = MeshWedges[i]; CSkelMeshVertex &DW = Lod.Verts[i]; DW.Position = CVT(MeshPoints[SW.iVertex]); DW.UV = CVT(SW.TexUV); // DW.Normal and DW.Tangent are unset // setup Bone[] and Weight[] const CVertInfo &V = Verts[SW.iVertex]; unsigned PackedWeights = 0; for (j = 0; j < V.NumInfs; j++) { DW.Bone[j] = V.Bone[j]; PackedWeights |= appRound(V.Weight[j] * 255) << (j * 8); } DW.PackedWeights = PackedWeights; for (/* continue */; j < NUM_INFLUENCES; j++) // place end marker and zero weight { DW.Bone[j] = -1; } } delete Verts; unguard; }
void UVertMesh::BuildNormals() { // UE1 meshes have no stored normals, should build them // This function is similar to BuildNormals() from SkelMeshInstance.cpp int numVerts = Verts.Num(); int i; Normals.Empty(numVerts); Normals.AddZeroed(numVerts); TArray<CVec3> tmpVerts, tmpNormals; tmpVerts.AddZeroed(numVerts); tmpNormals.AddZeroed(numVerts); // convert verts for (i = 0; i < numVerts; i++) { const FMeshVert &SV = Verts[i]; CVec3 &DV = tmpVerts[i]; DV[0] = SV.X * MeshScale.X; DV[1] = SV.Y * MeshScale.Y; DV[2] = SV.Z * MeshScale.Z; } // iterate faces for (i = 0; i < Faces.Num(); i++) { const FMeshFace &F = Faces[i]; // get vertex indices int i1 = Wedges[F.iWedge[0]].iVertex; int i2 = Wedges[F.iWedge[2]].iVertex; // note: reverse order in comparison with SkeletalMesh int i3 = Wedges[F.iWedge[1]].iVertex; // iterate all frames for (int j = 0; j < FrameCount; j++) { int base = VertexCount * j; // compute edges const CVec3 &V1 = tmpVerts[base + i1]; const CVec3 &V2 = tmpVerts[base + i2]; const CVec3 &V3 = tmpVerts[base + i3]; CVec3 D1, D2, D3; VectorSubtract(V2, V1, D1); VectorSubtract(V3, V2, D2); VectorSubtract(V1, V3, D3); // compute normal CVec3 norm; cross(D2, D1, norm); norm.Normalize(); // compute angles D1.Normalize(); D2.Normalize(); D3.Normalize(); float angle1 = acos(-dot(D1, D3)); float angle2 = acos(-dot(D1, D2)); float angle3 = acos(-dot(D2, D3)); // add normals for triangle verts VectorMA(tmpNormals[base + i1], angle1, norm); VectorMA(tmpNormals[base + i2], angle2, norm); VectorMA(tmpNormals[base + i3], angle3, norm); } } // normalize and convert computed normals for (i = 0; i < numVerts; i++) { CVec3 &SN = tmpNormals[i]; FMeshNorm &DN = Normals[i]; SN.Normalize(); DN.X = appRound(SN[0] * 511 + 512); DN.Y = appRound(SN[1] * 511 + 512); DN.Z = appRound(SN[2] * 511 + 512); } }
// Cinematic streaming and voice over network void S_RawSamples(int samples, int rate, int width, int channels, byte *data) { int i; int src, dst; if (!sound_started) return; if (s_rawend < paintedtime) s_rawend = paintedtime; float scale = (float)rate / dma.speed; if (channels == 2 && width == 2) { if (scale == 1.0) { // optimized case for (i=0 ; i<samples ; i++) { dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[i*2] << 8; s_rawsamples[dst].right = ((short *)data)[i*2+1] << 8; } } else { for (i=0 ; ; i++) { src = appRound(i*scale); if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src*2] << 8; s_rawsamples[dst].right = ((short *)data)[src*2+1] << 8; } } } else if (channels == 1 && width == 2) { for (i=0 ; ; i++) { src = appRound(i*scale); if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((short *)data)[src] << 8; s_rawsamples[dst].right = ((short *)data)[src] << 8; } } else if (channels == 2 && width == 1) { for (i=0 ; ; i++) { src = appRound(i*scale); if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = ((char *)data)[src*2] << 16; s_rawsamples[dst].right = ((char *)data)[src*2+1] << 16; } } else if (channels == 1 && width == 1) { for (i=0 ; ; i++) { src = appRound(i*scale); if (src >= samples) break; dst = s_rawend&(MAX_RAW_SAMPLES-1); s_rawend++; s_rawsamples[dst].left = (((byte *)data)[src]-128) << 16; s_rawsamples[dst].right = (((byte *)data)[src]-128) << 16; } } }
/* ==================== S_StartSound Validates the parms and ques the sound up if pos is NULL, the sound will be dynamically sourced from the entity Entchannel 0 will never override a playing sound ==================== */ void S_StartSound(const CVec3 *origin, int entnum, int entchannel, sfx_t *sfx, float fvol, float attenuation, float timeofs) { if (!sound_started) return; if (!sfx) return; if (sfx->Name[0] == '*') sfx = GetPlayerSound(&cl_entities[entnum].current, sfx->Name); // make sure the sound is loaded sfxcache_t *sc = S_LoadSound(sfx); if (!sc) return; // couldn't load the sound's data int vol = appRound(fvol*255); // make the playsound_t playsound_t *ps = S_AllocPlaysound(); if (!ps) return; if (origin) { ps->origin = *origin; ps->fixed_origin = true; } else ps->fixed_origin = false; ps->entnum = entnum; ps->entchannel = entchannel; ps->attenuation = attenuation; ps->volume = vol; ps->sfx = sfx; // drift s_beginofs int start = appRound(cl.frame.servertime / 1000.0f * dma.speed + s_beginofs); if (start < paintedtime) { start = paintedtime; s_beginofs = start - appRound(cl.frame.servertime / 1000.0f * dma.speed); } else if (start > paintedtime + 0.3 * dma.speed) { start = appRound(paintedtime + 0.1 * dma.speed); s_beginofs = start - appRound(cl.frame.servertime / 1000.0f * dma.speed); } else { s_beginofs-=10; } if (!timeofs) ps->begin = paintedtime; else ps->begin = appRound(start + timeofs * dma.speed); // sort into the pending sound list playsound_t *sort; for (sort = s_pendingplays.next; sort != &s_pendingplays && sort->begin < ps->begin; sort = sort->next) ; // empty ps->next = sort; ps->prev = sort->prev; ps->next->prev = ps; ps->prev->next = ps; }
void SV_LinkEdict(edict_t *ent) { guard(SV_LinkEdict); int i, j, k; //!! HOOK - move outside ? if (bspfile.type != map_q2) { if (ent->s.modelindex >= 0 && ent->s.modelindex < MAX_MODELS) { // link model hook const char *modelName = sv.configstrings[CS_MODELS + ent->s.modelindex]; if (!strcmp(modelName, "models/objects/dmspot/tris.md2")) { // teleporter (source+target) was found // appPrintf(S_CYAN"teleport: %g %g %g\n", VECTOR_ARG(ent->s.origin)); return; // simply do not link model; trigger entity will be added anyway } } } if (ent->area.prev) SV_UnlinkEdict(ent); // unlink from old position (i.e. relink edict) if (ent == ge->edicts) return; // don't add the world if (!ent->inuse) return; entityHull_t &ex = ents[NUM_FOR_EDICT(ent)]; memset(&ex, 0, sizeof(entityHull_t)); ex.owner = ent; ex.axis.FromEuler(ent->s.angles); // set the size VectorSubtract(ent->bounds.maxs, ent->bounds.mins, ent->size); // encode the size into the entity_state for client prediction if (ent->solid == SOLID_BBOX) { // assume that x/y are equal and symetric i = appRound(ent->bounds.maxs[0] / 8); // z is not symetric j = appRound(-ent->bounds.mins[2] / 8); // and z maxs can be negative... k = appRound((ent->bounds.maxs[2] + 32) / 8); // original Q2 have bounded i/j/k/ with lower margin==1 (for client prediction only); this will // produce incorrect collision test when bbox mins/maxs is (0,0,0) i = bound(i, 0, 31); // mins/maxs[0,1] range is -248..0/0..248 j = bound(j, 0, 31); // mins[2] range is [-248..0] k = bound(k, 0, 63); // maxs[2] range is [-32..472] // if SVF_DEADMONSTER, s.solid should be 0 ent->s.solid = (ent->svflags & SVF_DEADMONSTER) ? 0 : (k<<10) | (j<<5) | i; i *= 8; j *= 8; k *= 8; ex.bounds.mins.Set(-i, -i, -j); ex.bounds.maxs.Set(i, i, k - 32); ex.bounds.GetCenter(ex.center); ex.center.Add(ent->s.origin); ex.model = NULL; ex.radius = VectorDistance(ex.bounds.maxs, ex.bounds.mins) / 2; } else if (ent->solid == SOLID_BSP) { ex.model = sv.models[ent->s.modelindex]; if (!ex.model) Com_DropError("MOVETYPE_PUSH with a non bsp model"); CVec3 v; ex.model->bounds.GetCenter(v); UnTransformPoint(ent->s.origin, ex.axis, v, ex.center); ex.radius = ex.model->radius; ent->s.solid = 31; // a SOLID_BBOX will never create this value (mins=(-248,-248,0) maxs=(248,248,-32)) } else if (ent->solid == SOLID_TRIGGER) { ent->s.solid = 0; // check for model link ex.model = sv.models[ent->s.modelindex]; if (!ex.model) { // model not attached by game, check entstring //?? can optimize: add 'bool spawningEnts', set to 'true' before SpawnEntities() //?? and 'false' after; skip code below when 'false' for (triggerModelLink_t *link = bspfile.modelLinks; link; link = link->next) #define CMP(n) (fabs(ent->s.origin[n] - link->origin[n]) < 0.5f) if (CMP(0) && CMP(1) && CMP(2)) { CBspModel *model = CM_InlineModel(link->modelIdx); VectorSubtract(model->bounds.maxs, ent->s.origin, ent->bounds.maxs); VectorSubtract(model->bounds.mins, ent->s.origin, ent->bounds.mins); break; } #undef CMP } } else ent->s.solid = 0; // set the abs box if (ent->solid == SOLID_BSP && (ent->s.angles[0] || ent->s.angles[1] || ent->s.angles[2])) { // expand for rotation for (i = 0; i < 3 ; i++) { ent->absBounds.mins[i] = ex.center[i] - ex.radius; ent->absBounds.maxs[i] = ex.center[i] + ex.radius; } } else { // normal VectorAdd(ent->s.origin, ent->bounds.mins, ent->absBounds.mins); VectorAdd(ent->s.origin, ent->bounds.maxs, ent->absBounds.maxs); } // because movement is clipped an epsilon away from an actual edge, // we must fully check even when bounding boxes don't quite touch for (i = 0; i < 3; i++) { ent->absBounds.mins[i] -= 1; ent->absBounds.maxs[i] += 1; } // link to PVS leafs ent->num_clusters = 0; ent->zonenum = 0; ent->zonenum2 = 0; // get all leafs, including solids CBspLeaf *leafs[MAX_TOTAL_ENT_LEAFS]; int topnode; int num_leafs = CM_BoxLeafs(ent->absBounds, ARRAY_ARG(leafs), &topnode); // set zones int clusters[MAX_TOTAL_ENT_LEAFS]; for (i = 0; i < num_leafs; i++) { clusters[i] = leafs[i]->cluster; int zone = leafs[i]->zone; if (zone) { // doors may legally straggle two zones, // but nothing should evern need more than that if (ent->zonenum && ent->zonenum != zone) { if (ent->zonenum2 && ent->zonenum2 != zone && sv.state == ss_loading) Com_DPrintf("Object touching 3 zones at %g %g %g\n", VECTOR_ARG(ent->absBounds.mins)); ent->zonenum2 = zone; } else ent->zonenum = zone; } } if (num_leafs >= MAX_TOTAL_ENT_LEAFS) { // assume we missed some leafs, and mark by headnode ent->num_clusters = -1; ent->headnode = topnode; } else { ent->num_clusters = 0; for (i = 0; i < num_leafs; i++) { if (clusters[i] == -1) continue; // not a visible leaf for (j = 0; j < i; j++) if (clusters[j] == clusters[i]) break; if (j == i) { if (ent->num_clusters == MAX_ENT_CLUSTERS) { // assume we missed some leafs, and mark by headnode ent->num_clusters = -1; ent->headnode = topnode; break; } ent->clusternums[ent->num_clusters++] = clusters[i]; } } } // if first time, make sure old_origin is valid if (!ent->linkcount) ent->s.old_origin = ent->s.origin; ent->linkcount++; if (ent->solid == SOLID_NOT) return; // find the first node that the ent's box crosses areanode_t *node = areaNodes; while (node->axis != -1) { if (ent->absBounds.mins[node->axis] > node->dist) node = node->children[0]; else if (ent->absBounds.maxs[node->axis] < node->dist) node = node->children[1]; else break; // crosses the node } // link it in areanode_t *node2 = node; if (ent->solid == SOLID_TRIGGER) { InsertLinkBefore(ent->area, node->trigEdicts); for ( ; node2; node2 = node2->parent) node2->numTrigEdicts++; } else { InsertLinkBefore(ent->area, node->solidEdicts); for ( ; node2; node2 = node2->parent) node2->numSolidEdicts++; } ex.area = node; unguard; }
void GLimp_SetGamma(float gamma) { #if FIND_GAMMA EXEC_ONCE(appWPrintf("Find gamma mode!\n")); float a, b; // y = ax + b a = Cvar_Get("a","1")->value; # if !FIND_GAMMA2 b = Cvar_Get("b","0.5")->value; # else b = Cvar_Get("b","-0.5")->value; # endif #endif if (!gammaStored) return; gamma = bound(gamma, 0.5, 3); float contr = bound(r_contrast->value, 0.1, 2); float bright = bound(r_brightness->value, 0.1, 2); float invGamma = 1.0f / gamma; float overbright = gl_config.overbright + 1; for (int i = 0; i < 256; i++) { #if 0 float tmp = (i / 255.0f * overbright - 0.5f) * contr + 0.5f; if (tmp < 0) tmp = 0; // without this, can get semi-negative picture when r_gamma=0.5 (invGamma=2, sqr func) int v = appRound(65535.0f * (pow(tmp, invGamma) + bright - 1)); #else // taken from UT2003 // note: ut_br = br-0.5, ut_contr = contr-0.5 (replace later: norm. bright=0.5, contr=0.5) !! float tmp = pow(i * overbright / 255.0f, invGamma) * contr * 65535; tmp = tmp + (bright - 1) * 32768 - (contr - 0.5) * 32768 + 16384; int v = appRound(tmp); #endif if (GIsWin2K) { // Win2K/XP performs checking of gamma ramp and may reject it #if !FIND_GAMMA // clamp gamma curve with line 'y=x*GAMMA_ANGLE+GAMMA_OFFSET' #define GAMMA_ANGLE 1 #define GAMMA_OFFSET 0.5 int m = i * (GAMMA_ANGLE*256) + (int)(GAMMA_OFFSET*65536); if (v > m) v = m; #define GAMMA_ANGLE2 1 #define GAMMA_OFFSET2 -0.5 m = i * (GAMMA_ANGLE2*256) + (int)(GAMMA_OFFSET2*65536); if (v < m) v = m; #else // FIND_GAMMA # if !FIND_GAMMA2 int m = appRound(i * a * 256 + b * 65535); if (v > m) v = m; # else int m = appRound(i * a * 256 + b * 65535); if (v < m) v = m; # endif #endif // FIND_GAMMA } v = bound(v, 0, 65535); newGamma[i] = newGamma[i+256] = newGamma[i+512] = v; } gammaValid = true; UpdateGamma(); }
static void LoadAnimationCfg(clientInfo_t &ci, const char *filename) { char *buf = (char*) GFileSystem->LoadFile(filename); gci = &ci; int animNumber = 0; animation_t *anims = ci.animations; int frameSkip = 0; ci.modelGender = 'm'; // male; default CSimpleParser text; text.InitFromBuf(buf); while (const char *line = text.GetLine()) { // execute line if (line[0] >= '0' && line[0] <= '9') // numbers => frame numbers { if (animNumber >= MAX_ANIMATIONS) { appWPrintf("Too much animations in \"%s\"\n", filename); break; } int firstFrame, numFrames, loopFrames; float fps; if (sscanf(line, "%d %d %d %f", &firstFrame, &numFrames, &loopFrames, &fps) != 4) { appWPrintf("Invalid frame info [%s] in \"%s\"\n", line, filename); break; } // some processing on acquired data + store info if (animNumber == LEGS_WALKCR) { frameSkip = firstFrame - anims[TORSO_GESTURE].firstFrame; firstFrame = anims[TORSO_GESTURE].firstFrame; } else if (animNumber > LEGS_WALKCR && animNumber < TORSO_GETFLAG) firstFrame -= frameSkip; if (numFrames < 0) { numFrames = -numFrames; anims[animNumber].reversed = true; } anims[animNumber].firstFrame = firstFrame; anims[animNumber].numFrames = numFrames; anims[animNumber].loopFrames = loopFrames; if (fps < 1) fps = 1; anims[animNumber].frameLerp = appRound(1000.0f / fps); animNumber++; } else if (!ExecuteCommand(line, ARRAY_ARG(animCommands))) { appWPrintf("Invalid line [%s] in \"%s\"\n", line, filename); break; } } // copy gestures when absent if (animNumber >= TORSO_GETFLAG && animNumber <= TORSO_NEGATIVE) for (int i = animNumber; i <= TORSO_NEGATIVE; i++) { anims[i] = anims[TORSO_GESTURE]; anims[i].reversed = false; } // crouch backward animation anims[LEGS_BACKCR] = anims[LEGS_WALKCR]; anims[LEGS_BACKCR].reversed = true; // walk backward animation anims[LEGS_BACKWALK] = anims[LEGS_WALK]; anims[LEGS_BACKWALK].reversed = true; // Q3 have some processing of flag animations ... for (int i = 0; i < MAX_TOTALANIMATIONS; i++) if (anims[i].frameLerp == 0) anims[i].frameLerp = 100; // just in case delete buf; }