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 if (MapInfo_Map_author != "")
1059 WriteByte(msg_type, flags);
1061 WriteString(msg_type, autocvar_hostname);
1062 WriteString(msg_type, autocvar_g_xonoticversion);
1064 WriteString(msg_type, MapInfo_Map_titlestring);
1066 WriteString(msg_type, MapInfo_Map_author);
1067 MapInfo_ClearTemps();
1069 WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
1070 WriteByte(msg_type, GetPlayerLimit());
1072 MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
1073 string modifications = M_ARGV(0, string);
1075 if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
1076 modifications = strcat(modifications, ", No start weapons");
1077 if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
1078 modifications = strcat(modifications, ", Low gravity");
1079 if(g_weapon_stay && !g_cts)
1080 modifications = strcat(modifications, ", Weapons stay");
1081 if(autocvar_g_jetpack)
1082 modifications = strcat(modifications, ", Jetpack");
1083 modifications = substring(modifications, 2, strlen(modifications) - 2);
1085 WriteString(msg_type, modifications);
1087 WriteString(msg_type, g_weaponarena_list);
1089 if(cache_lastmutatormsg != autocvar_g_mutatormsg)
1091 strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
1092 strcpy(cache_mutatormsg, cache_lastmutatormsg);
1095 WriteString(msg_type, cache_mutatormsg);
1097 WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
1104 Called when a client connects to the server
1107 void ClientConnect(entity this)
1109 if (Ban_MaybeEnforceBanOnce(this)) return;
1110 assert(!IS_CLIENT(this), return);
1111 this.flags |= FL_CLIENT;
1112 assert(player_count >= 0, player_count = 0);
1114 TRANSMUTE(Client, this);
1115 CS(this).version_nagtime = time + 10 + random() * 10;
1117 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
1119 bot_clientconnect(this);
1121 Player_DetermineForcedTeam(this);
1123 TRANSMUTE(Observer, this);
1125 PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
1127 // always track bots, don't ask for cl_allow_uidtracking
1128 if (IS_BOT_CLIENT(this))
1129 PlayerStats_GameReport_AddPlayer(this);
1131 CS(this).allowed_timeouts = autocvar_sv_timeout_number;
1133 if (autocvar_sv_eventlog)
1134 GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
1136 CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
1138 stuffcmd(this, clientstuff, "\n");
1139 stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1141 FixClientCvars(this);
1143 // get version info from player
1144 stuffcmd(this, "cmd clientversion $gameversion\n");
1146 // notify about available teams
1149 entity balance = TeamBalance_CheckAllowedTeams(this);
1150 int t = TeamBalance_GetAllowedTeams(balance);
1151 TeamBalance_Destroy(balance);
1152 stuffcmd(this, sprintf("set _teams_available %d\n", t));
1156 stuffcmd(this, "set _teams_available 0\n");
1159 bot_relinkplayerlist();
1161 CS(this).spectatortime = time;
1162 if (blockSpectators)
1164 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1167 CS(this).jointime = time;
1169 if (IS_REAL_CLIENT(this))
1171 if (g_weaponarena_weapons == WEPSET(TUBA))
1172 stuffcmd(this, "cl_cmd settemp chase_active 1\n");
1173 // quickmenu file must be put in a subfolder with an unique name
1174 // to reduce chances of overriding custom client quickmenus
1175 if (waypointeditor_enabled)
1176 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
1177 else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
1178 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
1181 if (!autocvar_sv_foginterval && world.fog != "")
1182 stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1184 if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
1185 if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
1186 send_CSQC_teamnagger();
1188 CSQCMODEL_AUTOINIT(this);
1190 CS(this).model_randomizer = random();
1192 if (IS_REAL_CLIENT(this))
1193 sv_notice_join(this);
1195 this.move_qcphysics = true;
1197 // update physics stats (players can spawn before physics runs)
1198 Physics_UpdateStats(this);
1200 IL_EACH(g_initforplayer, it.init_for_player, {
1201 it.init_for_player(it, this);
1204 Handicap_Initialize(this);
1207 if (PlayerInList(this, autocvar_g_playban_list))
1208 TRANSMUTE(Observer, this);
1210 if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
1211 CS(this).muted = true;
1213 MUTATOR_CALLHOOK(ClientConnect, this);
1215 if (player_count == 1)
1217 if (autocvar_sv_autopause && server_is_dedicated)
1219 localcmd("\nsv_hook_firstjoin\n");
1223 .string shootfromfixedorigin;
1224 .entity chatbubbleentity;
1225 void player_powerups_remove_all(entity this);
1231 Called when a client disconnects from the server
1234 void ClientDisconnect(entity this)
1236 assert(IS_CLIENT(this), return);
1238 /* from "ignore" command */
1239 strfree(this.ignore_list);
1240 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it.ignore_list,
1242 if(it.crypto_idfp && it.crypto_idfp != "")
1244 string mylist = ignore_removefromlist(it, this);
1246 strunzone(it.ignore_list);
1248 it.ignore_list = strzone(mylist);
1250 /* from "ignore" command */
1252 PlayerStats_GameReport_FinalizePlayer(this);
1253 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
1254 if (CS(this).active_minigame) part_minigame(this);
1255 if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
1257 if (autocvar_sv_eventlog)
1258 GameLogEcho(strcat(":part:", ftos(this.playerid)));
1260 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
1263 SetSpectatee(this, NULL);
1265 MUTATOR_CALLHOOK(ClientDisconnect, this);
1267 strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
1268 strfree(CS_CVAR(this).weaponorder_byimpulse);
1269 ClientState_detach(this);
1271 Portal_ClearAll(this);
1273 Unfreeze(this, false);
1275 RemoveGrapplingHooks(this);
1277 strfree(this.shootfromfixedorigin);
1279 // Here, everything has been done that requires this player to be a client.
1281 this.flags &= ~FL_CLIENT;
1283 if (this.chatbubbleentity) delete(this.chatbubbleentity);
1284 if (this.killindicator) delete(this.killindicator);
1286 IL_EACH(g_counters, it.realowner == this,
1291 WaypointSprite_PlayerGone(this);
1293 bot_relinkplayerlist();
1295 strfree(this.clientstatus);
1296 if (this.personal) delete(this.personal);
1299 if (warmup_stage || game_starttime > time) ReadyCount();
1300 if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
1302 player_powerups_remove_all(this); // stop powerup sound
1306 if (player_count == 0)
1307 localcmd("\nsv_hook_lastleave\n");
1310 void ChatBubbleThink(entity this)
1312 this.nextthink = time;
1313 if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this)
1315 if(this.owner) // but why can that ever be NULL?
1316 this.owner.chatbubbleentity = NULL;
1323 if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
1325 if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
1326 this.mdl = "models/sprites/minigame_busy.iqm";
1327 else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
1328 this.mdl = "models/misc/chatbubble.spr";
1331 if ( this.model != this.mdl )
1332 _setmodel(this, this.mdl);
1336 void UpdateChatBubble(entity this)
1340 // spawn a chatbubble entity if needed
1341 if (!this.chatbubbleentity)
1343 this.chatbubbleentity = new(chatbubbleentity);
1344 this.chatbubbleentity.owner = this;
1345 this.chatbubbleentity.exteriormodeltoclient = this;
1346 setthink(this.chatbubbleentity, ChatBubbleThink);
1347 this.chatbubbleentity.nextthink = time;
1348 setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below
1349 //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1');
1350 setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1');
1351 setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth
1352 this.chatbubbleentity.mdl = this.chatbubbleentity.model;
1353 //this.chatbubbleentity.model = "";
1354 this.chatbubbleentity.effects = EF_LOWPRECISION;
1358 void calculate_player_respawn_time(entity this)
1360 if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
1363 float gametype_setting_tmp;
1364 float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
1365 float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
1366 float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
1367 float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
1368 float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
1369 float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
1371 float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
1374 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1375 if(it.team == this.team)
1378 if (sdelay_small_count == 0)
1379 sdelay_small_count = 1;
1380 if (sdelay_large_count == 0)
1381 sdelay_large_count = 1;
1385 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1388 if (sdelay_small_count == 0)
1390 if (IS_INDEPENDENT_PLAYER(this))
1392 // Players play independently. No point in requiring enemies.
1393 sdelay_small_count = 1;
1397 // Players play AGAINST each other. Enemies required.
1398 sdelay_small_count = 2;
1401 if (sdelay_large_count == 0)
1403 if (IS_INDEPENDENT_PLAYER(this))
1405 // Players play independently. No point in requiring enemies.
1406 sdelay_large_count = 1;
1410 // Players play AGAINST each other. Enemies required.
1411 sdelay_large_count = 2;
1418 if (pcount <= sdelay_small_count)
1419 sdelay = sdelay_small;
1420 else if (pcount >= sdelay_large_count)
1421 sdelay = sdelay_large;
1422 else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
1423 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
1426 this.respawn_time = ceil((time + sdelay) / waves) * waves;
1428 this.respawn_time = time + sdelay;
1430 if(sdelay < sdelay_max)
1431 this.respawn_time_max = time + sdelay_max;
1433 this.respawn_time_max = this.respawn_time;
1435 if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
1436 this.respawn_countdown = 10; // first number to count down from is 10
1438 this.respawn_countdown = -1; // do not count down
1440 if(autocvar_g_forced_respawn)
1441 this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
1444 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1445 // added to the model skins
1446 /*void UpdateColorModHack()
1449 c = this.clientcolors & 15;
1450 // LordHavoc: only bothering to support white, green, red, yellow, blue
1451 if (!teamplay) this.colormod = '0 0 0';
1452 else if (c == 0) this.colormod = '1.00 1.00 1.00';
1453 else if (c == 3) this.colormod = '0.10 1.73 0.10';
1454 else if (c == 4) this.colormod = '1.73 0.10 0.10';
1455 else if (c == 12) this.colormod = '1.22 1.22 0.10';
1456 else if (c == 13) this.colormod = '0.10 0.10 1.73';
1457 else this.colormod = '1 1 1';
1460 void respawn(entity this)
1462 bool damagedbycontents_prev = this.damagedbycontents;
1465 if(autocvar_g_respawn_ghosts)
1467 this.solid = SOLID_NOT;
1468 this.takedamage = DAMAGE_NO;
1469 this.damagedbycontents = false;
1470 set_movetype(this, MOVETYPE_FLY);
1471 this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1472 this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1473 this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1474 this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha);
1475 Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1);
1476 if(autocvar_g_respawn_ghosts_time > 0)
1477 SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime);
1480 SUB_SetFade (this, time, 1); // fade out the corpse immediately
1484 this.damagedbycontents = damagedbycontents_prev;
1486 this.effects |= EF_NODRAW; // prevent another CopyBody
1487 PutClientInServer(this);
1490 void play_countdown(entity this, float finished, Sound samp)
1493 float time_left = finished - time;
1494 if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
1495 sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
1498 // it removes special powerups not handled by StatusEffects
1499 void player_powerups_remove_all(entity this)
1501 if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1503 // don't play the poweroff sound when the game restarts or the player disconnects
1504 if (time > game_starttime + 1 && IS_CLIENT(this)
1505 && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
1507 sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
1509 if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1510 stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
1511 this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
1515 void player_powerups(entity this)
1517 if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
1518 this.modelflags |= MF_ROCKET;
1520 this.modelflags &= ~MF_ROCKET;
1522 this.effects &= ~EF_NODEPTHTEST;
1525 player_powerups_remove_all(this);
1527 if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
1530 // add a way to see what the items were BEFORE all of these checks for the mutator hook
1531 int items_prev = this.items;
1533 if (!MUTATOR_IS_ENABLED(mutator_instagib))
1535 // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
1536 if (this.items & IT_SUPERWEAPON)
1538 if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
1540 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
1541 this.items = this.items - (this.items & IT_SUPERWEAPON);
1542 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
1543 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1545 else if (this.items & IT_UNLIMITED_SUPERWEAPONS)
1547 // don't let them run out
1551 play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
1552 if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
1554 this.items = this.items - (this.items & IT_SUPERWEAPON);
1555 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1556 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname);
1557 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1561 else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
1563 if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
1565 this.items = this.items | IT_SUPERWEAPON;
1566 if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
1569 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
1570 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1575 if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
1576 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
1577 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1580 else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
1582 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
1586 if(autocvar_g_nodepthtestplayers)
1587 this.effects = this.effects | EF_NODEPTHTEST;
1589 if(autocvar_g_fullbrightplayers)
1590 this.effects = this.effects | EF_FULLBRIGHT;
1592 MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
1595 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1597 if(current > stable)
1599 else if(current > stable - 0.25) // when close enough, "snap"
1602 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1605 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1607 if(current < stable)
1609 else if(current < stable + 0.25) // when close enough, "snap"
1612 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1615 void RotRegen(entity this, Resource res, float limit_mod,
1616 float regenstable, float regenfactor, float regenlinear, float regenframetime,
1617 float rotstable, float rotfactor, float rotlinear, float rotframetime)
1619 float old = GetResource(this, res);
1620 float current = old;
1621 if(current > rotstable)
1623 if(rotframetime > 0)
1625 current = CalcRot(current, rotstable, rotfactor, rotframetime);
1626 current = max(rotstable, current - rotlinear * rotframetime);
1629 else if(current < regenstable)
1631 if(regenframetime > 0)
1633 current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1634 current = min(regenstable, current + regenlinear * regenframetime);
1638 float limit = GetResourceLimit(this, res) * limit_mod;
1643 SetResource(this, res, current);
1646 void player_regen(entity this)
1648 float max_mod, regen_mod, rot_mod, limit_mod;
1649 max_mod = regen_mod = rot_mod = limit_mod = 1;
1651 float regen_health = autocvar_g_balance_health_regen;
1652 float regen_health_linear = autocvar_g_balance_health_regenlinear;
1653 float regen_health_rot = autocvar_g_balance_health_rot;
1654 float regen_health_rotlinear = autocvar_g_balance_health_rotlinear;
1655 float regen_health_stable = autocvar_g_balance_health_regenstable;
1656 float regen_health_rotstable = autocvar_g_balance_health_rotstable;
1657 bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot,
1658 regen_health_rotlinear, regen_health_stable, regen_health_rotstable);
1659 max_mod = M_ARGV(1, float);
1660 regen_mod = M_ARGV(2, float);
1661 rot_mod = M_ARGV(3, float);
1662 limit_mod = M_ARGV(4, float);
1663 regen_health = M_ARGV(5, float);
1664 regen_health_linear = M_ARGV(6, float);
1665 regen_health_rot = M_ARGV(7, float);
1666 regen_health_rotlinear = M_ARGV(8, float);
1667 regen_health_stable = M_ARGV(9, float);
1668 regen_health_rotstable = M_ARGV(10, float);
1670 float rotstable, regenstable, rotframetime, regenframetime;
1672 if(!mutator_returnvalue)
1673 if(!STAT(FROZEN, this))
1675 regenstable = autocvar_g_balance_armor_regenstable;
1676 rotstable = autocvar_g_balance_armor_rotstable;
1677 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1678 rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
1679 RotRegen(this, RES_ARMOR, limit_mod,
1680 regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
1681 rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
1683 // NOTE: max_mod is only applied to health
1684 regenstable = regen_health_stable * max_mod;
1685 rotstable = regen_health_rotstable * max_mod;
1686 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1687 rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
1688 RotRegen(this, RES_HEALTH, limit_mod,
1689 regenstable, regen_health, regen_health_linear, regenframetime,
1690 rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
1693 // if player rotted to death... die!
1694 // check this outside above checks, as player may still be able to rot to death
1695 if(GetResource(this, RES_HEALTH) < 1)
1698 vehicles_exit(this.vehicle, VHEF_RELEASE);
1699 if(this.event_damage)
1700 this.event_damage(this, this, this, 1, DEATH_ROT.m_id, DMG_NOWEP, this.origin, '0 0 0');
1703 if (!(this.items & IT_UNLIMITED_AMMO))
1705 regenstable = autocvar_g_balance_fuel_regenstable;
1706 rotstable = autocvar_g_balance_fuel_rotstable;
1707 regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
1708 rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
1709 RotRegen(this, RES_FUEL, 1,
1710 regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
1711 rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
1716 void SetZoomState(entity this, float newzoom)
1718 if(newzoom != CS(this).zoomstate)
1720 CS(this).zoomstate = newzoom;
1721 ClientData_Touch(this);
1723 zoomstate_set = true;
1726 void GetPressedKeys(entity this)
1728 MUTATOR_CALLHOOK(GetPressedKeys, this);
1731 CS(this).pressedkeys = 0;
1732 STAT(PRESSED_KEYS, this) = 0;
1736 // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
1737 int keys = STAT(PRESSED_KEYS, this);
1738 keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0);
1739 keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0);
1740 keys = BITSET(keys, KEY_RIGHT, CS(this).movement.y > 0);
1741 keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
1743 keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
1744 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
1745 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
1746 keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
1747 CS(this).pressedkeys = keys; // store for other users
1749 STAT(PRESSED_KEYS, this) = keys;
1753 ======================
1754 spectate mode routines
1755 ======================
1758 void SpectateCopy(entity this, entity spectatee)
1760 TC(Client, this); TC(Client, spectatee);
1762 MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
1763 PS(this) = PS(spectatee);
1764 this.armortype = spectatee.armortype;
1765 SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
1766 SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
1767 SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
1768 SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
1769 SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
1770 SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
1771 SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
1772 this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1773 SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
1774 CS(this).impulse = 0;
1775 this.disableclientprediction = 1; // no need to run prediction on a spectator
1776 this.items = spectatee.items;
1777 STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
1778 STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
1779 STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
1780 STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
1781 STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
1782 this.punchangle = spectatee.punchangle;
1783 this.view_ofs = spectatee.view_ofs;
1784 this.velocity = spectatee.velocity;
1785 this.dmg_take = spectatee.dmg_take;
1786 this.dmg_save = spectatee.dmg_save;
1787 this.dmg_inflictor = spectatee.dmg_inflictor;
1788 this.v_angle = spectatee.v_angle;
1789 this.angles = spectatee.v_angle;
1790 STAT(FROZEN, this) = STAT(FROZEN, spectatee);
1791 STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee);
1792 this.viewloc = spectatee.viewloc;
1793 if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
1794 this.fixangle = true;
1795 setorigin(this, spectatee.origin);
1796 setsize(this, spectatee.mins, spectatee.maxs);
1797 SetZoomState(this, CS(spectatee).zoomstate);
1799 anticheat_spectatecopy(this, spectatee);
1800 STAT(HUD, this) = STAT(HUD, spectatee);
1801 if(spectatee.vehicle)
1803 this.angles = spectatee.v_angle;
1805 //this.fixangle = false;
1806 //this.velocity = spectatee.vehicle.velocity;
1807 this.vehicle_health = spectatee.vehicle_health;
1808 this.vehicle_shield = spectatee.vehicle_shield;
1809 this.vehicle_energy = spectatee.vehicle_energy;
1810 this.vehicle_ammo1 = spectatee.vehicle_ammo1;
1811 this.vehicle_ammo2 = spectatee.vehicle_ammo2;
1812 this.vehicle_reload1 = spectatee.vehicle_reload1;
1813 this.vehicle_reload2 = spectatee.vehicle_reload2;
1815 //msg_entity = this;
1817 // WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1818 //WriteAngle(MSG_ONE, spectatee.v_angle.x);
1819 // WriteAngle(MSG_ONE, spectatee.v_angle.y);
1820 // WriteAngle(MSG_ONE, spectatee.v_angle.z);
1822 //WriteByte (MSG_ONE, SVC_SETVIEW);
1823 // WriteEntity(MSG_ONE, this);
1824 //makevectors(spectatee.v_angle);
1825 //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/
1829 bool SpectateUpdate(entity this)
1834 if(!IS_PLAYER(this.enemy) || this == this.enemy)
1836 SetSpectatee(this, NULL);
1840 SpectateCopy(this, this.enemy);
1845 bool SpectateSet(entity this)
1847 if(!IS_PLAYER(this.enemy))
1850 ClientData_Touch(this.enemy);
1853 WriteByte(MSG_ONE, SVC_SETVIEW);
1854 WriteEntity(MSG_ONE, this.enemy);
1855 set_movetype(this, MOVETYPE_NONE);
1856 accuracy_resend(this);
1858 if(!SpectateUpdate(this))
1859 PutObserverInServer(this, false, true);
1864 void SetSpectatee_status(entity this, int spectatee_num)
1866 int oldspectatee_status = CS(this).spectatee_status;
1867 CS(this).spectatee_status = spectatee_num;
1869 if (CS(this).spectatee_status != oldspectatee_status)
1871 if (STAT(PRESSED_KEYS, this))
1873 CS(this).pressedkeys = 0;
1874 STAT(PRESSED_KEYS, this) = 0;
1876 ClientData_Touch(this);
1877 if (g_race || g_cts) race_InitSpectator();
1881 void SetSpectatee(entity this, entity spectatee)
1883 if(IS_BOT_CLIENT(this))
1884 return; // bots abuse .enemy, this code is useless to them
1886 entity old_spectatee = this.enemy;
1888 this.enemy = spectatee;
1891 // these are required to fix the spectator bug with arc
1894 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1896 .entity weaponentity = weaponentities[slot];
1897 if(old_spectatee.(weaponentity).arc_beam)
1898 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1903 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1905 .entity weaponentity = weaponentities[slot];
1906 if(spectatee.(weaponentity).arc_beam)
1907 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1912 SetSpectatee_status(this, etof(spectatee));
1914 // needed to update spectator list
1915 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1918 bool Spectate(entity this, entity pl)
1920 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1922 pl = M_ARGV(1, entity);
1924 SetSpectatee(this, pl);
1925 return SpectateSet(this);
1928 bool SpectateNext(entity this)
1930 entity ent = find(this.enemy, classname, STR_PLAYER);
1932 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1933 ent = M_ARGV(1, entity);
1935 ent = find(ent, classname, STR_PLAYER);
1937 if(ent) { SetSpectatee(this, ent); }
1939 return SpectateSet(this);
1942 bool SpectatePrev(entity this)
1944 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1945 entity ent = findchain(classname, STR_PLAYER);
1946 if (!ent) // no player
1950 // skip players until current spectated player
1952 while(ent && ent != this.enemy)
1955 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1957 case MUT_SPECPREV_FOUND:
1958 ent = M_ARGV(1, entity);
1960 case MUT_SPECPREV_RETURN:
1962 case MUT_SPECPREV_CONTINUE:
1973 SetSpectatee(this, ent);
1974 return SpectateSet(this);
1979 ShowRespawnCountdown()
1981 Update a respawn countdown display.
1984 void ShowRespawnCountdown(entity this)
1987 if(!IS_DEAD(this)) // just respawned?
1991 number = ceil(this.respawn_time - time);
1994 if(number <= this.respawn_countdown)
1996 this.respawn_countdown = number - 1;
1997 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
1998 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
2003 .bool team_selected;
2004 bool ShowTeamSelection(entity this)
2006 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
2008 if (frametime) // once per frame is more than enough
2009 stuffcmd(this, "_scoreboard_team_selection 1\n");
2012 void Join(entity this)
2014 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2017 TRANSMUTE(Player, this);
2019 if(!this.team_selected)
2020 if(autocvar_g_campaign || autocvar_g_balance_teams)
2021 TeamBalance_JoinBestTeam(this);
2023 if(autocvar_g_campaign)
2024 campaign_bots_may_start = true;
2026 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2028 PutClientInServer(this);
2031 if(teamplay && this.team != -1)
2035 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2036 this.team_selected = false;
2039 int GetPlayerLimit()
2042 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)
2043 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2044 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2045 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2046 player_limit = M_ARGV(0, int);
2047 return player_limit < maxclients ? player_limit : 0;
2051 * Determines whether the player is allowed to join. This depends on cvar
2052 * g_maxplayers, if it isn't used this function always return true, otherwise
2053 * it checks whether the number of currently playing players exceeds g_maxplayers.
2054 * @return int number of free slots for players, 0 if none
2056 int nJoinAllowed(entity this, entity ignore)
2059 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2060 // so report 0 free slots if restricted
2062 if(autocvar_g_forced_team_otherwise == "spectate")
2064 if(autocvar_g_forced_team_otherwise == "spectator")
2068 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2069 return 0; // forced spectators can never join
2071 static float msg_time = 0;
2072 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2076 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2077 msg_time = time + 0.5;
2082 // TODO simplify this
2083 int totalClients = 0;
2084 int currentlyPlaying = 0;
2085 FOREACH_CLIENT(true, {
2088 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2092 int player_limit = GetPlayerLimit();
2096 free_slots = maxclients - totalClients;
2097 else if(player_limit > 0 && currentlyPlaying < player_limit)
2098 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2100 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2102 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2103 msg_time = time + 0.5;
2109 bool joinAllowed(entity this)
2111 if (CS(this).version_mismatch) return false;
2112 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2113 if (!nJoinAllowed(this, this)) return false;
2114 if (teamplay && lockteams) return false;
2115 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2116 if (ShowTeamSelection(this)) return false;
2120 void show_entnum(entity this)
2122 // waypoint editor implements a similar feature for waypoints
2123 if (waypointeditor_enabled)
2126 if (wasfreed(this.wp_aimed))
2127 this.wp_aimed = NULL;
2129 WarpZone_crosshair_trace_plusvisibletriggers(this);
2134 if (ent != this.wp_aimed)
2136 string str = sprintf(
2137 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2138 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2139 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2142 if (this.wp_aimed != ent)
2143 this.wp_aimed = ent;
2146 .bool dualwielding_prev;
2147 bool PlayerThink(entity this)
2149 if (game_stopped || intermission_running) {
2150 this.modelflags &= ~MF_ROCKET;
2151 if(intermission_running)
2152 IntermissionThink(this);
2156 if (timeout_status == TIMEOUT_ACTIVE) {
2157 // don't allow the player to turn around while game is paused
2158 // FIXME turn this into CSQC stuff
2159 this.v_angle = this.lastV_angle;
2160 this.angles = this.lastV_angle;
2161 this.fixangle = true;
2164 if (frametime) player_powerups(this);
2166 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2168 if (IS_DEAD(this)) {
2169 if (this.personal && g_race_qualifying) {
2170 if (time > this.respawn_time) {
2171 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2173 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2176 if (frametime) player_anim(this);
2178 if (this.respawn_flags & RESPAWN_DENY)
2180 STAT(RESPAWN_TIME, this) = 0;
2184 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));
2186 switch(this.deadflag)
2190 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2191 this.deadflag = DEAD_RESPAWNING;
2192 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2193 this.deadflag = DEAD_DEAD;
2199 this.deadflag = DEAD_RESPAWNABLE;
2200 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2201 this.deadflag = DEAD_RESPAWNING;
2204 case DEAD_RESPAWNABLE:
2206 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2207 this.deadflag = DEAD_RESPAWNING;
2210 case DEAD_RESPAWNING:
2212 if (time > this.respawn_time)
2214 this.respawn_time = time + 1; // only retry once a second
2215 this.respawn_time_max = this.respawn_time;
2222 ShowRespawnCountdown(this);
2224 if (this.respawn_flags & RESPAWN_SILENT)
2225 STAT(RESPAWN_TIME, this) = 0;
2226 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2228 if (time < this.respawn_time)
2229 STAT(RESPAWN_TIME, this) = this.respawn_time;
2230 else if (this.deadflag != DEAD_RESPAWNING)
2231 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2234 STAT(RESPAWN_TIME, this) = this.respawn_time;
2237 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2238 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2239 STAT(RESPAWN_TIME, this) *= -1;
2244 FixPlayermodel(this);
2246 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2247 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2248 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2251 // reset gun alignment when dual wielding status changes
2252 // to ensure guns are always aligned right and left
2253 bool dualwielding = W_DualWielding(this);
2254 if(this.dualwielding_prev != dualwielding)
2256 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2257 this.dualwielding_prev = dualwielding;
2260 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2263 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2265 .entity weaponentity = weaponentities[slot];
2266 if(WEP_CVAR(vortex, charge_always))
2267 W_Vortex_Charge(this, weaponentity, frametime);
2268 W_WeaponFrame(this, weaponentity);
2274 // WEAPONTODO: Add a weapon request for this
2275 // rot vortex charge to the charge limit
2276 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2278 .entity weaponentity = weaponentities[slot];
2279 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2280 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2285 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2288 monsters_setstatus(this);
2293 .bool would_spectate;
2294 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2295 void ObserverOrSpectatorThink(entity this)
2297 bool is_spec = IS_SPEC(this);
2298 if ( CS(this).impulse )
2300 int r = MinigameImpulse(this, CS(this).impulse);
2302 CS(this).impulse = 0;
2304 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2306 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2307 CS(this).impulse = 0;
2312 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2314 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2316 CS(this).autojoin_checked = true;
2317 TRANSMUTE(Player, this);
2318 PutClientInServer(this);
2320 .entity weaponentity = weaponentities[0];
2321 if(this.(weaponentity).m_weapon == WEP_Null)
2322 W_NextWeapon(this, 0, weaponentity);
2327 if (this.flags & FL_JUMPRELEASED) {
2328 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2329 this.flags &= ~FL_JUMPRELEASED;
2330 this.flags |= FL_SPAWNING;
2331 } 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)))
2332 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2333 this.flags &= ~FL_JUMPRELEASED;
2334 if(SpectateNext(this)) {
2335 TRANSMUTE(Spectator, this);
2336 } else if (is_spec) {
2337 TRANSMUTE(Observer, this);
2338 PutClientInServer(this);
2341 this.would_spectate = false; // unable to spectate anyone
2343 CS(this).impulse = 0;
2344 } else if (is_spec) {
2345 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2346 this.flags &= ~FL_JUMPRELEASED;
2347 if(SpectatePrev(this)) {
2348 TRANSMUTE(Spectator, this);
2350 TRANSMUTE(Observer, this);
2351 PutClientInServer(this);
2353 CS(this).impulse = 0;
2354 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2355 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2356 this.would_spectate = false;
2357 this.flags &= ~FL_JUMPRELEASED;
2358 TRANSMUTE(Observer, this);
2359 PutClientInServer(this);
2361 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2362 PutObserverInServer(this, false, true);
2363 this.would_spectate = true;
2367 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2368 if (PHYS_INPUT_BUTTON_USE(this))
2369 wouldclip = !wouldclip;
2370 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2371 set_movetype(this, preferred_movetype);
2373 } else { // jump pressed
2374 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2375 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2376 this.flags |= FL_JUMPRELEASED;
2377 // primary attack pressed
2378 if(this.flags & FL_SPAWNING)
2380 this.flags &= ~FL_SPAWNING;
2381 if(joinAllowed(this))
2383 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2384 CS(this).autojoin_checked = -1;
2388 if(is_spec && !SpectateUpdate(this))
2389 PutObserverInServer(this, false, true);
2392 this.flags |= FL_CLIENT | FL_NOTARGET;
2395 void PlayerUseKey(entity this)
2397 if (!IS_PLAYER(this))
2404 vehicles_exit(this.vehicle, VHEF_NORMAL);
2408 else if(autocvar_g_vehicles_enter)
2410 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2412 entity head, closest_target = NULL;
2413 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2415 while(head) // find the closest acceptable target to enter
2417 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2418 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2422 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2423 { closest_target = head; }
2425 else { closest_target = head; }
2431 if(closest_target) { vehicles_enter(this, closest_target); return; }
2435 // a use key was pressed; call handlers
2436 MUTATOR_CALLHOOK(PlayerUseKey, this);
2444 Called every frame for each real client by DP (and for each bot by StartFrame()),
2445 and when executing every asynchronous move, so only include things that MUST be done then.
2446 Use PlayerFrame() instead for code that only needs to run once per server frame.
2447 frametime == 0 in the asynchronous code path.
2449 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2452 .float last_vehiclecheck;
2453 void PlayerPreThink (entity this)
2455 WarpZone_PlayerPhysics_FixVAngle(this);
2457 zoomstate_set = false;
2459 MUTATOR_CALLHOOK(PlayerPreThink, this);
2461 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2463 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2465 if (IS_PLAYER(this)) {
2466 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2467 error("Client can't be spawned as player on connection!");
2468 if(!PlayerThink(this))
2471 else if (game_stopped || intermission_running) {
2472 if(intermission_running)
2473 IntermissionThink(this);
2476 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2478 bool early_join_requested = (CS(this).autojoin_checked < 0);
2479 CS(this).autojoin_checked = 1;
2480 // don't do this in ClientConnect
2481 // many things can go wrong if a client is spawned as player on connection
2482 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2483 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2484 && (!teamplay || autocvar_g_balance_teams)))
2486 if(joinAllowed(this))
2491 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2492 ObserverOrSpectatorThink(this);
2495 // WEAPONTODO: Add weapon request for this
2496 if (!zoomstate_set) {
2497 bool wep_zoomed = false;
2498 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2500 .entity weaponentity = weaponentities[slot];
2501 Weapon thiswep = this.(weaponentity).m_weapon;
2502 if(thiswep != WEP_Null && thiswep.wr_zoom)
2503 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2505 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2508 // Voice sound effects
2509 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2511 CS(this).teamkill_soundtime = 0;
2513 entity e = CS(this).teamkill_soundsource;
2514 entity oldpusher = e.pusher;
2516 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2517 e.pusher = oldpusher;
2520 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2521 CS(this).taunt_soundtime = 0;
2522 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2525 target_voicescript_next(this);
2528 void DrownPlayer(entity this)
2530 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2531 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2533 STAT(AIR_FINISHED, this) = 0;
2537 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2539 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2540 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2541 STAT(AIR_FINISHED, this) = 0;
2545 if (!STAT(AIR_FINISHED, this))
2546 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2547 if (STAT(AIR_FINISHED, this) < time)
2549 if (this.pain_finished < time)
2551 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');
2552 this.pain_finished = time + 0.5;
2558 .bool move_qcphysics;
2560 void Player_Physics(entity this)
2562 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2564 if(!this.move_qcphysics)
2567 if(!frametime && !CS(this).pm_frametime)
2570 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2572 CS(this).pm_frametime = 0;
2579 Called every frame for each real client by DP (and for each bot by StartFrame()),
2580 and when executing every asynchronous move, so only include things that MUST be done then.
2581 Use PlayerFrame() instead for code that only needs to run once per server frame.
2582 frametime == 0 in the asynchronous code path.
2585 void PlayerPostThink (entity this)
2587 Player_Physics(this);
2589 if (IS_PLAYER(this)) {
2590 if(this.death_time == time && IS_DEAD(this))
2592 // player's bbox gets resized now, instead of in the damage event that killed the player,
2593 // once all the damage events of this frame have been processed with normal size
2595 setsize(this, this.mins, this.maxs);
2598 UpdateChatBubble(this);
2599 if (CS(this).impulse) ImpulseCommands(this);
2600 GetPressedKeys(this);
2603 CSQCMODEL_AUTOUPDATE(this);
2607 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2609 CS(this).pressedkeys = 0;
2610 STAT(PRESSED_KEYS, this) = 0;
2613 CSQCMODEL_AUTOUPDATE(this);
2620 Called every frame for each client by StartFrame().
2621 Use this for code that only needs to run once per server frame.
2622 frametime is always set here.
2625 void PlayerFrame (entity this)
2627 // formerly PreThink code
2628 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2629 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2631 // physics frames: update anticheat stuff
2632 anticheat_prethink(this);
2634 // Check if spectating is allowed
2635 if (blockSpectators && IS_REAL_CLIENT(this)
2636 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2637 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2639 if (dropclient_schedule(this))
2640 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2643 // Check for nameless players
2644 if (this.netname == "" || this.netname != CS(this).netname_previous)
2646 bool assume_unchanged = (CS(this).netname_previous == "");
2647 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2649 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2650 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2651 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2652 assume_unchanged = false;
2653 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2655 if (isInvisibleString(this.netname))
2657 this.netname = strzone(sprintf("Player#%d", this.playerid));
2658 sprint(this, "Warning: invisible names are not allowed.\n");
2659 assume_unchanged = false;
2660 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2662 if (!assume_unchanged && autocvar_sv_eventlog)
2663 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2664 strcpy(CS(this).netname_previous, this.netname);
2668 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2670 CS(this).version_nagtime = 0;
2671 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2675 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2678 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2682 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2683 if (r < 0) // old client
2684 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2685 else if (r > 0) // old server
2686 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2691 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2693 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2694 this.max_armorvalue = 0;
2698 if (IS_PLAYER(this) && time >= game_starttime)
2700 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2702 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2703 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2705 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2707 if (STAT(REVIVE_PROGRESS, this) >= 1)
2708 Unfreeze(this, false);
2710 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2712 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2713 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2715 if (GetResource(this, RES_HEALTH) < 1)
2718 vehicles_exit(this.vehicle, VHEF_RELEASE);
2719 if(this.event_damage)
2720 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2722 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2723 Unfreeze(this, false);
2728 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2729 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2731 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2735 if(!it.team || SAME_TEAM(this, it))
2736 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2737 else if(autocvar_g_vehicles_steal)
2738 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2740 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2742 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2746 this.last_vehiclecheck = time + 1;
2751 // formerly PostThink code
2752 if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
2753 if (IS_REAL_CLIENT(this))
2754 if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
2755 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2757 int totalClients = 0;
2758 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2760 // maxidle disabled in local matches by not counting clients (totalClients 0)
2761 if (server_is_dedicated)
2763 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2767 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2771 else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2773 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2779 if (totalClients < autocvar_sv_maxidle_minplayers)
2781 // idle kick disabled
2782 CS(this).parm_idlesince = time;
2784 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2786 if (CS(this).idlekick_lasttimeleft)
2788 CS(this).idlekick_lasttimeleft = 0;
2789 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2794 float maxidle_time = autocvar_sv_maxidle;
2795 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2796 maxidle_time = autocvar_sv_maxidle_playertospectator;
2797 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2798 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2799 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2801 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2802 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2804 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2806 if (timeleft <= 0) {
2807 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2809 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2810 PutObserverInServer(this, true, true);
2814 if (dropclient_schedule(this))
2815 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2819 else if (timeleft <= countdown_time) {
2820 if (timeleft != CS(this).idlekick_lasttimeleft)
2821 play2(this, SND(TALK2));
2822 CS(this).idlekick_lasttimeleft = timeleft;
2831 this.solid = SOLID_NOT;
2832 this.takedamage = DAMAGE_NO;
2833 set_movetype(this, MOVETYPE_NONE);
2834 CS(this).teamkill_complain = 0;
2835 CS(this).teamkill_soundtime = 0;
2836 CS(this).teamkill_soundsource = NULL;
2839 if (this.waypointsprite_attachedforcarrier) {
2840 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2841 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2845 // hack to copy the button fields from the client entity to the Client State
2846 void PM_UpdateButtons(entity this, entity store)
2849 store.impulse = this.impulse;
2852 bool typing = this.buttonchat || this.button12;
2854 store.button0 = (typing) ? 0 : this.button0;
2856 store.button2 = (typing) ? 0 : this.button2;
2857 store.button3 = (typing) ? 0 : this.button3;
2858 store.button4 = this.button4;
2859 store.button5 = (typing) ? 0 : this.button5;
2860 store.button6 = this.button6;
2861 store.button7 = this.button7;
2862 store.button8 = this.button8;
2863 store.button9 = this.button9;
2864 store.button10 = this.button10;
2865 store.button11 = this.button11;
2866 store.button12 = this.button12;
2867 store.button13 = this.button13;
2868 store.button14 = this.button14;
2869 store.button15 = this.button15;
2870 store.button16 = this.button16;
2871 store.buttonuse = this.buttonuse;
2872 store.buttonchat = this.buttonchat;
2874 store.cursor_active = this.cursor_active;
2875 store.cursor_screen = this.cursor_screen;
2876 store.cursor_trace_start = this.cursor_trace_start;
2877 store.cursor_trace_endpos = this.cursor_trace_endpos;
2878 store.cursor_trace_ent = this.cursor_trace_ent;
2880 store.ping = this.ping;
2881 store.ping_packetloss = this.ping_packetloss;
2882 store.ping_movementloss = this.ping_movementloss;
2884 store.v_angle = this.v_angle;
2885 store.movement = this.movement;
2888 NET_HANDLE(fpsreport, bool)
2890 int fps = ReadShort();
2891 PlayerScore_Set(sender, SP_FPS, fps);