/* ============== ClientThink This will be called once for each client frame, which will usually be a couple times for each server frame on fast clients. If "g_synchronousClients 1" is set, this will be called exactly once for each server frame, which makes for smooth demo recording. ============== */ void ClientThink_real(gentity_t *ent) { int msec, oldEventSequence; pmove_t pm; usercmd_t *ucmd; gclient_t *client = ent->client; // don't think if the client is not yet connected (and thus not yet spawned in) if (client->pers.connected != CON_CONNECTED) { return; } if (ent->s.eFlags & EF_MOUNTEDTANK) { client->pmext.centerangles[YAW] = ent->tagParent->r.currentAngles[YAW]; client->pmext.centerangles[PITCH] = ent->tagParent->r.currentAngles[PITCH]; } // mark the time, so the connection sprite can be removed ucmd = &ent->client->pers.cmd; ent->client->ps.identifyClient = ucmd->identClient; // NERVE - SMF // sanity check the command time to prevent speedup cheating if (ucmd->serverTime > level.time + 200) { ucmd->serverTime = level.time + 200; } if (ucmd->serverTime < level.time - 1000) { ucmd->serverTime = level.time - 1000; } msec = ucmd->serverTime - client->ps.commandTime; // following others may result in bad times, but we still want // to check for follow toggles if (msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW) { return; } if (msec > 200) { msec = 200; } // Nico, pmove_fixed if (client->pers.pmoveFixed) { ucmd->serverTime = ((ucmd->serverTime + pmove_msec.integer - 1) / pmove_msec.integer) * pmove_msec.integer; } if (client->wantsscore) { G_SendScore(ent); client->wantsscore = qfalse; } // check for inactivity timer, but never drop the local client of a non-dedicated server // OSP - moved here to allow for spec inactivity checks as well if (!ClientInactivityTimer(client)) { return; } if (!(ucmd->flags & 0x01) || ucmd->forwardmove || ucmd->rightmove || ucmd->upmove || ucmd->wbuttons || ucmd->doubleTap) { ent->r.svFlags &= ~(SVF_SELF_PORTAL_EXCLUSIVE | SVF_SELF_PORTAL); } // spectators don't do much // DHM - Nerve :: In limbo use SpectatorThink if (client->sess.sessionTeam == TEAM_SPECTATOR || client->ps.pm_flags & PMF_LIMBO) { SpectatorThink(ent, ucmd); return; } if (client->ps.eFlags & EF_VIEWING_CAMERA) { ucmd->buttons = 0; ucmd->forwardmove = 0; ucmd->rightmove = 0; ucmd->upmove = 0; ucmd->wbuttons = 0; ucmd->doubleTap = 0; // freeze player client->ps.pm_type = PM_FREEZE; } else if (client->noclip) { client->ps.pm_type = PM_NOCLIP; } else if (client->ps.stats[STAT_HEALTH] <= 0) { client->ps.pm_type = PM_DEAD; } else { client->ps.pm_type = PM_NORMAL; } client->ps.aiState = AISTATE_COMBAT; client->ps.gravity = DEFAULT_GRAVITY; client->ps.speed = DEFAULT_SPEED; if (client->speedScale) { // Goalitem speed scale client->ps.speed *= (client->speedScale * 0.01); } // set up for pmove oldEventSequence = client->ps.eventSequence; client->currentAimSpreadScale = (float)client->ps.aimSpreadScale / 255.0; memset(&pm, 0, sizeof (pm)); pm.ps = &client->ps; pm.pmext = &client->pmext; pm.character = client->pers.character; pm.cmd = *ucmd; pm.oldcmd = client->pers.oldcmd; // MrE: always use capsule for AI and player pm.trace = trap_TraceCapsule; // Nico, ghost players pm.tracemask = MASK_PLAYERSOLID & ~CONTENTS_BODY; if (pm.ps->pm_type == PM_DEAD) { pm.ps->eFlags |= EF_DEAD; } else if (pm.ps->pm_type == PM_SPECTATOR) { pm.trace = trap_TraceCapsuleNoEnts; } // Nico, end of ghost players //DHM - Nerve :: We've gone back to using normal bbox traces pm.pointcontents = trap_PointContents; pm.debugLevel = g_debugMove.integer; pm.noFootsteps = qfalse; // Nico, pmove_fixed // pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; pm.pmove_fixed = client->pers.pmoveFixed; pm.pmove_msec = pmove_msec.integer; // Nico, game physics pm.physics = physics.integer; pm.isTimerun = isTimerun.integer; pm.timerunActive = client->sess.timerunActive; pm.timerunStartTime = client->sess.timerunStartTime + 500; // Nico, store logins status in pmove if (client->sess.logged) { pm.isLogged = 1; } else { pm.isLogged = 0; } pm.noWeapClips = qfalse; VectorCopy(client->ps.origin, client->oldOrigin); // NERVE - SMF pm.ltChargeTime = level.lieutenantChargeTime[client->sess.sessionTeam - 1]; pm.soldierChargeTime = level.soldierChargeTime[client->sess.sessionTeam - 1]; pm.engineerChargeTime = level.engineerChargeTime[client->sess.sessionTeam - 1]; pm.medicChargeTime = level.medicChargeTime[client->sess.sessionTeam - 1]; // -NERVE - SMF client->pmext.airleft = ent->client->airOutTime - level.time; pm.covertopsChargeTime = level.covertopsChargeTime[client->sess.sessionTeam - 1]; // Gordon: bit hacky, stop the slight lag from client -> server even on locahost, switching back to the weapon you were holding // and then back to what weapon you should have, became VERY noticible for the kar98/carbine + gpg40, esp now i've added the // animation locking if (level.time - client->pers.lastSpawnTime < 1000) { pm.cmd.weapon = client->ps.weapon; } Pmove(&pm); // Gordon: thx to bani for this // ikkyo - fix leaning players bug VectorCopy(client->ps.velocity, ent->s.pos.trDelta); SnapVector(ent->s.pos.trDelta); // end // server cursor hints if (ent->lastHintCheckTime < level.time) { G_CheckForCursorHints(ent); ent->lastHintCheckTime = level.time + FRAMETIME; } // DHM - Nerve :: Set animMovetype to 1 if ducking if (ent->client->ps.pm_flags & PMF_DUCKED) { ent->s.animMovetype = 1; } else { ent->s.animMovetype = 0; } // save results of pmove if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; ent->r.eventTime = level.time; } // Ridah, fixes jittery zombie movement if (g_smoothClients.integer) { BG_PlayerStateToEntityStateExtraPolate(&ent->client->ps, &ent->s, level.time, qfalse); } else { BG_PlayerStateToEntityState(&ent->client->ps, &ent->s, qfalse); } if (!(ent->client->ps.eFlags & EF_FIRING)) { client->fireHeld = qfalse; // for grapple } // // use the snapped origin for linking so it matches client predicted versions VectorCopy(ent->s.pos.trBase, ent->r.currentOrigin); VectorCopy(pm.mins, ent->r.mins); VectorCopy(pm.maxs, ent->r.maxs); ent->waterlevel = pm.waterlevel; ent->watertype = pm.watertype; // execute client events ClientEvents(ent, oldEventSequence); // link entity now, after any personal teleporters have been used trap_LinkEntity(ent); if (!ent->client->noclip) { G_TouchTriggers(ent); } // NOTE: now copy the exact origin over otherwise clients can be snapped into solid VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); // touch other objects ClientImpacts(ent, &pm); // save results of triggers and client events if (ent->client->ps.eventSequence != oldEventSequence) { ent->eventTime = level.time; } // swap and latch button actions client->oldbuttons = client->buttons; client->buttons = ucmd->buttons; client->latched_buttons = client->buttons & ~client->oldbuttons; //----(SA) added client->oldwbuttons = client->wbuttons; client->wbuttons = ucmd->wbuttons; client->latched_wbuttons = client->wbuttons & ~client->oldwbuttons; // Rafael - Activate // Ridah, made it a latched event (occurs on keydown only) if (client->latched_buttons & BUTTON_ACTIVATE) { Cmd_Activate_f(ent); } if (g_entities[ent->client->ps.identifyClient].team != ent->team || !g_entities[ent->client->ps.identifyClient].client) { ent->client->ps.identifyClient = -1; } // check for respawning if (client->ps.stats[STAT_HEALTH] <= 0) { // Nico, forcing respawn limbo(ent); return; } // perform once-a-second actions ClientTimerActions(ent, msec); // Nico, check ping if (client->sess.timerunActive && client->ps.ping > MAX_PLAYER_PING) { CP(va("cpm \"%s^w: ^1too high ping detected, timerun stopped\n\"", GAME_VERSION_COLORED)); // Nico, notify the client and its spectators the timerun has stopped notify_timerun_stop(ent, 0); client->sess.timerunActive = qfalse; } // Nico, pmove_fixed if (!client->pers.pmoveFixed) { CP(va("cpm \"%s^w: ^1you were removed from teams because you can not use pmove_fixed 0\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "pmoveon"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check rate if (client->pers.rate < MIN_PLAYER_RATE_VALUE || client->pers.rate > MAX_PLAYER_RATE_VALUE) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use %d <= rate <= %d\n\"", GAME_VERSION_COLORED, MIN_PLAYER_RATE_VALUE, MAX_PLAYER_RATE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetRate"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check snaps (unsigned int) if (client->pers.snaps > MAX_PLAYER_SNAPS_VALUE) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use %d <= snaps <= %d\n\"", GAME_VERSION_COLORED, MIN_PLAYER_SNAPS_VALUE, MAX_PLAYER_SNAPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetSnaps"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check timenudge if (client->pers.clientTimeNudge != FORCED_PLAYER_TIMENUDGE_VALUE) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use cl_timenudge %d\n\"", GAME_VERSION_COLORED, FORCED_PLAYER_TIMENUDGE_VALUE)); trap_SendServerCommand(ent - g_entities, "resetTimeNudge"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check maxpackets if (client->pers.clientMaxPackets < MIN_PLAYER_MAX_PACKETS_VALUE || client->pers.clientMaxPackets > MAX_PLAYER_MAX_PACKETS_VALUE) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use %d <= cl_maxpackets <= %d\n\"", GAME_VERSION_COLORED, MIN_PLAYER_MAX_PACKETS_VALUE, MAX_PLAYER_MAX_PACKETS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxPackets"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, check max FPS if (client->pers.maxFPS < MIN_PLAYER_FPS_VALUE || client->pers.maxFPS > MAX_PLAYER_FPS_VALUE) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use %d <= com_maxfps <= %d\n\"", GAME_VERSION_COLORED, MIN_PLAYER_FPS_VALUE, MAX_PLAYER_FPS_VALUE)); trap_SendServerCommand(ent - g_entities, "resetMaxFPS"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force auto demo record in cup mode if (g_cupMode.integer != 0 && client->pers.autoDemo == 0) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use cg_autoDemo 1\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "autoDemoOn"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force hide me in cup mode if (g_cupMode.integer != 0 && client->pers.hideme == 0) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use cg_hideMe 1\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "hideMeOn"); SetTeam(ent, "s", -1, -1, qfalse); } // Nico, force CGaz 0 in cup mode if (g_cupMode.integer != 0 && client->pers.cgaz != 0) { CP(va("cpm \"%s^w: ^1you were removed from teams because you must use cg_drawCGaz 0\n\"", GAME_VERSION_COLORED)); trap_SendServerCommand(ent - g_entities, "CGazOff"); SetTeam(ent, "s", -1, -1, qfalse); } }
/* QUAKED target_stopTimer (1 0 0) (-8 -8 -8) (8 8 8) * timer stop * * "name" timerun name * "minCheckpoints" minimal passed checkpoints to activate this stoptimer */ void target_stoptimer_use(gentity_t *self, gentity_t *other, gentity_t *activator) { int time; gclient_t *client = activator->client; int timerunNum; // Nico, silent GCC (void)other; if (!client->sess.timerunActive) { return; } // don't stop the time if this isn't a corresponding stoptimer if (Q_stricmp(self->timerunName, client->sess.currentTimerun)) { return; } timerunNum = client->sess.currentTimerunNum; // required number of checkpoints passed? if (client->sess.timerunCheckpointsPassed < self->count) { CPx(activator - g_entities, va("cpm \"^d%s^f:^1 Minimum checkpoints not passed (%d/%d)\n\"", client->sess.currentTimerun, client->sess.timerunCheckpointsPassed, self->count)); notify_timerun_stop(activator, 0); client->sess.timerunActive = qfalse; return; } time = client->sess.timerunLastTime[timerunNum] = client->ps.commandTime - client->sess.timerunStartTime; if (!client->sess.timerunBestTime[timerunNum] || time < client->sess.timerunBestTime[timerunNum]) { // best personal for this session if (client->sess.logged) { client->sess.timerunBestTime[timerunNum] = time; // Nico, update best speed of run client->sess.timerunBestSpeed[timerunNum] = client->sess.maxSpeed; // Nico, set score so that xfire can see it (only if cup mode is DISABLED) if (g_cupMode.integer != 1) { client->ps.persistant[PERS_SCORE] = client->sess.timerunLastTime[timerunNum]; } } // CP are updated here if API is not used or if CP were note loaded if (!g_useAPI.integer || client->sess.timerunCheckpointWereLoaded[timerunNum] == 0) { memcpy(client->sess.timerunBestCheckpointTimes[timerunNum], client->sess.timerunCheckpointTimes, sizeof (client->sess.timerunCheckpointTimes)); } } // Nico, stop speed client->sess.stopSpeed = (int)sqrt(client->ps.velocity[0] * client->ps.velocity[0] + client->ps.velocity[1] * client->ps.velocity[1]); // Nico, send record if needed if (g_useAPI.integer && client->sess.logged) { Cmd_SendRecord_f(activator, client->sess.currentTimerun, client->pers.authToken, time, client->sess.startSpeed, client->sess.stopSpeed, client->sess.maxSpeed, client->ps.identifyClientHealth, // Nico, this is used as a jumps counter client->pers.ip, client->pers.maxFPS, client->pers.clientTimeNudge, client->pers.rate, client->pers.clientMaxPackets, client->pers.snaps, g_strictSaveLoad.integer, g_disableDrowning.integer, g_holdDoorsOpen.integer, g_enableMapEntities.integer); } else { // Nico, API is not used and/or client is not logged, // we cannnot know if his last time his SB/PB or something // else. So we keep his last demo. saveDemo(activator); } // Nico, notify the client and its spectators the timerun has stopped notify_timerun_stop(activator, client->sess.timerunLastTime[timerunNum]); client->sess.timerunActive = qfalse; }
/* =========== ClientSpawn Called every time a client is placed fresh in the world: after the first ClientBegin, and after each respawn Initializes all non-persistant parts of playerState ============ */ void ClientSpawn(gentity_t *ent) { int index; vec3_t spawn_origin, spawn_angles; gclient_t *client; int i; clientPersistant_t saved; clientSession_t savedSess; int persistant[MAX_PERSISTANT]; gentity_t *spawnPoint; int flags; int savedPing; int savedTeam; qboolean update = qfalse; save_position_t *pos = NULL; index = ent - g_entities; client = ent->client; G_UpdateSpawnCounts(); client->pers.lastSpawnTime = level.time; if (client->sess.sessionTeam != TEAM_AXIS && client->sess.sessionTeam != TEAM_ALLIES) { spawnPoint = SelectSpectatorSpawnPoint(spawn_origin, spawn_angles); } else { spawnPoint = SelectPlayerSpawnPoint(client->sess.sessionTeam, spawn_origin, spawn_angles, client->sess.spawnObjectiveIndex); } client->pers.teamState.state = TEAM_ACTIVE; // toggle the teleport bit so the client knows to not lerp flags = ent->client->ps.eFlags & EF_TELEPORT_BIT; flags ^= EF_TELEPORT_BIT; flags |= (client->ps.eFlags & EF_VOTED); // clear everything but the persistant data ent->s.eFlags &= ~EF_MOUNTEDTANK; // Nico, notify timerun_stop notify_timerun_stop(ent, 0); ent->client->sess.timerunActive = qfalse; saved = client->pers; savedSess = client->sess; savedPing = client->ps.ping; savedTeam = client->ps.teamNum; for (i = 0 ; i < MAX_PERSISTANT ; i++) { persistant[i] = client->ps.persistant[i]; } memset(client, 0, sizeof (*client)); client->pers = saved; client->sess = savedSess; client->ps.ping = savedPing; client->ps.teamNum = savedTeam; for (i = 0 ; i < MAX_PERSISTANT ; i++) { client->ps.persistant[i] = persistant[i]; } // increment the spawncount so the client will detect the respawn client->ps.persistant[PERS_SPAWN_COUNT]++; client->ps.persistant[PERS_TEAM] = client->sess.sessionTeam; client->ps.persistant[PERS_HWEAPON_USE] = 0; client->airOutTime = level.time + 12000; // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->ps.eFlags = flags; ent->s.groundEntityNum = ENTITYNUM_NONE; ent->client = &level.clients[index]; ent->takedamage = qtrue; ent->inuse = qtrue; ent->classname = "player"; ent->r.contents = CONTENTS_BODY; ent->clipmask = MASK_PLAYERSOLID; // DHM - Nerve :: Init to -1 on first spawn; ent->props_frame_state = -1; ent->die = player_die; ent->waterlevel = 0; ent->watertype = 0; ent->flags = 0; VectorCopy(playerMins, ent->r.mins); VectorCopy(playerMaxs, ent->r.maxs); // Ridah, setup the bounding boxes and viewheights for prediction VectorCopy(ent->r.mins, client->ps.mins); VectorCopy(ent->r.maxs, client->ps.maxs); client->ps.crouchViewHeight = CROUCH_VIEWHEIGHT; client->ps.standViewHeight = DEFAULT_VIEWHEIGHT; client->ps.deadViewHeight = DEAD_VIEWHEIGHT; client->ps.crouchMaxZ = client->ps.maxs[2] - (client->ps.standViewHeight - client->ps.crouchViewHeight); client->ps.runSpeedScale = 0.8; client->ps.sprintSpeedScale = 1.1; client->ps.crouchSpeedScale = 0.25; client->ps.weaponstate = WEAPON_READY; // Rafael client->ps.friction = 1.0; // done. // TTimo // retrieve from the persistant storage (we use this in pmoveExt_t beause we need it in bg_*) client->pmext.bAutoReload = client->pers.bAutoReloadAux; // done client->ps.clientNum = index; trap_GetUsercmd(client - level.clients, &ent->client->pers.cmd); // NERVE - SMF - moved this up here if (client->sess.playerType != client->sess.latchPlayerType) { update = qtrue; } client->sess.playerType = client->sess.latchPlayerType; if (client->sess.playerWeapon != client->sess.latchPlayerWeapon) { client->sess.playerWeapon = client->sess.latchPlayerWeapon; update = qtrue; } client->sess.playerWeapon2 = client->sess.latchPlayerWeapon2; if (update) { ClientUserinfoChanged(index); } G_UpdateCharacter(client); SetWolfSpawnWeapons(client); client->pers.maxHealth = 125; client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; client->pers.cmd.weapon = ent->client->ps.weapon; // dhm - end ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; G_SetOrigin(ent, spawn_origin); VectorCopy(spawn_origin, client->ps.origin); // the respawned flag will be cleared after the attack and jump keys come up client->ps.pm_flags |= PMF_RESPAWNED; SetClientViewAngle(ent, spawn_angles); if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { trap_LinkEntity(ent); } client->inactivityTime = level.time + g_inactivity.integer * 1000; client->latched_buttons = 0; client->latched_wbuttons = 0; //----(SA) added // fire the targets of the spawn point G_UseTargets(spawnPoint, ent); // run a client frame to drop exactly to the floor, // initialize animations and other things client->ps.commandTime = level.time - 100; ent->client->pers.cmd.serverTime = level.time; ClientThink(ent - g_entities); // positively link the client, even if the command times are weird if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue); VectorCopy(ent->client->ps.origin, ent->r.currentOrigin); trap_LinkEntity(ent); } // run the presend to set anything else ClientEndFrame(ent); // set idle animation on weapon ent->client->ps.weapAnim = ((ent->client->ps.weapAnim & ANIM_TOGGLEBIT) ^ ANIM_TOGGLEBIT) | PM_IdleAnimForWeapon(ent->client->ps.weapon); // clear entity state values BG_PlayerStateToEntityState(&client->ps, &ent->s, qtrue); // show_bug.cgi?id=569 G_ResetMarkers(ent); // RF, start the scripting system if (client->sess.sessionTeam != TEAM_SPECTATOR) { // RF, call entity scripting event G_Script_ScriptEvent(ent, "playerstart", ""); } // Nico, autoload position if (ent->client->pers.autoLoad && !ent->client->sess.lastDieWasASelfkill && (ent->client->sess.sessionTeam == TEAM_AXIS || ent->client->sess.sessionTeam == TEAM_ALLIES)) { if (ent->client->sess.sessionTeam == TEAM_ALLIES) { pos = ent->client->sess.alliesSaves; } else { pos = ent->client->sess.axisSaves; } if (pos->valid) { VectorCopy(pos->origin, ent->client->ps.origin); // Nico, load angles if cg_loadViewAngles = 1 if (ent->client->pers.loadViewAngles) { SetClientViewAngle(ent, pos->vangles); } // Nico, load saved weapon if cg_loadWeapon = 1 if (ent->client->pers.loadWeapon) { ent->client->ps.weapon = pos->weapon; } VectorClear(ent->client->ps.velocity); if (ent->client->ps.stats[STAT_HEALTH] < 100 && ent->client->ps.stats[STAT_HEALTH] > 0) { ent->health = 100; } } } }