3 #include <common/csqcmodel_settings.qh>
4 #include <common/deathtypes/all.qh>
5 #include <common/debug.qh>
6 #include <common/effects/all.qh>
7 #include <common/effects/qc/globalsound.qh>
8 #include <common/ent_cs.qh>
9 #include <common/gamemodes/_mod.qh>
10 #include <common/gamemodes/gamemode/nexball/sv_nexball.qh>
11 #include <common/items/_mod.qh>
12 #include <common/items/inventory.qh>
13 #include <common/mapobjects/func/conveyor.qh>
14 #include <common/mapobjects/func/ladder.qh>
15 #include <common/mapobjects/subs.qh>
16 #include <common/mapobjects/target/spawnpoint.qh>
17 #include <common/mapobjects/teleporters.qh>
18 #include <common/mapobjects/trigger/counter.qh>
19 #include <common/mapobjects/trigger/secret.qh>
20 #include <common/mapobjects/trigger/swamp.qh>
21 #include <common/mapobjects/triggers.qh>
22 #include <common/minigames/sv_minigames.qh>
23 #include <common/monsters/sv_monsters.qh>
24 #include <common/mutators/mutator/instagib/sv_instagib.qh>
25 #include <common/mutators/mutator/nades/nades.qh>
26 #include <common/mutators/mutator/overkill/oknex.qh>
27 #include <common/mutators/mutator/status_effects/_mod.qh>
28 #include <common/mutators/mutator/waypoints/all.qh>
29 #include <common/net_linked.qh>
30 #include <common/net_notice.qh>
31 #include <common/notifications/all.qh>
32 #include <common/physics/player.qh>
33 #include <common/playerstats.qh>
34 #include <common/resources/sv_resources.qh>
35 #include <common/state.qh>
36 #include <common/stats.qh>
37 #include <common/vehicles/all.qh>
38 #include <common/vehicles/sv_vehicles.qh>
39 #include <common/viewloc.qh>
40 #include <common/weapons/_all.qh>
41 #include <common/weapons/weapon/vortex.qh>
42 #include <common/wepent.qh>
43 #include <lib/csqcmodel/sv_model.qh>
44 #include <lib/warpzone/common.qh>
45 #include <lib/warpzone/server.qh>
46 #include <server/anticheat.qh>
47 #include <server/antilag.qh>
48 #include <server/bot/api.qh>
49 #include <server/bot/default/cvars.qh>
50 #include <server/bot/default/waypoints.qh>
51 #include <server/campaign.qh>
52 #include <server/chat.qh>
53 #include <server/cheats.qh>
54 #include <server/clientkill.qh>
55 #include <server/command/banning.qh>
56 #include <server/command/cmd.qh>
57 #include <server/command/common.qh>
58 #include <server/command/vote.qh>
59 #include <server/compat/quake3.qh>
60 #include <server/damage.qh>
61 #include <server/gamelog.qh>
62 #include <server/handicap.qh>
63 #include <server/hook.qh>
64 #include <server/impulse.qh>
65 #include <server/intermission.qh>
66 #include <server/ipban.qh>
67 #include <server/main.qh>
68 #include <server/mutators/_mod.qh>
69 #include <server/player.qh>
70 #include <server/portals.qh>
71 #include <server/race.qh>
72 #include <server/scores.qh>
73 #include <server/scores_rules.qh>
74 #include <server/spawnpoints.qh>
75 #include <server/teamplay.qh>
76 #include <server/weapons/accuracy.qh>
77 #include <server/weapons/common.qh>
78 #include <server/weapons/hitplot.qh>
79 #include <server/weapons/selection.qh>
80 #include <server/weapons/tracing.qh>
81 #include <server/weapons/weaponsystem.qh>
82 #include <server/world.qh>
84 STATIC_METHOD(Client, Add, void(Client this, int _team))
87 TRANSMUTE(Player, this);
90 PutClientInServer(this);
93 STATIC_METHOD(Client, Remove, void(Client this))
95 TRANSMUTE(Observer, this);
96 PutClientInServer(this);
97 ClientDisconnect(this);
100 void send_CSQC_teamnagger() {
101 WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER);
104 int CountSpectators(entity player, entity to)
106 if(!player) { return 0; } // not sure how, but best to be safe
110 FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player,
118 void WriteSpectators(entity player, entity to)
120 if(!player) { return; } // not sure how, but best to be safe
123 FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player,
125 if(spec_count >= MAX_SPECTATORS)
127 WriteByte(MSG_ENTITY, num_for_edict(it));
132 bool ClientData_Send(entity this, entity to, int sf)
134 assert(to == this.owner, return false);
137 if (IS_SPEC(e)) e = e.enemy;
140 if (CS(e).race_completed) sf |= BIT(0); // forced scoreboard
141 if (CS(to).spectatee_status) sf |= BIT(1); // spectator ent number follows
142 if (CS(e).zoomstate) sf |= BIT(2); // zoomed
143 if (observe_blocked_if_eliminated && INGAME(to))
144 sf |= BIT(3); // observing blocked
145 if (autocvar_sv_showspectators == 1 || (autocvar_sv_showspectators && IS_SPEC(to)))
146 sf |= BIT(4); // show spectators
148 WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
149 WriteByte(MSG_ENTITY, sf);
152 WriteByte(MSG_ENTITY, CS(to).spectatee_status);
156 float specs = CountSpectators(e, to);
157 WriteByte(MSG_ENTITY, specs);
158 WriteSpectators(e, to);
164 void ClientData_Attach(entity this)
166 Net_LinkEntity(CS(this).clientdata = new_pure(clientdata), false, 0, ClientData_Send);
167 CS(this).clientdata.drawonlytoclient = this;
168 CS(this).clientdata.owner = this;
171 void ClientData_Detach(entity this)
173 delete(CS(this).clientdata);
174 CS(this).clientdata = NULL;
177 void ClientData_Touch(entity e)
179 entity cd = CS(e).clientdata;
180 if (cd) { cd.SendFlags = 1; }
182 // make it spectatable
183 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != e && IS_SPEC(it) && it.enemy == e,
185 entity cd = CS(it).clientdata;
186 if (cd) { cd.SendFlags = 1; }
195 Checks if the argument string can be a valid playermodel.
196 Returns a valid one in doubt.
199 string FallbackPlayerModel;
200 string CheckPlayerModel(string plyermodel) {
201 if(FallbackPlayerModel != cvar_defstring("_cl_playermodel"))
203 // note: we cannot summon Don Strunzone here, some player may
204 // still have the model string set. In case anyone manages how
205 // to change a cvar default, we'll have a small leak here.
206 FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel"));
208 // only in right path
209 if(substring(plyermodel, 0, 14) != "models/player/")
210 return FallbackPlayerModel;
211 // only good file extensions
212 if(substring(plyermodel, -4, 4) != ".iqm"
213 && substring(plyermodel, -4, 4) != ".zym"
214 && substring(plyermodel, -4, 4) != ".dpm"
215 && substring(plyermodel, -4, 4) != ".md3"
216 && substring(plyermodel, -4, 4) != ".psk")
218 return FallbackPlayerModel;
220 // forbid the LOD models
221 if(substring(plyermodel, -9, 5) == "_lod1" || substring(plyermodel, -9, 5) == "_lod2")
222 return FallbackPlayerModel;
223 if(plyermodel != strtolower(plyermodel))
224 return FallbackPlayerModel;
225 // also, restrict to server models
226 if(autocvar_sv_servermodelsonly)
228 if(!fexists(plyermodel))
229 return FallbackPlayerModel;
234 void setplayermodel(entity e, string modelname)
236 precache_model(modelname);
237 _setmodel(e, modelname);
238 player_setupanimsformodel(e);
239 if(!autocvar_g_debug_globalsounds)
240 UpdatePlayerSounds(e);
243 /** putting a client as observer in the server */
244 void PutObserverInServer(entity this, bool is_forced, bool use_spawnpoint)
246 bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this, is_forced);
247 bool recount_ready = false;
248 PlayerState_detach(this);
252 if(GetResource(this, RES_HEALTH) >= 1)
255 Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
258 // was a player, recount votes and ready status
259 if(IS_REAL_CLIENT(this))
261 if (vote_called) { VoteCount(false); }
263 if (warmup_stage || game_starttime > time) recount_ready = true;
265 entcs_update_players(this);
270 entity spot = SelectSpawnPoint(this, true);
271 if (!spot) LOG_FATAL("No spawnpoints for observers?!?");
272 this.angles = vec2(spot.angles);
273 // offset it so that the spectator spawns higher off the ground, looks better this way
274 setorigin(this, spot.origin + STAT(PL_VIEW_OFS, this));
276 else // change origin to restore previous view origin
277 setorigin(this, this.origin + STAT(PL_VIEW_OFS, this) - STAT(PL_CROUCH_VIEW_OFS, this));
278 this.fixangle = true;
280 if (IS_REAL_CLIENT(this))
283 WriteByte(MSG_ONE, SVC_SETVIEW);
284 WriteEntity(MSG_ONE, this);
286 // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY
287 // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS"
288 if(!autocvar_g_debug_globalsounds)
290 // needed for player sounds
292 FixPlayermodel(this);
294 setmodel(this, MDL_Null);
295 setsize(this, STAT(PL_CROUCH_MIN, this), STAT(PL_CROUCH_MAX, this));
296 this.view_ofs = '0 0 0';
298 RemoveGrapplingHooks(this);
299 Portal_ClearAll(this);
300 Unfreeze(this, false);
301 SetSpectatee(this, NULL);
306 PlayerStats_GameReport_Event_Player(this, PLAYERSTATS_ALIVETIME, time - this.alivetime);
310 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
312 TRANSMUTE(Observer, this);
314 if(recount_ready) ReadyCount(); // FIXME: please add comment about why this is delayed
316 WaypointSprite_PlayerDead(this);
317 accuracy_resend(this);
319 if (CS(this).killcount != FRAGS_SPECTATOR && !game_stopped && CHAT_NOSPECTATORS())
320 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
322 CS(this).spectatortime = time;
324 IL_REMOVE(g_bot_targets, this);
325 this.bot_attack = false;
326 if(this.monster_attack)
327 IL_REMOVE(g_monster_targets, this);
328 this.monster_attack = false;
329 STAT(HUD, this) = HUD_NORMAL;
330 this.iscreature = false;
331 this.teleportable = TELEPORT_SIMPLE;
332 if(this.damagedbycontents)
333 IL_REMOVE(g_damagedbycontents, this);
334 this.damagedbycontents = false;
335 SetResourceExplicit(this, RES_HEALTH, FRAGS_SPECTATOR);
336 SetSpectatee_status(this, etof(this));
337 this.takedamage = DAMAGE_NO;
338 this.solid = SOLID_NOT;
339 set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink
340 this.flags = FL_CLIENT | FL_NOTARGET;
342 SetResourceExplicit(this, RES_ARMOR, autocvar_g_balance_armor_start); // was 666?!
343 this.pauserotarmor_finished = 0;
344 this.pauserothealth_finished = 0;
345 this.pauseregen_finished = 0;
346 this.damageforcescale = 0;
348 this.respawn_flags = 0;
349 this.respawn_time = 0;
350 STAT(RESPAWN_TIME, this) = 0;
354 this.pain_finished = 0;
355 STAT(AIR_FINISHED, this) = 0;
356 //this.dphitcontentsmask = 0;
357 this.dphitcontentsmask = DPCONTENTS_SOLID;
358 if (autocvar_g_playerclip_collisions)
359 this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
362 setthink(this, func_null);
364 this.deadflag = DEAD_NO;
366 STAT(REVIVE_PROGRESS, this) = 0;
367 this.revival_time = 0;
368 this.draggable = drag_undraggable;
370 player_powerups_remove_all(this);
372 STAT(WEAPONS, this) = '0 0 0';
373 this.drawonlytoclient = this;
377 //this.spawnpoint_targ = NULL; // keep it so they can return to where they were?
379 this.weaponmodel = "";
380 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
382 this.weaponentities[slot] = NULL;
384 this.exteriorweaponentity = NULL;
385 CS(this).killcount = FRAGS_SPECTATOR;
386 this.velocity = '0 0 0';
387 this.avelocity = '0 0 0';
388 this.punchangle = '0 0 0';
389 this.punchvector = '0 0 0';
390 this.oldvelocity = this.velocity;
391 this.event_damage = func_null;
392 this.event_heal = func_null;
394 for(int slot = 0; slot < MAX_AXH; ++slot)
396 entity axh = this.(AuxiliaryXhair[slot]);
397 this.(AuxiliaryXhair[slot]) = NULL;
399 if(axh.owner == this && axh != NULL && !wasfreed(axh))
403 if (mutator_returnvalue)
405 // mutator prevents resetting teams+score
409 SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); // clears scores too in game modes without teams
410 this.frags = FRAGS_SPECTATOR;
413 bot_relinkplayerlist();
415 if (CS(this).just_joined)
416 CS(this).just_joined = false;
419 int player_getspecies(entity this)
421 get_model_parameters(this.model, this.skin);
422 int s = get_model_parameters_species;
423 get_model_parameters(string_null, 0);
424 if (s < 0) return SPECIES_HUMAN;
428 .float model_randomizer;
429 void FixPlayermodel(entity player)
431 string defaultmodel = "";
433 if(autocvar_sv_defaultcharacter)
439 case NUM_TEAM_1: defaultmodel = autocvar_sv_defaultplayermodel_red; defaultskin = autocvar_sv_defaultplayerskin_red; break;
440 case NUM_TEAM_2: defaultmodel = autocvar_sv_defaultplayermodel_blue; defaultskin = autocvar_sv_defaultplayerskin_blue; break;
441 case NUM_TEAM_3: defaultmodel = autocvar_sv_defaultplayermodel_yellow; defaultskin = autocvar_sv_defaultplayerskin_yellow; break;
442 case NUM_TEAM_4: defaultmodel = autocvar_sv_defaultplayermodel_pink; defaultskin = autocvar_sv_defaultplayerskin_pink; break;
446 if(defaultmodel == "")
448 defaultmodel = autocvar_sv_defaultplayermodel;
449 defaultskin = autocvar_sv_defaultplayerskin;
452 int n = tokenize_console(defaultmodel);
455 defaultmodel = argv(floor(n * CS(player).model_randomizer));
456 // However, do NOT randomize if the player-selected model is in the list.
457 for (int i = 0; i < n; ++i)
458 if ((argv(i) == player.playermodel && defaultskin == stof(player.playerskin)) || argv(i) == strcat(player.playermodel, ":", player.playerskin))
459 defaultmodel = argv(i);
462 int i = strstrofs(defaultmodel, ":", 0);
465 defaultskin = stof(substring(defaultmodel, i+1, -1));
466 defaultmodel = substring(defaultmodel, 0, i);
469 if(autocvar_sv_defaultcharacterskin && !defaultskin)
475 case NUM_TEAM_1: defaultskin = autocvar_sv_defaultplayerskin_red; break;
476 case NUM_TEAM_2: defaultskin = autocvar_sv_defaultplayerskin_blue; break;
477 case NUM_TEAM_3: defaultskin = autocvar_sv_defaultplayerskin_yellow; break;
478 case NUM_TEAM_4: defaultskin = autocvar_sv_defaultplayerskin_pink; break;
483 defaultskin = autocvar_sv_defaultplayerskin;
486 MUTATOR_CALLHOOK(FixPlayermodel, defaultmodel, defaultskin, player);
487 defaultmodel = M_ARGV(0, string);
488 defaultskin = M_ARGV(1, int);
492 if(defaultmodel != "")
494 if (defaultmodel != player.model)
496 vector m1 = player.mins;
497 vector m2 = player.maxs;
498 setplayermodel (player, defaultmodel);
499 setsize (player, m1, m2);
503 oldskin = player.skin;
504 player.skin = defaultskin;
506 if (player.playermodel != player.model || player.playermodel == "")
508 player.playermodel = CheckPlayerModel(player.playermodel); // this is never "", so no endless loop
509 vector m1 = player.mins;
510 vector m2 = player.maxs;
511 setplayermodel (player, player.playermodel);
512 setsize (player, m1, m2);
516 if(!autocvar_sv_defaultcharacterskin)
518 oldskin = player.skin;
519 player.skin = stof(player.playerskin);
523 oldskin = player.skin;
524 player.skin = defaultskin;
528 if(chmdl || oldskin != player.skin) // model or skin has changed
530 player.species = player_getspecies(player); // update species
531 if(!autocvar_g_debug_globalsounds)
532 UpdatePlayerSounds(player); // update skin sounds
536 if(strlen(autocvar_sv_defaultplayercolors))
537 if(player.clientcolors != stof(autocvar_sv_defaultplayercolors))
538 setcolor(player, stof(autocvar_sv_defaultplayercolors));
541 void GiveWarmupResources(entity this)
543 SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
544 SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
545 SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
546 SetResource(this, RES_CELLS, warmup_start_ammo_cells);
547 SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
548 SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
549 SetResource(this, RES_HEALTH, warmup_start_health);
550 SetResource(this, RES_ARMOR, warmup_start_armorvalue);
551 STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
554 void PutPlayerInServer(entity this)
556 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
558 PlayerState_attach(this);
559 accuracy_resend(this);
561 if (teamplay && this.bot_forced_team)
562 SetPlayerTeam(this, this.bot_forced_team, TEAM_CHANGE_MANUAL);
565 TeamBalance_JoinBestTeam(this);
567 entity spot = SelectSpawnPoint(this, false);
569 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_NOSPAWNS);
570 return; // spawn failed
573 TRANSMUTE(Player, this);
575 CS(this).wasplayer = true;
576 this.iscreature = true;
577 this.teleportable = TELEPORT_NORMAL;
578 if(!this.damagedbycontents)
579 IL_PUSH(g_damagedbycontents, this);
580 this.damagedbycontents = true;
581 set_movetype(this, MOVETYPE_WALK);
582 this.solid = SOLID_SLIDEBOX;
583 this.dphitcontentsmask = DPCONTENTS_BODY | DPCONTENTS_SOLID;
584 if (autocvar_g_playerclip_collisions)
585 this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
586 if (IS_BOT_CLIENT(this) && autocvar_g_botclip_collisions)
587 this.dphitcontentsmask |= DPCONTENTS_BOTCLIP;
588 this.frags = FRAGS_PLAYER;
589 if (INDEPENDENT_PLAYERS) MAKE_INDEPENDENT_PLAYER(this);
590 this.flags = FL_CLIENT | FL_PICKUPITEMS;
591 if (autocvar__notarget)
592 this.flags |= FL_NOTARGET;
593 this.takedamage = DAMAGE_AIM;
594 this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
597 GiveWarmupResources(this);
600 SetResource(this, RES_SHELLS, start_ammo_shells);
601 SetResource(this, RES_BULLETS, start_ammo_nails);
602 SetResource(this, RES_ROCKETS, start_ammo_rockets);
603 SetResource(this, RES_CELLS, start_ammo_cells);
604 SetResource(this, RES_PLASMA, start_ammo_plasma);
605 SetResource(this, RES_FUEL, start_ammo_fuel);
606 SetResource(this, RES_HEALTH, start_health);
607 SetResource(this, RES_ARMOR, start_armorvalue);
608 STAT(WEAPONS, this) = start_weapons;
609 if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false)
611 GiveRandomWeapons(this, random_start_weapons_count,
612 autocvar_g_random_start_weapons, random_start_ammo);
615 SetSpectatee_status(this, 0);
617 PS(this).dual_weapons = '0 0 0';
619 if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
620 StatusEffects_apply(STATUSEFFECT_Superweapons, this, time + autocvar_g_balance_superweapons_time, 0);
622 this.items = start_items;
624 float shieldtime = time + autocvar_g_spawnshieldtime;
626 this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn;
627 this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn;
628 this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn;
629 this.pauseregen_finished = time + autocvar_g_balance_pause_health_regen_spawn;
630 if (!sv_ready_restart_after_countdown && time < game_starttime)
632 float f = game_starttime - time;
634 this.pauserotarmor_finished += f;
635 this.pauserothealth_finished += f;
636 this.pauseregen_finished += f;
639 StatusEffects_apply(STATUSEFFECT_SpawnShield, this, shieldtime, 0);
641 this.damageforcescale = autocvar_g_player_damageforcescale;
643 this.respawn_flags = 0;
644 this.respawn_time = 0;
645 STAT(RESPAWN_TIME, this) = 0;
646 // DP model scaling uses 1/16 accuracy and 13/16 is closest to 56/69
647 this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) ? 0.8125 : autocvar_sv_player_scale);
649 this.pain_finished = 0;
651 setthink(this, func_null); // players have no think function
654 PS(this).ballistics_density = autocvar_g_ballistics_density_player;
656 this.deadflag = DEAD_NO;
658 this.angles = spot.angles;
659 this.angles_z = 0; // never spawn tilted even if the spot says to
660 if (IS_BOT_CLIENT(this))
662 this.v_angle = this.angles;
665 this.fixangle = true; // turn this way immediately
666 this.oldvelocity = this.velocity = '0 0 0';
667 this.avelocity = '0 0 0';
668 this.punchangle = '0 0 0';
669 this.punchvector = '0 0 0';
671 STAT(REVIVE_PROGRESS, this) = 0;
672 this.revival_time = 0;
674 STAT(AIR_FINISHED, this) = 0;
675 this.waterlevel = WATERLEVEL_NONE;
676 this.watertype = CONTENT_EMPTY;
678 entity spawnevent = new_pure(spawnevent);
679 spawnevent.owner = this;
680 Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
682 // Cut off any still running player sounds.
683 stopsound(this, CH_PLAYER_SINGLE);
686 FixPlayermodel(this);
687 this.drawonlytoclient = NULL;
691 for(int slot = 0; slot < MAX_AXH; ++slot)
693 entity axh = this.(AuxiliaryXhair[slot]);
694 this.(AuxiliaryXhair[slot]) = NULL;
696 if(axh.owner == this && axh != NULL && !wasfreed(axh))
700 this.spawnpoint_targ = NULL;
703 this.view_ofs = STAT(PL_VIEW_OFS, this);
704 setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
705 this.spawnorigin = spot.origin;
706 setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
707 // don't reset back to last position, even if new position is stuck in solid
708 this.oldorigin = this.origin;
710 IL_REMOVE(g_conveyed, this);
711 this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
713 IL_REMOVE(g_swamped, this);
714 this.swampslug = NULL;
715 this.swamp_interval = 0;
716 if(this.ladder_entity)
717 IL_REMOVE(g_ladderents, this);
718 this.ladder_entity = NULL;
719 IL_EACH(g_counters, it.realowner == this,
723 STAT(HUD, this) = HUD_NORMAL;
725 this.event_damage = PlayerDamage;
726 this.event_heal = PlayerHeal;
728 this.draggable = func_null;
731 IL_PUSH(g_bot_targets, this);
732 this.bot_attack = true;
733 if(!this.monster_attack)
734 IL_PUSH(g_monster_targets, this);
735 this.monster_attack = true;
736 navigation_dynamicgoal_init(this, false);
738 PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
740 // player was spectator
741 if (CS(this).killcount == FRAGS_SPECTATOR) {
742 PlayerScore_Clear(this);
743 CS(this).killcount = 0;
744 CS(this).startplaytime = time;
747 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
749 .entity weaponentity = weaponentities[slot];
750 CL_SpawnWeaponentity(this, weaponentity);
752 this.alpha = default_player_alpha;
753 this.colormod = '1 1 1' * autocvar_g_player_brightness;
754 this.exteriorweaponentity.alpha = default_weapon_alpha;
756 this.speedrunning = false;
758 this.counter_cnt = 0;
759 this.fragsfilter_cnt = 0;
761 target_voicescript_clear(this);
763 // reset fields the weapons may use
764 FOREACH(Weapons, true, {
765 it.wr_resetplayer(it, this);
766 // reload all reloadable weapons
767 if (it.spawnflags & WEP_FLAG_RELOADABLE) {
768 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
770 .entity weaponentity = weaponentities[slot];
771 this.(weaponentity).weapon_load[it.m_id] = it.reloading_ammo;
776 Unfreeze(this, false);
778 MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
780 string s = spot.target;
781 if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
782 spot.target = string_null;
783 SUB_UseTargets(spot, this, NULL);
784 if(g_assault || g_race)
788 if (autocvar_spawn_debug)
790 sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n"));
791 delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
794 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
796 .entity weaponentity = weaponentities[slot];
797 entity w_ent = this.(weaponentity);
798 if(slot == 0 || autocvar_g_weaponswitch_debug == 1)
799 w_ent.m_switchweapon = w_getbestweapon(this, weaponentity);
801 w_ent.m_switchweapon = WEP_Null;
802 w_ent.m_weapon = WEP_Null;
803 w_ent.weaponname = "";
804 w_ent.m_switchingweapon = WEP_Null;
808 MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
810 if (CS(this).impulse) ImpulseCommands(this);
812 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
813 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
815 .entity weaponentity = weaponentities[slot];
816 W_WeaponFrame(this, weaponentity);
819 if (!warmup_stage && !this.alivetime)
820 this.alivetime = time;
822 antilag_clear(this, CS(this));
824 if (warmup_stage < 0 || warmup_stage > 1)
828 /** Called when a client spawns in the server */
829 void PutClientInServer(entity this)
831 if (IS_REAL_CLIENT(this)) {
833 WriteByte(MSG_ONE, SVC_SETVIEW);
834 WriteEntity(MSG_ONE, this);
837 TRANSMUTE(Observer, this);
839 bool use_spawnpoint = (!this.enemy); // check this.enemy here since SetSpectatee will clear it
840 SetSpectatee(this, NULL);
844 PS(this).itemkeys = 0;
846 MUTATOR_CALLHOOK(PutClientInServer, this);
848 if (IS_OBSERVER(this)) {
849 PutObserverInServer(this, false, use_spawnpoint);
850 } else if (IS_PLAYER(this)) {
851 PutPlayerInServer(this);
854 bot_relinkplayerlist();
857 // TODO do we need all these fields, or should we stop autodetecting runtime
858 // changes and just have a console command to update this?
859 bool ClientInit_SendEntity(entity this, entity to, int sf)
861 WriteHeader(MSG_ENTITY, _ENT_CLIENT_INIT);
864 // MSG_INIT replacement
865 // TODO: make easier to use
867 W_PROP_reload(MSG_ONE, to);
868 ClientInit_misc(this);
869 MUTATOR_CALLHOOK(Ent_Init);
871 void ClientInit_misc(entity this)
873 int channel = MSG_ONE;
874 WriteHeader(channel, ENT_CLIENT_INIT);
875 WriteByte(channel, g_nexball_meter_period * 32);
876 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[0]));
877 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[1]));
878 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[2]));
879 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[3]));
880 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[0]));
881 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[1]));
882 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2]));
883 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3]));
885 if(autocvar_sv_foginterval && world.fog != "")
886 WriteString(channel, world.fog);
888 WriteString(channel, "");
889 WriteByte(channel, this.count * 255.0); // g_balance_armor_blockpercent
890 WriteByte(channel, this.cnt * 255.0); // g_balance_damagepush_speedfactor
891 WriteByte(channel, serverflags);
892 WriteCoord(channel, autocvar_g_trueaim_minrange);
895 void ClientInit_CheckUpdate(entity this)
897 this.nextthink = time;
898 if(this.count != autocvar_g_balance_armor_blockpercent)
900 this.count = autocvar_g_balance_armor_blockpercent;
903 if(this.cnt != autocvar_g_balance_damagepush_speedfactor)
905 this.cnt = autocvar_g_balance_damagepush_speedfactor;
910 void ClientInit_Spawn()
912 entity e = new_pure(clientinit);
913 setthink(e, ClientInit_CheckUpdate);
914 Net_LinkEntity(e, false, 0, ClientInit_SendEntity);
916 ClientInit_CheckUpdate(e);
926 // initialize parms for a new player
927 parm1 = -(86400 * 366);
929 MUTATOR_CALLHOOK(SetNewParms);
937 void SetChangeParms (entity this)
939 // save parms for level change
940 parm1 = CS(this).parm_idlesince - time;
942 MUTATOR_CALLHOOK(SetChangeParms);
950 void DecodeLevelParms(entity this)
953 CS(this).parm_idlesince = parm1;
954 if (CS(this).parm_idlesince == -(86400 * 366))
955 CS(this).parm_idlesince = time;
957 // whatever happens, allow 60 seconds of idling directly after connect for map loading
958 CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - autocvar_sv_maxidle + 60);
960 MUTATOR_CALLHOOK(DecodeLevelParms);
963 void FixClientCvars(entity e)
965 // send prediction settings to the client
966 if(autocvar_g_antilag == 3) // client side hitscan
967 stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n");
968 if(autocvar_sv_gentle)
969 stuffcmd(e, "cl_cmd settemp cl_gentle 1\n");
971 stuffcmd(e, sprintf("\ncl_jumpspeedcap_min \"%s\"\n", autocvar_sv_jumpspeedcap_min));
972 stuffcmd(e, sprintf("\ncl_jumpspeedcap_max \"%s\"\n", autocvar_sv_jumpspeedcap_max));
974 stuffcmd(e, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
976 MUTATOR_CALLHOOK(FixClientCvars, e);
979 bool findinlist_abbrev(string tofind, string list)
981 if(list == "" || tofind == "")
982 return false; // empty list or search, just return
984 // this function allows abbreviated strings!
985 FOREACH_WORD(list, it != "" && it == substring(tofind, 0, strlen(it)),
993 bool PlayerInIPList(entity p, string iplist)
995 // some safety checks (never allow local?)
996 if(p.netaddress == "local" || p.netaddress == "" || !IS_REAL_CLIENT(p))
999 return findinlist_abbrev(p.netaddress, iplist);
1002 bool PlayerInIDList(entity p, string idlist)
1004 // NOTE: we do NOT check crypto_idfp_signed here, an unsigned ID is fine too for this
1008 return findinlist_abbrev(p.crypto_idfp, idlist);
1011 bool PlayerInList(entity player, string list)
1015 return boolean(PlayerInIDList(player, list) || PlayerInIPList(player, list));
1018 #ifdef DP_EXT_PRECONNECT
1023 Called once (not at each match start) when a client begins a connection to the server
1026 void ClientPreConnect(entity this)
1028 if(autocvar_sv_eventlog)
1030 GameLogEcho(sprintf(":connect:%d:%d:%s",
1033 ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot")
1039 // NOTE csqc uses the active mutators list sent by this function
1040 // to understand which mutators are enabled
1041 // also note that they aren't all registered mutators, e.g. jetpack, low gravity
1042 void SendWelcomeMessage(entity this, int msg_type)
1044 if (boolean(autocvar_g_campaign))
1046 WriteByte(msg_type, 1);
1047 WriteByte(msg_type, Campaign_GetLevelNum());
1052 if (CS(this).version_mismatch)
1054 if (CS(this).version < autocvar_gameversion)
1056 MapInfo_Get_ByName(mi_shortname, 0, NULL);
1057 WriteByte(msg_type, flags);
1059 WriteString(msg_type, autocvar_hostname);
1060 WriteString(msg_type, autocvar_g_xonoticversion);
1062 WriteString(msg_type, MapInfo_Map_titlestring);
1063 MapInfo_ClearTemps();
1065 WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
1066 WriteByte(msg_type, GetPlayerLimit());
1068 MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
1069 string modifications = M_ARGV(0, string);
1071 if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
1072 modifications = strcat(modifications, ", No start weapons");
1073 if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
1074 modifications = strcat(modifications, ", Low gravity");
1075 if(g_weapon_stay && !g_cts)
1076 modifications = strcat(modifications, ", Weapons stay");
1077 if(autocvar_g_jetpack)
1078 modifications = strcat(modifications, ", Jetpack");
1079 modifications = substring(modifications, 2, strlen(modifications) - 2);
1081 WriteString(msg_type, modifications);
1083 WriteString(msg_type, g_weaponarena_list);
1085 if(cache_lastmutatormsg != autocvar_g_mutatormsg)
1087 strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
1088 strcpy(cache_mutatormsg, cache_lastmutatormsg);
1091 WriteString(msg_type, cache_mutatormsg);
1093 WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
1100 Called when a client connects to the server
1103 void ClientConnect(entity this)
1105 if (Ban_MaybeEnforceBanOnce(this)) return;
1106 assert(!IS_CLIENT(this), return);
1107 this.flags |= FL_CLIENT;
1108 assert(player_count >= 0, player_count = 0);
1110 TRANSMUTE(Client, this);
1111 CS(this).version_nagtime = time + 10 + random() * 10;
1113 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
1115 bot_clientconnect(this);
1117 Player_DetermineForcedTeam(this);
1119 TRANSMUTE(Observer, this);
1121 PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
1123 // always track bots, don't ask for cl_allow_uidtracking
1124 if (IS_BOT_CLIENT(this))
1125 PlayerStats_GameReport_AddPlayer(this);
1127 CS(this).allowed_timeouts = autocvar_sv_timeout_number;
1129 if (autocvar_sv_eventlog)
1130 GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
1132 CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
1134 stuffcmd(this, clientstuff, "\n");
1135 stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1137 FixClientCvars(this);
1139 // get version info from player
1140 stuffcmd(this, "cmd clientversion $gameversion\n");
1142 // notify about available teams
1145 entity balance = TeamBalance_CheckAllowedTeams(this);
1146 int t = TeamBalance_GetAllowedTeams(balance);
1147 TeamBalance_Destroy(balance);
1148 stuffcmd(this, sprintf("set _teams_available %d\n", t));
1152 stuffcmd(this, "set _teams_available 0\n");
1155 bot_relinkplayerlist();
1157 CS(this).spectatortime = time;
1158 if (blockSpectators)
1160 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1163 CS(this).jointime = time;
1165 if (IS_REAL_CLIENT(this))
1167 if (g_weaponarena_weapons == WEPSET(TUBA))
1168 stuffcmd(this, "cl_cmd settemp chase_active 1\n");
1169 // quickmenu file must be put in a subfolder with an unique name
1170 // to reduce chances of overriding custom client quickmenus
1171 if (waypointeditor_enabled)
1172 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
1173 else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
1174 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
1177 if (!autocvar_sv_foginterval && world.fog != "")
1178 stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1180 if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
1181 if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
1182 send_CSQC_teamnagger();
1184 CSQCMODEL_AUTOINIT(this);
1186 CS(this).model_randomizer = random();
1188 if (IS_REAL_CLIENT(this))
1189 sv_notice_join(this);
1191 this.move_qcphysics = true;
1193 // update physics stats (players can spawn before physics runs)
1194 Physics_UpdateStats(this);
1196 IL_EACH(g_initforplayer, it.init_for_player, {
1197 it.init_for_player(it, this);
1200 Handicap_Initialize(this);
1203 if (PlayerInList(this, autocvar_g_playban_list))
1204 TRANSMUTE(Observer, this);
1206 if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
1207 CS(this).muted = true;
1209 MUTATOR_CALLHOOK(ClientConnect, this);
1211 if (player_count == 1)
1213 if (autocvar_sv_autopause && server_is_dedicated)
1215 localcmd("\nsv_hook_firstjoin\n");
1219 .string shootfromfixedorigin;
1220 .entity chatbubbleentity;
1221 void player_powerups_remove_all(entity this);
1227 Called when a client disconnects from the server
1230 void ClientDisconnect(entity this)
1232 assert(IS_CLIENT(this), return);
1234 /* from "ignore" command */
1235 strfree(this.ignore_list);
1236 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it.ignore_list,
1238 if(it.crypto_idfp && it.crypto_idfp != "")
1240 string mylist = ignore_removefromlist(it, this);
1242 strunzone(it.ignore_list);
1244 it.ignore_list = strzone(mylist);
1246 /* from "ignore" command */
1248 PlayerStats_GameReport_FinalizePlayer(this);
1249 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
1250 if (CS(this).active_minigame) part_minigame(this);
1251 if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
1253 if (autocvar_sv_eventlog)
1254 GameLogEcho(strcat(":part:", ftos(this.playerid)));
1256 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
1259 SetSpectatee(this, NULL);
1261 MUTATOR_CALLHOOK(ClientDisconnect, this);
1263 strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
1264 strfree(CS_CVAR(this).weaponorder_byimpulse);
1265 ClientState_detach(this);
1267 Portal_ClearAll(this);
1269 Unfreeze(this, false);
1271 RemoveGrapplingHooks(this);
1273 strfree(this.shootfromfixedorigin);
1275 // Here, everything has been done that requires this player to be a client.
1277 this.flags &= ~FL_CLIENT;
1279 if (this.chatbubbleentity) delete(this.chatbubbleentity);
1280 if (this.killindicator) delete(this.killindicator);
1282 IL_EACH(g_counters, it.realowner == this,
1287 WaypointSprite_PlayerGone(this);
1289 bot_relinkplayerlist();
1291 strfree(this.clientstatus);
1292 if (this.personal) delete(this.personal);
1295 if (warmup_stage || game_starttime > time) ReadyCount();
1296 if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
1298 player_powerups_remove_all(this); // stop powerup sound
1302 if (player_count == 0)
1303 localcmd("\nsv_hook_lastleave\n");
1306 void ChatBubbleThink(entity this)
1308 this.nextthink = time;
1309 if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this)
1311 if(this.owner) // but why can that ever be NULL?
1312 this.owner.chatbubbleentity = NULL;
1319 if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
1321 if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
1322 this.mdl = "models/sprites/minigame_busy.iqm";
1323 else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
1324 this.mdl = "models/misc/chatbubble.spr";
1327 if ( this.model != this.mdl )
1328 _setmodel(this, this.mdl);
1332 void UpdateChatBubble(entity this)
1336 // spawn a chatbubble entity if needed
1337 if (!this.chatbubbleentity)
1339 this.chatbubbleentity = new(chatbubbleentity);
1340 this.chatbubbleentity.owner = this;
1341 this.chatbubbleentity.exteriormodeltoclient = this;
1342 setthink(this.chatbubbleentity, ChatBubbleThink);
1343 this.chatbubbleentity.nextthink = time;
1344 setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below
1345 //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1');
1346 setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1');
1347 setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth
1348 this.chatbubbleentity.mdl = this.chatbubbleentity.model;
1349 //this.chatbubbleentity.model = "";
1350 this.chatbubbleentity.effects = EF_LOWPRECISION;
1354 void calculate_player_respawn_time(entity this)
1356 if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
1359 float gametype_setting_tmp;
1360 float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
1361 float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
1362 float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
1363 float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
1364 float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
1365 float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
1367 float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
1370 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1371 if(it.team == this.team)
1374 if (sdelay_small_count == 0)
1375 sdelay_small_count = 1;
1376 if (sdelay_large_count == 0)
1377 sdelay_large_count = 1;
1381 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1384 if (sdelay_small_count == 0)
1386 if (IS_INDEPENDENT_PLAYER(this))
1388 // Players play independently. No point in requiring enemies.
1389 sdelay_small_count = 1;
1393 // Players play AGAINST each other. Enemies required.
1394 sdelay_small_count = 2;
1397 if (sdelay_large_count == 0)
1399 if (IS_INDEPENDENT_PLAYER(this))
1401 // Players play independently. No point in requiring enemies.
1402 sdelay_large_count = 1;
1406 // Players play AGAINST each other. Enemies required.
1407 sdelay_large_count = 2;
1414 if (pcount <= sdelay_small_count)
1415 sdelay = sdelay_small;
1416 else if (pcount >= sdelay_large_count)
1417 sdelay = sdelay_large;
1418 else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
1419 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
1422 this.respawn_time = ceil((time + sdelay) / waves) * waves;
1424 this.respawn_time = time + sdelay;
1426 if(sdelay < sdelay_max)
1427 this.respawn_time_max = time + sdelay_max;
1429 this.respawn_time_max = this.respawn_time;
1431 if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
1432 this.respawn_countdown = 10; // first number to count down from is 10
1434 this.respawn_countdown = -1; // do not count down
1436 if(autocvar_g_forced_respawn)
1437 this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
1440 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1441 // added to the model skins
1442 /*void UpdateColorModHack()
1445 c = this.clientcolors & 15;
1446 // LordHavoc: only bothering to support white, green, red, yellow, blue
1447 if (!teamplay) this.colormod = '0 0 0';
1448 else if (c == 0) this.colormod = '1.00 1.00 1.00';
1449 else if (c == 3) this.colormod = '0.10 1.73 0.10';
1450 else if (c == 4) this.colormod = '1.73 0.10 0.10';
1451 else if (c == 12) this.colormod = '1.22 1.22 0.10';
1452 else if (c == 13) this.colormod = '0.10 0.10 1.73';
1453 else this.colormod = '1 1 1';
1456 void respawn(entity this)
1458 bool damagedbycontents_prev = this.damagedbycontents;
1461 if(autocvar_g_respawn_ghosts)
1463 this.solid = SOLID_NOT;
1464 this.takedamage = DAMAGE_NO;
1465 this.damagedbycontents = false;
1466 set_movetype(this, MOVETYPE_FLY);
1467 this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1468 this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1469 this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1470 this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha);
1471 Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1);
1472 if(autocvar_g_respawn_ghosts_time > 0)
1473 SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime);
1476 SUB_SetFade (this, time, 1); // fade out the corpse immediately
1480 this.damagedbycontents = damagedbycontents_prev;
1482 this.effects |= EF_NODRAW; // prevent another CopyBody
1483 PutClientInServer(this);
1486 void play_countdown(entity this, float finished, Sound samp)
1489 float time_left = finished - time;
1490 if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
1491 sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
1494 // it removes special powerups not handled by StatusEffects
1495 void player_powerups_remove_all(entity this)
1497 if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1499 // don't play the poweroff sound when the game restarts or the player disconnects
1500 if (time > game_starttime + 1 && IS_CLIENT(this)
1501 && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
1503 sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
1505 if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1506 stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
1507 this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
1511 void player_powerups(entity this)
1513 if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
1514 this.modelflags |= MF_ROCKET;
1516 this.modelflags &= ~MF_ROCKET;
1518 this.effects &= ~EF_NODEPTHTEST;
1521 player_powerups_remove_all(this);
1523 if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
1526 // add a way to see what the items were BEFORE all of these checks for the mutator hook
1527 int items_prev = this.items;
1529 if (!MUTATOR_IS_ENABLED(mutator_instagib))
1531 // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
1532 if (this.items & IT_SUPERWEAPON)
1534 if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
1536 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
1537 this.items = this.items - (this.items & IT_SUPERWEAPON);
1538 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
1539 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1541 else if (this.items & IT_UNLIMITED_SUPERWEAPONS)
1543 // don't let them run out
1547 play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
1548 if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
1550 this.items = this.items - (this.items & IT_SUPERWEAPON);
1551 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1552 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname);
1553 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1557 else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
1559 if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
1561 this.items = this.items | IT_SUPERWEAPON;
1562 if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
1565 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
1566 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1571 if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
1572 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
1573 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1576 else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
1578 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
1582 if(autocvar_g_nodepthtestplayers)
1583 this.effects = this.effects | EF_NODEPTHTEST;
1585 if(autocvar_g_fullbrightplayers)
1586 this.effects = this.effects | EF_FULLBRIGHT;
1588 MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
1591 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1593 if(current > stable)
1595 else if(current > stable - 0.25) // when close enough, "snap"
1598 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1601 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1603 if(current < stable)
1605 else if(current < stable + 0.25) // when close enough, "snap"
1608 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1611 void RotRegen(entity this, Resource res, float limit_mod,
1612 float regenstable, float regenfactor, float regenlinear, float regenframetime,
1613 float rotstable, float rotfactor, float rotlinear, float rotframetime)
1615 float old = GetResource(this, res);
1616 float current = old;
1617 if(current > rotstable)
1619 if(rotframetime > 0)
1621 current = CalcRot(current, rotstable, rotfactor, rotframetime);
1622 current = max(rotstable, current - rotlinear * rotframetime);
1625 else if(current < regenstable)
1627 if(regenframetime > 0)
1629 current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1630 current = min(regenstable, current + regenlinear * regenframetime);
1634 float limit = GetResourceLimit(this, res) * limit_mod;
1639 SetResource(this, res, current);
1642 void player_regen(entity this)
1644 float max_mod, regen_mod, rot_mod, limit_mod;
1645 max_mod = regen_mod = rot_mod = limit_mod = 1;
1647 float regen_health = autocvar_g_balance_health_regen;
1648 float regen_health_linear = autocvar_g_balance_health_regenlinear;
1649 float regen_health_rot = autocvar_g_balance_health_rot;
1650 float regen_health_rotlinear = autocvar_g_balance_health_rotlinear;
1651 float regen_health_stable = autocvar_g_balance_health_regenstable;
1652 float regen_health_rotstable = autocvar_g_balance_health_rotstable;
1653 bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot,
1654 regen_health_rotlinear, regen_health_stable, regen_health_rotstable);
1655 max_mod = M_ARGV(1, float);
1656 regen_mod = M_ARGV(2, float);
1657 rot_mod = M_ARGV(3, float);
1658 limit_mod = M_ARGV(4, float);
1659 regen_health = M_ARGV(5, float);
1660 regen_health_linear = M_ARGV(6, float);
1661 regen_health_rot = M_ARGV(7, float);
1662 regen_health_rotlinear = M_ARGV(8, float);
1663 regen_health_stable = M_ARGV(9, float);
1664 regen_health_rotstable = M_ARGV(10, float);
1666 float rotstable, regenstable, rotframetime, regenframetime;
1668 if(!mutator_returnvalue)
1669 if(!STAT(FROZEN, this))
1671 regenstable = autocvar_g_balance_armor_regenstable;
1672 rotstable = autocvar_g_balance_armor_rotstable;
1673 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1674 rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
1675 RotRegen(this, RES_ARMOR, limit_mod,
1676 regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
1677 rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
1679 // NOTE: max_mod is only applied to health
1680 regenstable = regen_health_stable * max_mod;
1681 rotstable = regen_health_rotstable * max_mod;
1682 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1683 rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
1684 RotRegen(this, RES_HEALTH, limit_mod,
1685 regenstable, regen_health, regen_health_linear, regenframetime,
1686 rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
1689 // if player rotted to death... die!
1690 // check this outside above checks, as player may still be able to rot to death
1691 if(GetResource(this, RES_HEALTH) < 1)
1694 vehicles_exit(this.vehicle, VHEF_RELEASE);
1695 if(this.event_damage)
1696 this.event_damage(this, this, this, 1, DEATH_ROT.m_id, DMG_NOWEP, this.origin, '0 0 0');
1699 if (!(this.items & IT_UNLIMITED_AMMO))
1701 regenstable = autocvar_g_balance_fuel_regenstable;
1702 rotstable = autocvar_g_balance_fuel_rotstable;
1703 regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
1704 rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
1705 RotRegen(this, RES_FUEL, 1,
1706 regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
1707 rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
1712 void SetZoomState(entity this, float newzoom)
1714 if(newzoom != CS(this).zoomstate)
1716 CS(this).zoomstate = newzoom;
1717 ClientData_Touch(this);
1719 zoomstate_set = true;
1722 void GetPressedKeys(entity this)
1724 MUTATOR_CALLHOOK(GetPressedKeys, this);
1727 CS(this).pressedkeys = 0;
1728 STAT(PRESSED_KEYS, this) = 0;
1732 // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
1733 int keys = STAT(PRESSED_KEYS, this);
1734 keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0);
1735 keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0);
1736 keys = BITSET(keys, KEY_RIGHT, CS(this).movement.y > 0);
1737 keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
1739 keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
1740 keys = BITSET(keys, KEY_CROUCH, IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
1741 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
1742 keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
1743 CS(this).pressedkeys = keys; // store for other users
1745 STAT(PRESSED_KEYS, this) = keys;
1749 ======================
1750 spectate mode routines
1751 ======================
1754 void SpectateCopy(entity this, entity spectatee)
1756 TC(Client, this); TC(Client, spectatee);
1758 MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
1759 PS(this) = PS(spectatee);
1760 this.armortype = spectatee.armortype;
1761 SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
1762 SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
1763 SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
1764 SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
1765 SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
1766 SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
1767 SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
1768 this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1769 SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
1770 CS(this).impulse = 0;
1771 this.disableclientprediction = 1; // no need to run prediction on a spectator
1772 this.items = spectatee.items;
1773 STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
1774 STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
1775 STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
1776 STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
1777 STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
1778 this.punchangle = spectatee.punchangle;
1779 this.view_ofs = spectatee.view_ofs;
1780 this.velocity = spectatee.velocity;
1781 this.dmg_take = spectatee.dmg_take;
1782 this.dmg_save = spectatee.dmg_save;
1783 this.dmg_inflictor = spectatee.dmg_inflictor;
1784 this.v_angle = spectatee.v_angle;
1785 this.angles = spectatee.v_angle;
1786 STAT(FROZEN, this) = STAT(FROZEN, spectatee);
1787 STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee);
1788 this.viewloc = spectatee.viewloc;
1789 if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
1790 this.fixangle = true;
1791 setorigin(this, spectatee.origin);
1792 setsize(this, spectatee.mins, spectatee.maxs);
1793 SetZoomState(this, CS(spectatee).zoomstate);
1795 anticheat_spectatecopy(this, spectatee);
1796 STAT(HUD, this) = STAT(HUD, spectatee);
1797 if(spectatee.vehicle)
1799 this.angles = spectatee.v_angle;
1801 //this.fixangle = false;
1802 //this.velocity = spectatee.vehicle.velocity;
1803 this.vehicle_health = spectatee.vehicle_health;
1804 this.vehicle_shield = spectatee.vehicle_shield;
1805 this.vehicle_energy = spectatee.vehicle_energy;
1806 this.vehicle_ammo1 = spectatee.vehicle_ammo1;
1807 this.vehicle_ammo2 = spectatee.vehicle_ammo2;
1808 this.vehicle_reload1 = spectatee.vehicle_reload1;
1809 this.vehicle_reload2 = spectatee.vehicle_reload2;
1811 //msg_entity = this;
1813 // WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1814 //WriteAngle(MSG_ONE, spectatee.v_angle.x);
1815 // WriteAngle(MSG_ONE, spectatee.v_angle.y);
1816 // WriteAngle(MSG_ONE, spectatee.v_angle.z);
1818 //WriteByte (MSG_ONE, SVC_SETVIEW);
1819 // WriteEntity(MSG_ONE, this);
1820 //makevectors(spectatee.v_angle);
1821 //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/
1825 bool SpectateUpdate(entity this)
1830 if(!IS_PLAYER(this.enemy) || this == this.enemy)
1832 SetSpectatee(this, NULL);
1836 SpectateCopy(this, this.enemy);
1841 bool SpectateSet(entity this)
1843 if(!IS_PLAYER(this.enemy))
1846 ClientData_Touch(this.enemy);
1849 WriteByte(MSG_ONE, SVC_SETVIEW);
1850 WriteEntity(MSG_ONE, this.enemy);
1851 set_movetype(this, MOVETYPE_NONE);
1852 accuracy_resend(this);
1854 if(!SpectateUpdate(this))
1855 PutObserverInServer(this, false, true);
1860 void SetSpectatee_status(entity this, int spectatee_num)
1862 int oldspectatee_status = CS(this).spectatee_status;
1863 CS(this).spectatee_status = spectatee_num;
1865 if (CS(this).spectatee_status != oldspectatee_status)
1867 if (STAT(PRESSED_KEYS, this))
1869 CS(this).pressedkeys = 0;
1870 STAT(PRESSED_KEYS, this) = 0;
1872 ClientData_Touch(this);
1873 if (g_race || g_cts) race_InitSpectator();
1877 void SetSpectatee(entity this, entity spectatee)
1879 if(IS_BOT_CLIENT(this))
1880 return; // bots abuse .enemy, this code is useless to them
1882 entity old_spectatee = this.enemy;
1884 this.enemy = spectatee;
1887 // these are required to fix the spectator bug with arc
1890 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1892 .entity weaponentity = weaponentities[slot];
1893 if(old_spectatee.(weaponentity).arc_beam)
1894 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1899 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1901 .entity weaponentity = weaponentities[slot];
1902 if(spectatee.(weaponentity).arc_beam)
1903 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1908 SetSpectatee_status(this, etof(spectatee));
1910 // needed to update spectator list
1911 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1914 bool Spectate(entity this, entity pl)
1916 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1918 pl = M_ARGV(1, entity);
1920 SetSpectatee(this, pl);
1921 return SpectateSet(this);
1924 bool SpectateNext(entity this)
1926 entity ent = find(this.enemy, classname, STR_PLAYER);
1928 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1929 ent = M_ARGV(1, entity);
1931 ent = find(ent, classname, STR_PLAYER);
1933 if(ent) { SetSpectatee(this, ent); }
1935 return SpectateSet(this);
1938 bool SpectatePrev(entity this)
1940 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1941 entity ent = findchain(classname, STR_PLAYER);
1942 if (!ent) // no player
1946 // skip players until current spectated player
1948 while(ent && ent != this.enemy)
1951 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1953 case MUT_SPECPREV_FOUND:
1954 ent = M_ARGV(1, entity);
1956 case MUT_SPECPREV_RETURN:
1958 case MUT_SPECPREV_CONTINUE:
1969 SetSpectatee(this, ent);
1970 return SpectateSet(this);
1975 ShowRespawnCountdown()
1977 Update a respawn countdown display.
1980 void ShowRespawnCountdown(entity this)
1983 if(!IS_DEAD(this)) // just respawned?
1987 number = ceil(this.respawn_time - time);
1990 if(number <= this.respawn_countdown)
1992 this.respawn_countdown = number - 1;
1993 if(ceil(this.respawn_time - (time + 0.5)) == number) // only say it if it is the same number even in 0.5s; to prevent overlapping sounds
1994 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
1999 .bool team_selected;
2000 bool ShowTeamSelection(entity this)
2002 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
2004 if (frametime) // once per frame is more than enough
2005 stuffcmd(this, "_scoreboard_team_selection 1\n");
2008 void Join(entity this)
2010 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2013 TRANSMUTE(Player, this);
2015 if(!this.team_selected)
2016 if(autocvar_g_campaign || autocvar_g_balance_teams)
2017 TeamBalance_JoinBestTeam(this);
2019 if(autocvar_g_campaign)
2020 campaign_bots_may_start = true;
2022 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2024 PutClientInServer(this);
2027 if(teamplay && this.team != -1)
2031 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2032 this.team_selected = false;
2035 int GetPlayerLimit()
2038 return 2; // TODO: this workaround is needed since the mutator hook from duel can't be activated before the gametype is loaded (e.g. switching modes via gametype vote screen)
2039 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2040 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2041 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2042 player_limit = M_ARGV(0, int);
2043 return player_limit < maxclients ? player_limit : 0;
2047 * Determines whether the player is allowed to join. This depends on cvar
2048 * g_maxplayers, if it isn't used this function always return true, otherwise
2049 * it checks whether the number of currently playing players exceeds g_maxplayers.
2050 * @return int number of free slots for players, 0 if none
2052 int nJoinAllowed(entity this, entity ignore)
2055 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2056 // so report 0 free slots if restricted
2058 if(autocvar_g_forced_team_otherwise == "spectate")
2060 if(autocvar_g_forced_team_otherwise == "spectator")
2064 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2065 return 0; // forced spectators can never join
2067 static float msg_time = 0;
2068 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2072 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2073 msg_time = time + 0.5;
2078 // TODO simplify this
2079 int totalClients = 0;
2080 int currentlyPlaying = 0;
2081 FOREACH_CLIENT(true, {
2084 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2088 int player_limit = GetPlayerLimit();
2092 free_slots = maxclients - totalClients;
2093 else if(player_limit > 0 && currentlyPlaying < player_limit)
2094 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2096 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2098 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2099 msg_time = time + 0.5;
2105 bool joinAllowed(entity this)
2107 if (CS(this).version_mismatch) return false;
2108 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2109 if (!nJoinAllowed(this, this)) return false;
2110 if (teamplay && lockteams) return false;
2111 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2112 if (ShowTeamSelection(this)) return false;
2116 void show_entnum(entity this)
2118 // waypoint editor implements a similar feature for waypoints
2119 if (waypointeditor_enabled)
2122 if (wasfreed(this.wp_aimed))
2123 this.wp_aimed = NULL;
2125 WarpZone_crosshair_trace_plusvisibletriggers(this);
2130 if (ent != this.wp_aimed)
2132 string str = sprintf(
2133 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2134 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2135 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2138 if (this.wp_aimed != ent)
2139 this.wp_aimed = ent;
2142 .bool dualwielding_prev;
2143 bool PlayerThink(entity this)
2145 if (game_stopped || intermission_running) {
2146 this.modelflags &= ~MF_ROCKET;
2147 if(intermission_running)
2148 IntermissionThink(this);
2152 if (timeout_status == TIMEOUT_ACTIVE) {
2153 // don't allow the player to turn around while game is paused
2154 // FIXME turn this into CSQC stuff
2155 this.v_angle = this.lastV_angle;
2156 this.angles = this.lastV_angle;
2157 this.fixangle = true;
2160 if (frametime) player_powerups(this);
2162 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2164 if (IS_DEAD(this)) {
2165 if (this.personal && g_race_qualifying) {
2166 if (time > this.respawn_time) {
2167 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2169 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2172 if (frametime) player_anim(this);
2174 if (this.respawn_flags & RESPAWN_DENY)
2176 STAT(RESPAWN_TIME, this) = 0;
2180 bool button_pressed = (PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this) || PHYS_INPUT_BUTTON_ATCK2(this) || PHYS_INPUT_BUTTON_HOOK(this) || PHYS_INPUT_BUTTON_USE(this));
2182 switch(this.deadflag)
2186 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2187 this.deadflag = DEAD_RESPAWNING;
2188 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2189 this.deadflag = DEAD_DEAD;
2195 this.deadflag = DEAD_RESPAWNABLE;
2196 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2197 this.deadflag = DEAD_RESPAWNING;
2200 case DEAD_RESPAWNABLE:
2202 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2203 this.deadflag = DEAD_RESPAWNING;
2206 case DEAD_RESPAWNING:
2208 if (time > this.respawn_time)
2210 this.respawn_time = time + 1; // only retry once a second
2211 this.respawn_time_max = this.respawn_time;
2218 ShowRespawnCountdown(this);
2220 if (this.respawn_flags & RESPAWN_SILENT)
2221 STAT(RESPAWN_TIME, this) = 0;
2222 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2224 if (time < this.respawn_time)
2225 STAT(RESPAWN_TIME, this) = this.respawn_time;
2226 else if (this.deadflag != DEAD_RESPAWNING)
2227 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2230 STAT(RESPAWN_TIME, this) = this.respawn_time;
2233 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2234 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2235 STAT(RESPAWN_TIME, this) *= -1;
2240 FixPlayermodel(this);
2242 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2243 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2244 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2247 // reset gun alignment when dual wielding status changes
2248 // to ensure guns are always aligned right and left
2249 bool dualwielding = W_DualWielding(this);
2250 if(this.dualwielding_prev != dualwielding)
2252 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2253 this.dualwielding_prev = dualwielding;
2256 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2259 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2261 .entity weaponentity = weaponentities[slot];
2262 if(WEP_CVAR(vortex, charge_always))
2263 W_Vortex_Charge(this, weaponentity, frametime);
2264 W_WeaponFrame(this, weaponentity);
2270 // WEAPONTODO: Add a weapon request for this
2271 // rot vortex charge to the charge limit
2272 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2274 .entity weaponentity = weaponentities[slot];
2275 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2276 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2281 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2284 monsters_setstatus(this);
2289 .bool would_spectate;
2290 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2291 void ObserverOrSpectatorThink(entity this)
2293 bool is_spec = IS_SPEC(this);
2294 if ( CS(this).impulse )
2296 int r = MinigameImpulse(this, CS(this).impulse);
2298 CS(this).impulse = 0;
2300 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2302 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2303 CS(this).impulse = 0;
2308 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2310 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2312 CS(this).autojoin_checked = true;
2313 TRANSMUTE(Player, this);
2314 PutClientInServer(this);
2316 .entity weaponentity = weaponentities[0];
2317 if(this.(weaponentity).m_weapon == WEP_Null)
2318 W_NextWeapon(this, 0, weaponentity);
2323 if (this.flags & FL_JUMPRELEASED) {
2324 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2325 this.flags &= ~FL_JUMPRELEASED;
2326 this.flags |= FL_SPAWNING;
2327 } else if((is_spec && (PHYS_INPUT_BUTTON_ATCK(this) || CS(this).impulse == 10 || CS(this).impulse == 15 || CS(this).impulse == 18 || (CS(this).impulse >= 200 && CS(this).impulse <= 209)))
2328 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2329 this.flags &= ~FL_JUMPRELEASED;
2330 if(SpectateNext(this)) {
2331 TRANSMUTE(Spectator, this);
2332 } else if (is_spec) {
2333 TRANSMUTE(Observer, this);
2334 PutClientInServer(this);
2337 this.would_spectate = false; // unable to spectate anyone
2339 CS(this).impulse = 0;
2340 } else if (is_spec) {
2341 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2342 this.flags &= ~FL_JUMPRELEASED;
2343 if(SpectatePrev(this)) {
2344 TRANSMUTE(Spectator, this);
2346 TRANSMUTE(Observer, this);
2347 PutClientInServer(this);
2349 CS(this).impulse = 0;
2350 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2351 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2352 this.would_spectate = false;
2353 this.flags &= ~FL_JUMPRELEASED;
2354 TRANSMUTE(Observer, this);
2355 PutClientInServer(this);
2357 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2358 PutObserverInServer(this, false, true);
2359 this.would_spectate = true;
2363 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2364 if (PHYS_INPUT_BUTTON_USE(this))
2365 wouldclip = !wouldclip;
2366 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2367 set_movetype(this, preferred_movetype);
2369 } else { // jump pressed
2370 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2371 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2372 this.flags |= FL_JUMPRELEASED;
2373 // primary attack pressed
2374 if(this.flags & FL_SPAWNING)
2376 this.flags &= ~FL_SPAWNING;
2377 if(joinAllowed(this))
2379 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2380 CS(this).autojoin_checked = -1;
2384 if(is_spec && !SpectateUpdate(this))
2385 PutObserverInServer(this, false, true);
2388 this.flags |= FL_CLIENT | FL_NOTARGET;
2391 void PlayerUseKey(entity this)
2393 if (!IS_PLAYER(this))
2400 vehicles_exit(this.vehicle, VHEF_NORMAL);
2404 else if(autocvar_g_vehicles_enter)
2406 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2408 entity head, closest_target = NULL;
2409 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2411 while(head) // find the closest acceptable target to enter
2413 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2414 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2418 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2419 { closest_target = head; }
2421 else { closest_target = head; }
2427 if(closest_target) { vehicles_enter(this, closest_target); return; }
2431 // a use key was pressed; call handlers
2432 MUTATOR_CALLHOOK(PlayerUseKey, this);
2440 Called every frame for each real client by DP (and for each bot by StartFrame()),
2441 and when executing every asynchronous move, so only include things that MUST be done then.
2442 Use PlayerFrame() instead for code that only needs to run once per server frame.
2443 frametime == 0 in the asynchronous code path.
2445 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2448 .float last_vehiclecheck;
2449 void PlayerPreThink (entity this)
2451 WarpZone_PlayerPhysics_FixVAngle(this);
2453 zoomstate_set = false;
2455 MUTATOR_CALLHOOK(PlayerPreThink, this);
2457 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2459 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2461 if (IS_PLAYER(this)) {
2462 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2463 error("Client can't be spawned as player on connection!");
2464 if(!PlayerThink(this))
2467 else if (game_stopped || intermission_running) {
2468 if(intermission_running)
2469 IntermissionThink(this);
2472 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2474 bool early_join_requested = (CS(this).autojoin_checked < 0);
2475 CS(this).autojoin_checked = 1;
2476 // don't do this in ClientConnect
2477 // many things can go wrong if a client is spawned as player on connection
2478 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2479 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2480 && (!teamplay || autocvar_g_balance_teams)))
2482 if(joinAllowed(this))
2487 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2488 ObserverOrSpectatorThink(this);
2491 // WEAPONTODO: Add weapon request for this
2492 if (!zoomstate_set) {
2493 bool wep_zoomed = false;
2494 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2496 .entity weaponentity = weaponentities[slot];
2497 Weapon thiswep = this.(weaponentity).m_weapon;
2498 if(thiswep != WEP_Null && thiswep.wr_zoom)
2499 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2501 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2504 // Voice sound effects
2505 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2507 CS(this).teamkill_soundtime = 0;
2509 entity e = CS(this).teamkill_soundsource;
2510 entity oldpusher = e.pusher;
2512 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2513 e.pusher = oldpusher;
2516 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2517 CS(this).taunt_soundtime = 0;
2518 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2521 target_voicescript_next(this);
2524 void DrownPlayer(entity this)
2526 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2527 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2529 STAT(AIR_FINISHED, this) = 0;
2533 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2535 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2536 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2537 STAT(AIR_FINISHED, this) = 0;
2541 if (!STAT(AIR_FINISHED, this))
2542 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2543 if (STAT(AIR_FINISHED, this) < time)
2545 if (this.pain_finished < time)
2547 Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, DMG_NOWEP, this.origin, '0 0 0');
2548 this.pain_finished = time + 0.5;
2554 .bool move_qcphysics;
2556 void Player_Physics(entity this)
2558 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2560 if(!this.move_qcphysics)
2563 if(!frametime && !CS(this).pm_frametime)
2566 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2568 CS(this).pm_frametime = 0;
2575 Called every frame for each real client by DP (and for each bot by StartFrame()),
2576 and when executing every asynchronous move, so only include things that MUST be done then.
2577 Use PlayerFrame() instead for code that only needs to run once per server frame.
2578 frametime == 0 in the asynchronous code path.
2581 void PlayerPostThink (entity this)
2583 Player_Physics(this);
2585 if (IS_PLAYER(this)) {
2586 if(this.death_time == time && IS_DEAD(this))
2588 // player's bbox gets resized now, instead of in the damage event that killed the player,
2589 // once all the damage events of this frame have been processed with normal size
2591 setsize(this, this.mins, this.maxs);
2594 UpdateChatBubble(this);
2595 if (CS(this).impulse) ImpulseCommands(this);
2596 GetPressedKeys(this);
2599 CSQCMODEL_AUTOUPDATE(this);
2603 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2605 CS(this).pressedkeys = 0;
2606 STAT(PRESSED_KEYS, this) = 0;
2609 CSQCMODEL_AUTOUPDATE(this);
2616 Called every frame for each client by StartFrame().
2617 Use this for code that only needs to run once per server frame.
2618 frametime is always set here.
2621 void PlayerFrame (entity this)
2623 // formerly PreThink code
2624 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2625 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2627 // physics frames: update anticheat stuff
2628 anticheat_prethink(this);
2630 // Check if spectating is allowed
2631 if (blockSpectators && IS_REAL_CLIENT(this)
2632 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2633 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2635 if (dropclient_schedule(this))
2636 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2639 // Check for nameless players
2640 if (this.netname == "" || this.netname != CS(this).netname_previous)
2642 bool assume_unchanged = (CS(this).netname_previous == "");
2643 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2645 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2646 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2647 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2648 assume_unchanged = false;
2649 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2651 if (isInvisibleString(this.netname))
2653 this.netname = strzone(sprintf("Player#%d", this.playerid));
2654 sprint(this, "Warning: invisible names are not allowed.\n");
2655 assume_unchanged = false;
2656 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2658 if (!assume_unchanged && autocvar_sv_eventlog)
2659 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2660 strcpy(CS(this).netname_previous, this.netname);
2664 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2666 CS(this).version_nagtime = 0;
2667 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2671 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2674 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2678 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2679 if (r < 0) // old client
2680 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2681 else if (r > 0) // old server
2682 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2687 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2689 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2690 this.max_armorvalue = 0;
2694 if (IS_PLAYER(this) && time >= game_starttime)
2696 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2698 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2699 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2701 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2703 if (STAT(REVIVE_PROGRESS, this) >= 1)
2704 Unfreeze(this, false);
2706 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2708 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2709 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2711 if (GetResource(this, RES_HEALTH) < 1)
2714 vehicles_exit(this.vehicle, VHEF_RELEASE);
2715 if(this.event_damage)
2716 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2718 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2719 Unfreeze(this, false);
2724 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2725 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2727 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2731 if(!it.team || SAME_TEAM(this, it))
2732 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2733 else if(autocvar_g_vehicles_steal)
2734 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2736 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2738 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2742 this.last_vehiclecheck = time + 1;
2747 // formerly PostThink code
2748 if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
2749 if (IS_REAL_CLIENT(this))
2750 if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
2751 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2753 int totalClients = 0;
2754 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2756 // maxidle disabled in local matches by not counting clients (totalClients 0)
2757 if (server_is_dedicated)
2759 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2763 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2767 else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2769 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2775 if (totalClients < autocvar_sv_maxidle_minplayers)
2777 // idle kick disabled
2778 CS(this).parm_idlesince = time;
2780 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2782 if (CS(this).idlekick_lasttimeleft)
2784 CS(this).idlekick_lasttimeleft = 0;
2785 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2790 float maxidle_time = autocvar_sv_maxidle;
2791 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2792 maxidle_time = autocvar_sv_maxidle_playertospectator;
2793 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2794 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2795 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2797 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2798 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2800 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2802 if (timeleft <= 0) {
2803 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2805 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2806 PutObserverInServer(this, true, true);
2810 if (dropclient_schedule(this))
2811 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2815 else if (timeleft <= countdown_time) {
2816 if (timeleft != CS(this).idlekick_lasttimeleft)
2817 play2(this, SND(TALK2));
2818 CS(this).idlekick_lasttimeleft = timeleft;
2827 this.solid = SOLID_NOT;
2828 this.takedamage = DAMAGE_NO;
2829 set_movetype(this, MOVETYPE_NONE);
2830 CS(this).teamkill_complain = 0;
2831 CS(this).teamkill_soundtime = 0;
2832 CS(this).teamkill_soundsource = NULL;
2835 if (this.waypointsprite_attachedforcarrier) {
2836 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2837 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2841 // hack to copy the button fields from the client entity to the Client State
2842 void PM_UpdateButtons(entity this, entity store)
2845 store.impulse = this.impulse;
2848 bool typing = this.buttonchat || this.button12;
2850 store.button0 = (typing) ? 0 : this.button0;
2852 store.button2 = (typing) ? 0 : this.button2;
2853 store.button3 = (typing) ? 0 : this.button3;
2854 store.button4 = this.button4;
2855 store.button5 = (typing) ? 0 : this.button5;
2856 store.button6 = this.button6;
2857 store.button7 = this.button7;
2858 store.button8 = this.button8;
2859 store.button9 = this.button9;
2860 store.button10 = this.button10;
2861 store.button11 = this.button11;
2862 store.button12 = this.button12;
2863 store.button13 = this.button13;
2864 store.button14 = this.button14;
2865 store.button15 = this.button15;
2866 store.button16 = this.button16;
2867 store.buttonuse = this.buttonuse;
2868 store.buttonchat = this.buttonchat;
2870 store.cursor_active = this.cursor_active;
2871 store.cursor_screen = this.cursor_screen;
2872 store.cursor_trace_start = this.cursor_trace_start;
2873 store.cursor_trace_endpos = this.cursor_trace_endpos;
2874 store.cursor_trace_ent = this.cursor_trace_ent;
2876 store.ping = this.ping;
2877 store.ping_packetloss = this.ping_packetloss;
2878 store.ping_movementloss = this.ping_movementloss;
2880 store.v_angle = this.v_angle;
2881 store.movement = this.movement;
2884 NET_HANDLE(fpsreport, bool)
2886 int fps = ReadShort();
2887 PlayerScore_Set(sender, SP_FPS, fps);