X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=sv_phys.c;h=3f91ff3ebec94cefbd580e810fb1b419b9daa163;hb=c3001ce26f2c2fd69caa6af345ccca1d4376f4e5;hp=eb7588b91b4a936b5937e53ac85c93d4f06c4719;hpb=8a2270984f3abc755bd49eff17e99b108da6ba1a;p=xonotic%2Fdarkplaces.git diff --git a/sv_phys.c b/sv_phys.c index eb7588b9..3f91ff3e 100644 --- a/sv_phys.c +++ b/sv_phys.c @@ -39,17 +39,17 @@ solid_edge items only clip against bsp models. */ -cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4"}; -cvar_t sv_stopspeed = {CVAR_NOTIFY, "sv_stopspeed","100"}; -cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800"}; -cvar_t sv_maxvelocity = {CVAR_NOTIFY, "sv_maxvelocity","2000"}; -cvar_t sv_nostep = {CVAR_NOTIFY, "sv_nostep","0"}; -cvar_t sv_stepheight = {CVAR_NOTIFY, "sv_stepheight", "18"}; -cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "1"}; -cvar_t sv_wallfriction = {CVAR_NOTIFY, "sv_wallfriction", "1"}; -cvar_t sv_newflymove = {CVAR_NOTIFY, "sv_newflymove", "0"}; -cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0"}; -cvar_t sv_playerphysicsqc = {CVAR_NOTIFY, "sv_playerphysicsqc", "1"}; +cvar_t sv_friction = {CVAR_NOTIFY, "sv_friction","4", "how fast you slow down"}; +cvar_t sv_stopspeed = {CVAR_NOTIFY, "sv_stopspeed","100", "how fast you come to a complete stop"}; +cvar_t sv_gravity = {CVAR_NOTIFY, "sv_gravity","800", "how fast you fall (512 = roughly earth gravity)"}; +cvar_t sv_maxvelocity = {CVAR_NOTIFY, "sv_maxvelocity","2000", "universal speed limit on all entities"}; +cvar_t sv_nostep = {CVAR_NOTIFY, "sv_nostep","0", "prevents MOVETYPE_STEP entities (monsters) from moving"}; +cvar_t sv_stepheight = {CVAR_NOTIFY, "sv_stepheight", "18", "how high you can step up (TW_SV_STEPCONTROL extension)"}; +cvar_t sv_jumpstep = {CVAR_NOTIFY, "sv_jumpstep", "1", "whether you can step up while jumping (sv_gameplayfix_stepwhilejumping must also be 1)"}; +cvar_t sv_wallfriction = {CVAR_NOTIFY, "sv_wallfriction", "1", "how much you slow down when sliding along a wall"}; +cvar_t sv_newflymove = {CVAR_NOTIFY, "sv_newflymove", "0", "enables simpler/buggier player physics (not recommended)"}; +cvar_t sv_freezenonclients = {CVAR_NOTIFY, "sv_freezenonclients", "0", "freezes time, except for players, allowing you to walk around and take screenshots of explosions"}; +cvar_t sv_playerphysicsqc = {CVAR_NOTIFY, "sv_playerphysicsqc", "1", "enables QuakeC function to override player physics"}; #define MOVE_EPSILON 0.01 @@ -359,7 +359,7 @@ int SV_FlyMove (prvm_edict_t *ent, float time, float *stepnormal) // run the impact function if (impact) { - SV_Impact(ent, trace.ent); + SV_Impact(ent, (prvm_edict_t *)trace.ent); // break if removed by the impact function if (ent->priv.server->free) @@ -512,7 +512,7 @@ trace_t SV_PushEntity (prvm_edict_t *ent, vec3_t push) SV_LinkEdict (ent, true); if (trace.ent && (!((int)ent->fields.server->flags & FL_ONGROUND) || ent->fields.server->groundentity != PRVM_EDICT_TO_PROG(trace.ent))) - SV_Impact (ent, trace.ent); + SV_Impact (ent, (prvm_edict_t *)trace.ent); return trace; } @@ -523,13 +523,11 @@ SV_PushMove ============ */ -trace_t SV_ClipMoveToEntity (prvm_edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int movetype, int hitsupercontents); void SV_PushMove (prvm_edict_t *pusher, float movetime) { int i, e, index; - prvm_edict_t *check, *ed; float savesolid, movetime2, pushltime; - vec3_t mins, maxs, move, move1, moveangle, pushorig, pushang, a, forward, left, up, org, org2; + vec3_t mins, maxs, move, move1, moveangle, pushorig, pushang, a, forward, left, up, org; int num_moved; int numcheckentities; static prvm_edict_t *checkentities[MAX_EDICTS]; @@ -647,7 +645,7 @@ void SV_PushMove (prvm_edict_t *pusher, float movetime) numcheckentities = SV_EntitiesInBox(mins, maxs, MAX_EDICTS, checkentities); for (e = 0;e < numcheckentities;e++) { - check = checkentities[e]; + prvm_edict_t *check = checkentities[e]; if (check->fields.server->movetype == MOVETYPE_NONE || check->fields.server->movetype == MOVETYPE_PUSH || check->fields.server->movetype == MOVETYPE_FOLLOW @@ -669,6 +667,7 @@ void SV_PushMove (prvm_edict_t *pusher, float movetime) if (forward[0] != 1 || left[1] != 1) // quick way to check if any rotation is used { + vec3_t org2; VectorSubtract (check->fields.server->origin, pusher->fields.server->origin, org); org2[0] = DotProduct (org, forward); org2[1] = DotProduct (org, left); @@ -694,47 +693,60 @@ void SV_PushMove (prvm_edict_t *pusher, float movetime) if (SV_ClipMoveToEntity(pusher, check->fields.server->origin, check->fields.server->mins, check->fields.server->maxs, check->fields.server->origin, 0, SUPERCONTENTS_SOLID).startsolid) { // try moving the contacted entity a tiny bit further to account for precision errors + vec3_t move2; pusher->fields.server->solid = SOLID_NOT; - VectorScale(move, 0.1, move); - SV_PushEntity (check, move); + VectorScale(move, 1.1, move2); + VectorCopy (check->priv.server->moved_from, check->fields.server->origin); + VectorCopy (check->priv.server->moved_fromangles, check->fields.server->angles); + SV_PushEntity (check, move2); pusher->fields.server->solid = savesolid; if (SV_ClipMoveToEntity(pusher, check->fields.server->origin, check->fields.server->mins, check->fields.server->maxs, check->fields.server->origin, 0, SUPERCONTENTS_SOLID).startsolid) { - // still inside pusher, so it's really blocked - - // fail the move - if (check->fields.server->mins[0] == check->fields.server->maxs[0]) - continue; - if (check->fields.server->solid == SOLID_NOT || check->fields.server->solid == SOLID_TRIGGER) + // try moving the contacted entity a tiny bit less to account for precision errors + pusher->fields.server->solid = SOLID_NOT; + VectorScale(move, 0.9, move2); + VectorCopy (check->priv.server->moved_from, check->fields.server->origin); + VectorCopy (check->priv.server->moved_fromangles, check->fields.server->angles); + SV_PushEntity (check, move2); + pusher->fields.server->solid = savesolid; + if (SV_ClipMoveToEntity(pusher, check->fields.server->origin, check->fields.server->mins, check->fields.server->maxs, check->fields.server->origin, 0, SUPERCONTENTS_SOLID).startsolid) { - // corpse - check->fields.server->mins[0] = check->fields.server->mins[1] = 0; - VectorCopy (check->fields.server->mins, check->fields.server->maxs); - continue; - } + // still inside pusher, so it's really blocked - VectorCopy (pushorig, pusher->fields.server->origin); - VectorCopy (pushang, pusher->fields.server->angles); - pusher->fields.server->ltime = pushltime; - SV_LinkEdict (pusher, false); + // fail the move + if (check->fields.server->mins[0] == check->fields.server->maxs[0]) + continue; + if (check->fields.server->solid == SOLID_NOT || check->fields.server->solid == SOLID_TRIGGER) + { + // corpse + check->fields.server->mins[0] = check->fields.server->mins[1] = 0; + VectorCopy (check->fields.server->mins, check->fields.server->maxs); + continue; + } - // move back any entities we already moved - for (i = 0;i < num_moved;i++) - { - ed = sv.moved_edicts[i]; - VectorCopy (ed->priv.server->moved_from, ed->fields.server->origin); - VectorCopy (ed->priv.server->moved_fromangles, ed->fields.server->angles); - SV_LinkEdict (ed, false); - } + VectorCopy (pushorig, pusher->fields.server->origin); + VectorCopy (pushang, pusher->fields.server->angles); + pusher->fields.server->ltime = pushltime; + SV_LinkEdict (pusher, false); - // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone - if (pusher->fields.server->blocked) - { - prog->globals.server->self = PRVM_EDICT_TO_PROG(pusher); - prog->globals.server->other = PRVM_EDICT_TO_PROG(check); - PRVM_ExecuteProgram (pusher->fields.server->blocked, "QC function self.blocked is missing"); + // move back any entities we already moved + for (i = 0;i < num_moved;i++) + { + prvm_edict_t *ed = sv.moved_edicts[i]; + VectorCopy (ed->priv.server->moved_from, ed->fields.server->origin); + VectorCopy (ed->priv.server->moved_fromangles, ed->fields.server->angles); + SV_LinkEdict (ed, false); + } + + // if the pusher has a "blocked" function, call it, otherwise just stay in place until the obstacle is gone + if (pusher->fields.server->blocked) + { + prog->globals.server->self = PRVM_EDICT_TO_PROG(pusher); + prog->globals.server->other = PRVM_EDICT_TO_PROG(check); + PRVM_ExecuteProgram (pusher->fields.server->blocked, "QC function self.blocked is missing"); + } + break; } - break; } } } @@ -1151,7 +1163,7 @@ SV_CheckWaterTransition void SV_CheckWaterTransition (prvm_edict_t *ent) { int cont; - cont = SV_PointQ1Contents(ent->fields.server->origin); + cont = Mod_Q1BSP_NativeContentsFromSuperContents(NULL, SV_PointSuperContents(ent->fields.server->origin)); if (!ent->fields.server->watertype) { // just spawned here @@ -1188,22 +1200,25 @@ void SV_Physics_Toss (prvm_edict_t *ent) trace_t trace; vec3_t move; - // don't stick to ground if onground and moving upward - if (ent->fields.server->velocity[2] >= (1.0 / 32.0) && ((int)ent->fields.server->flags & FL_ONGROUND)) - ent->fields.server->flags = (int)ent->fields.server->flags & ~FL_ONGROUND; - // if onground, return without moving if ((int)ent->fields.server->flags & FL_ONGROUND) { - if (ent->fields.server->groundentity == 0 || sv_gameplayfix_noairborncorpse.integer) - return; - // if ent was supported by a brush model on previous frame, - // and groundentity is now freed, set groundentity to 0 (floating) - if (ent->priv.server->suspendedinairflag && PRVM_PROG_TO_EDICT(ent->fields.server->groundentity)->priv.server->free) + // don't stick to ground if onground and moving upward + if (ent->fields.server->velocity[2] >= (1.0 / 32.0)) + ent->fields.server->flags -= FL_ONGROUND; + else { - // leave it suspended in the air - ent->fields.server->groundentity = 0; - return; + prvm_edict_t *ground = PRVM_PROG_TO_EDICT(ent->fields.server->groundentity); + if (ground->fields.server->solid == SOLID_BSP || !sv_gameplayfix_noairborncorpse.integer) + return; + // if ent was supported by a brush model on previous frame, + // and groundentity is now freed, set groundentity to 0 (floating) + if (ent->priv.server->suspendedinairflag && ground->priv.server->free) + { + // leave it suspended in the air + ent->fields.server->groundentity = 0; + return; + } } } ent->priv.server->suspendedinairflag = false; @@ -1303,23 +1318,38 @@ will fall if the floor is pulled out from under them. */ void SV_Physics_Step (prvm_edict_t *ent) { - // don't stick to ground if onground and moving upward - if (ent->fields.server->velocity[2] >= (1.0 / 32.0) && ((int)ent->fields.server->flags & FL_ONGROUND)) - ent->fields.server->flags = (int)ent->fields.server->flags & ~FL_ONGROUND; - - // freefall if not onground/fly/swim - if (!((int)ent->fields.server->flags & (FL_ONGROUND | FL_FLY | FL_SWIM))) + int flags = (int)ent->fields.server->flags; + // don't fall at all if fly/swim + if (!(flags & (FL_FLY | FL_SWIM))) { - int hitsound = ent->fields.server->velocity[2] < sv_gravity.value * -0.1; + if (flags & FL_ONGROUND) + { + // freefall if onground and moving upward + // freefall if not standing on a world surface (it may be a lift) + prvm_edict_t *ground = PRVM_PROG_TO_EDICT(ent->fields.server->groundentity); + if (ent->fields.server->velocity[2] >= (1.0 / 32.0) || (ground->fields.server->solid != SOLID_BSP && sv_gameplayfix_noairborncorpse.integer)) + { + ent->fields.server->flags -= FL_ONGROUND; + SV_AddGravity(ent); + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, NULL); + SV_LinkEdict(ent, true); + } + } + else + { + // freefall if not onground + int hitsound = ent->fields.server->velocity[2] < sv_gravity.value * -0.1; - SV_AddGravity(ent); - SV_CheckVelocity(ent); - SV_FlyMove(ent, sv.frametime, NULL); - SV_LinkEdict(ent, true); + SV_AddGravity(ent); + SV_CheckVelocity(ent); + SV_FlyMove(ent, sv.frametime, NULL); + SV_LinkEdict(ent, true); - // just hit ground - if (hitsound && (int)ent->fields.server->flags & FL_ONGROUND && gamemode != GAME_NEXUIZ) - SV_StartSound(ent, 0, "demon/dland2.wav", 255, 1); + // just hit ground + if (hitsound && (int)ent->fields.server->flags & FL_ONGROUND && gamemode != GAME_NEXUIZ) + SV_StartSound(ent, 0, "demon/dland2.wav", 255, 1); + } } // regular thinking @@ -1330,38 +1360,88 @@ void SV_Physics_Step (prvm_edict_t *ent) //============================================================================ -void SV_Physics_Entity (prvm_edict_t *ent, qboolean runmove) +static void SV_Physics_Entity (prvm_edict_t *ent, qboolean runmove) { - int i = ent - prog->edicts; - if (i >= 1 && i <= svs.maxclients) + switch ((int) ent->fields.server->movetype) { - // make sure the velocity is sane (not a NaN) - SV_CheckVelocity(ent); - // LordHavoc: QuakeC replacement for SV_ClientThink (player movement) - if (SV_PlayerPhysicsQC && sv_playerphysicsqc.integer) + case MOVETYPE_PUSH: + case MOVETYPE_FAKEPUSH: + SV_Physics_Pusher (ent); + break; + case MOVETYPE_NONE: + // LordHavoc: manually inlined the thinktime check here because MOVETYPE_NONE is used on so many objects + if (ent->fields.server->nextthink > 0 && ent->fields.server->nextthink <= sv.time + sv.frametime) + SV_RunThink (ent); + break; + case MOVETYPE_FOLLOW: + SV_Physics_Follow (ent); + break; + case MOVETYPE_NOCLIP: + if (SV_RunThink(ent)) { - prog->globals.server->time = sv.time; - prog->globals.server->self = PRVM_EDICT_TO_PROG(ent); - PRVM_ExecuteProgram ((func_t)(SV_PlayerPhysicsQC - prog->functions), "QC function SV_PlayerPhysics is missing"); + SV_CheckWater(ent); + VectorMA(ent->fields.server->origin, sv.frametime, ent->fields.server->velocity, ent->fields.server->origin); + VectorMA(ent->fields.server->angles, sv.frametime, ent->fields.server->avelocity, ent->fields.server->angles); } - else - SV_ClientThink (); - // make sure the velocity is sane (not a NaN) - SV_CheckVelocity(ent); - // LordHavoc: a hack to ensure that the (rather silly) id1 quakec - // player_run/player_stand1 does not horribly malfunction if the - // velocity becomes a number that is both == 0 and != 0 - // (sounds to me like NaN but to be absolutely safe...) - if (DotProduct(ent->fields.server->velocity, ent->fields.server->velocity) < 0.0001) - VectorClear(ent->fields.server->velocity); - // call standard client pre-think + SV_LinkEdict(ent, false); + break; + case MOVETYPE_STEP: + SV_Physics_Step (ent); + break; + case MOVETYPE_WALK: + if (SV_RunThink (ent)) + { + if (!SV_CheckWater (ent) && ! ((int)ent->fields.server->flags & FL_WATERJUMP) ) + SV_AddGravity (ent); + SV_CheckStuck (ent); + SV_WalkMove (ent); + SV_LinkEdict (ent, true); + } + break; + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + case MOVETYPE_BOUNCEMISSILE: + case MOVETYPE_FLYMISSILE: + case MOVETYPE_FLY: + // regular thinking + if (SV_RunThink (ent) && runmove) + SV_Physics_Toss (ent); + break; + default: + Con_Printf ("SV_Physics: bad movetype %i\n", (int)ent->fields.server->movetype); + break; + } +} + +void SV_ApplyClientMove (void); +void SV_Physics_ClientEntity (prvm_edict_t *ent) +{ + SV_ApplyClientMove(); + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + // LordHavoc: QuakeC replacement for SV_ClientThink (player movement) + if (SV_PlayerPhysicsQC && sv_playerphysicsqc.integer) + { prog->globals.server->time = sv.time; prog->globals.server->self = PRVM_EDICT_TO_PROG(ent); - PRVM_ExecuteProgram (prog->globals.server->PlayerPreThink, "QC function PlayerPreThink is missing"); - SV_CheckVelocity (ent); + PRVM_ExecuteProgram ((func_t)(SV_PlayerPhysicsQC - prog->functions), "QC function SV_PlayerPhysics is missing"); } + else + SV_ClientThink (); + // make sure the velocity is sane (not a NaN) + SV_CheckVelocity(ent); + // LordHavoc: a hack to ensure that the (rather silly) id1 quakec + // player_run/player_stand1 does not horribly malfunction if the + // velocity becomes a number that is both == 0 and != 0 + // (sounds to me like NaN but to be absolutely safe...) + if (DotProduct(ent->fields.server->velocity, ent->fields.server->velocity) < 0.0001) + VectorClear(ent->fields.server->velocity); + // call standard client pre-think + prog->globals.server->time = sv.time; + prog->globals.server->self = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (prog->globals.server->PlayerPreThink, "QC function PlayerPreThink is missing"); + SV_CheckVelocity (ent); - // LordHavoc: merged client and normal entity physics switch ((int) ent->fields.server->movetype) { case MOVETYPE_PUSH: @@ -1383,9 +1463,6 @@ void SV_Physics_Entity (prvm_edict_t *ent, qboolean runmove) VectorMA(ent->fields.server->origin, sv.frametime, ent->fields.server->velocity, ent->fields.server->origin); VectorMA(ent->fields.server->angles, sv.frametime, ent->fields.server->avelocity, ent->fields.server->angles); } - // relink normal entities here, players always get relinked so don't relink twice - if (!(i > 0 && i <= svs.maxclients)) - SV_LinkEdict(ent, false); break; case MOVETYPE_STEP: SV_Physics_Step (ent); @@ -1397,9 +1474,6 @@ void SV_Physics_Entity (prvm_edict_t *ent, qboolean runmove) SV_AddGravity (ent); SV_CheckStuck (ent); SV_WalkMove (ent); - // relink normal entities here, players always get relinked so don't relink twice - if (!(i > 0 && i <= svs.maxclients)) - SV_LinkEdict (ent, true); } break; case MOVETYPE_TOSS: @@ -1407,42 +1481,33 @@ void SV_Physics_Entity (prvm_edict_t *ent, qboolean runmove) case MOVETYPE_BOUNCEMISSILE: case MOVETYPE_FLYMISSILE: // regular thinking - if (SV_RunThink (ent) && runmove) + if (SV_RunThink (ent)) SV_Physics_Toss (ent); break; case MOVETYPE_FLY: - if (SV_RunThink (ent) && runmove) + if (SV_RunThink (ent)) { - if (i > 0 && i <= svs.maxclients) - { - SV_CheckWater (ent); - SV_WalkMove (ent); - } - else - SV_Physics_Toss (ent); + SV_CheckWater (ent); + SV_WalkMove (ent); } break; default: - Con_Printf ("SV_Physics: bad movetype %i", (int)ent->fields.server->movetype); + Con_Printf ("SV_Physics_ClientEntity: bad movetype %i\n", (int)ent->fields.server->movetype); break; } - if (i >= 1 && i <= svs.maxclients) - { - SV_CheckVelocity (ent); + SV_CheckVelocity (ent); - // call standard player post-think - SV_LinkEdict (ent, true); + // call standard player post-think + SV_LinkEdict (ent, true); - SV_CheckVelocity (ent); + SV_CheckVelocity (ent); - prog->globals.server->time = sv.time; - prog->globals.server->self = PRVM_EDICT_TO_PROG(ent); - PRVM_ExecuteProgram (prog->globals.server->PlayerPostThink, "QC function PlayerPostThink is missing"); - } + prog->globals.server->time = sv.time; + prog->globals.server->self = PRVM_EDICT_TO_PROG(ent); + PRVM_ExecuteProgram (prog->globals.server->PlayerPostThink, "QC function PlayerPostThink is missing"); } - /* ================ SV_Physics @@ -1453,7 +1518,7 @@ void SV_Physics (void) { int i, newnum_edicts; prvm_edict_t *ent; - qbyte runmove[MAX_EDICTS]; + unsigned char runmove[MAX_EDICTS]; // let the progs know that a new frame has started prog->globals.server->self = PRVM_EDICT_TO_PROG(prog->edicts); @@ -1462,6 +1527,8 @@ void SV_Physics (void) prog->globals.server->frametime = sv.frametime; PRVM_ExecuteProgram (prog->globals.server->StartFrame, "QC function StartFrame is missing"); + // don't run a move on newly spawned projectiles as it messes up movement + // interpolation and rocket trails newnum_edicts = 0; for (i = 0, ent = prog->edicts;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) if ((runmove[i] = !ent->priv.server->free)) @@ -1472,33 +1539,32 @@ void SV_Physics (void) // treat each object in turn // - for (i = 0, ent = prog->edicts;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) - { - if (ent->priv.server->free) - continue; - - if (prog->globals.server->force_retouch) - SV_LinkEdict (ent, true); // force retouch even for stationary + // if force_retouch, relink all the entities + if (prog->globals.server->force_retouch > 0) + for (i = 1, ent = PRVM_EDICT_NUM(i);i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_LinkEdict (ent, true); // force retouch even for stationary - if (i >= 1 && i <= svs.maxclients) + // run physics on the client entities + for (i = 1, ent = PRVM_EDICT_NUM(i), host_client = svs.clients;i <= svs.maxclients;i++, ent = PRVM_NEXT_EDICT(ent), host_client++) + { + if (!ent->priv.server->free) { - host_client = svs.clients + i - 1; // don't do physics on disconnected clients, FrikBot relies on this if (!host_client->spawned) - { memset(&host_client->cmd, 0, sizeof(host_client->cmd)); - continue; - } - // connected slot - if (host_client->movesequence) - continue; // return if running asynchronously + // don't run physics here if running asynchronously + else if (!host_client->movesequence) + SV_Physics_ClientEntity(ent); } - else if (sv_freezenonclients.integer) - continue; - - SV_Physics_Entity(ent, runmove[i]); } + // run physics on all the non-client entities + if (!sv_freezenonclients.integer) + for (;i < prog->num_edicts;i++, ent = PRVM_NEXT_EDICT(ent)) + if (!ent->priv.server->free) + SV_Physics_Entity(ent, runmove[i]); + if (prog->globals.server->force_retouch > 0) prog->globals.server->force_retouch = max(0, prog->globals.server->force_retouch - 1);