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 WriteByte(msg_type, boolean(autocvar_g_campaign));
1045 if (boolean(autocvar_g_campaign))
1047 WriteByte(msg_type, Campaign_GetLevelNum());
1050 WriteString(msg_type, autocvar_hostname);
1051 WriteString(msg_type, autocvar_g_xonoticversion);
1052 WriteByte(msg_type, CS(this).version_mismatch);
1053 WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
1054 WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
1055 WriteByte(msg_type, GetPlayerLimit());
1057 MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
1058 string modifications = M_ARGV(0, string);
1060 if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
1061 modifications = strcat(modifications, ", No start weapons");
1062 if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
1063 modifications = strcat(modifications, ", Low gravity");
1064 if(g_weapon_stay && !g_cts)
1065 modifications = strcat(modifications, ", Weapons stay");
1066 if(autocvar_g_jetpack)
1067 modifications = strcat(modifications, ", Jetpack");
1068 modifications = substring(modifications, 2, strlen(modifications) - 2);
1070 WriteString(msg_type, modifications);
1072 WriteString(msg_type, g_weaponarena_list);
1074 if(cache_lastmutatormsg != autocvar_g_mutatormsg)
1076 strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
1077 strcpy(cache_mutatormsg, cache_lastmutatormsg);
1080 WriteString(msg_type, cache_mutatormsg);
1082 WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
1089 Called when a client connects to the server
1092 void ClientConnect(entity this)
1094 if (Ban_MaybeEnforceBanOnce(this)) return;
1095 assert(!IS_CLIENT(this), return);
1096 this.flags |= FL_CLIENT;
1097 assert(player_count >= 0, player_count = 0);
1099 TRANSMUTE(Client, this);
1100 CS(this).version_nagtime = time + 10 + random() * 10;
1102 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
1104 bot_clientconnect(this);
1106 Player_DetermineForcedTeam(this);
1108 TRANSMUTE(Observer, this);
1110 PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
1112 // always track bots, don't ask for cl_allow_uidtracking
1113 if (IS_BOT_CLIENT(this))
1114 PlayerStats_GameReport_AddPlayer(this);
1116 CS(this).allowed_timeouts = autocvar_sv_timeout_number;
1118 if (autocvar_sv_eventlog)
1119 GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
1121 CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
1123 stuffcmd(this, clientstuff, "\n");
1124 stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1126 FixClientCvars(this);
1128 // get version info from player
1129 stuffcmd(this, "cmd clientversion $gameversion\n");
1131 // notify about available teams
1134 entity balance = TeamBalance_CheckAllowedTeams(this);
1135 int t = TeamBalance_GetAllowedTeams(balance);
1136 TeamBalance_Destroy(balance);
1137 stuffcmd(this, sprintf("set _teams_available %d\n", t));
1141 stuffcmd(this, "set _teams_available 0\n");
1144 bot_relinkplayerlist();
1146 CS(this).spectatortime = time;
1147 if (blockSpectators)
1149 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1152 CS(this).jointime = time;
1154 if (IS_REAL_CLIENT(this))
1156 if (g_weaponarena_weapons == WEPSET(TUBA))
1157 stuffcmd(this, "cl_cmd settemp chase_active 1\n");
1158 // quickmenu file must be put in a subfolder with an unique name
1159 // to reduce chances of overriding custom client quickmenus
1160 if (waypointeditor_enabled)
1161 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
1162 else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
1163 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
1166 if (!autocvar_sv_foginterval && world.fog != "")
1167 stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1169 if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
1170 if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
1171 send_CSQC_teamnagger();
1173 CSQCMODEL_AUTOINIT(this);
1175 CS(this).model_randomizer = random();
1177 if (IS_REAL_CLIENT(this))
1178 sv_notice_join(this);
1180 this.move_qcphysics = true;
1182 // update physics stats (players can spawn before physics runs)
1183 Physics_UpdateStats(this);
1185 IL_EACH(g_initforplayer, it.init_for_player, {
1186 it.init_for_player(it, this);
1189 Handicap_Initialize(this);
1192 if (PlayerInList(this, autocvar_g_playban_list))
1193 TRANSMUTE(Observer, this);
1195 if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
1196 CS(this).muted = true;
1198 MUTATOR_CALLHOOK(ClientConnect, this);
1200 if (player_count == 1)
1202 if (autocvar_sv_autopause && server_is_dedicated)
1204 localcmd("\nsv_hook_firstjoin\n");
1208 .string shootfromfixedorigin;
1209 .entity chatbubbleentity;
1210 void player_powerups_remove_all(entity this);
1216 Called when a client disconnects from the server
1219 void ClientDisconnect(entity this)
1221 assert(IS_CLIENT(this), return);
1223 /* from "ignore" command */
1224 strfree(this.ignore_list);
1225 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it.ignore_list,
1227 if(it.crypto_idfp && it.crypto_idfp != "")
1229 string mylist = ignore_removefromlist(it, this);
1231 strunzone(it.ignore_list);
1233 it.ignore_list = strzone(mylist);
1235 /* from "ignore" command */
1237 PlayerStats_GameReport_FinalizePlayer(this);
1238 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
1239 if (CS(this).active_minigame) part_minigame(this);
1240 if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
1242 if (autocvar_sv_eventlog)
1243 GameLogEcho(strcat(":part:", ftos(this.playerid)));
1245 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
1248 SetSpectatee(this, NULL);
1250 MUTATOR_CALLHOOK(ClientDisconnect, this);
1252 strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
1253 strfree(CS_CVAR(this).weaponorder_byimpulse);
1254 ClientState_detach(this);
1256 Portal_ClearAll(this);
1258 Unfreeze(this, false);
1260 RemoveGrapplingHooks(this);
1262 strfree(this.shootfromfixedorigin);
1264 // Here, everything has been done that requires this player to be a client.
1266 this.flags &= ~FL_CLIENT;
1268 if (this.chatbubbleentity) delete(this.chatbubbleentity);
1269 if (this.killindicator) delete(this.killindicator);
1271 IL_EACH(g_counters, it.realowner == this,
1276 WaypointSprite_PlayerGone(this);
1278 bot_relinkplayerlist();
1280 strfree(this.clientstatus);
1281 if (this.personal) delete(this.personal);
1284 if (warmup_stage || game_starttime > time) ReadyCount();
1285 if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
1287 player_powerups_remove_all(this); // stop powerup sound
1291 if (player_count == 0)
1292 localcmd("\nsv_hook_lastleave\n");
1295 void ChatBubbleThink(entity this)
1297 this.nextthink = time;
1298 if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this)
1300 if(this.owner) // but why can that ever be NULL?
1301 this.owner.chatbubbleentity = NULL;
1308 if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
1310 if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
1311 this.mdl = "models/sprites/minigame_busy.iqm";
1312 else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
1313 this.mdl = "models/misc/chatbubble.spr";
1316 if ( this.model != this.mdl )
1317 _setmodel(this, this.mdl);
1321 void UpdateChatBubble(entity this)
1325 // spawn a chatbubble entity if needed
1326 if (!this.chatbubbleentity)
1328 this.chatbubbleentity = new(chatbubbleentity);
1329 this.chatbubbleentity.owner = this;
1330 this.chatbubbleentity.exteriormodeltoclient = this;
1331 setthink(this.chatbubbleentity, ChatBubbleThink);
1332 this.chatbubbleentity.nextthink = time;
1333 setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below
1334 //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1');
1335 setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1');
1336 setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth
1337 this.chatbubbleentity.mdl = this.chatbubbleentity.model;
1338 //this.chatbubbleentity.model = "";
1339 this.chatbubbleentity.effects = EF_LOWPRECISION;
1343 void calculate_player_respawn_time(entity this)
1345 if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
1348 float gametype_setting_tmp;
1349 float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
1350 float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
1351 float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
1352 float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
1353 float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
1354 float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
1356 float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
1359 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1360 if(it.team == this.team)
1363 if (sdelay_small_count == 0)
1364 sdelay_small_count = 1;
1365 if (sdelay_large_count == 0)
1366 sdelay_large_count = 1;
1370 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1373 if (sdelay_small_count == 0)
1375 if (IS_INDEPENDENT_PLAYER(this))
1377 // Players play independently. No point in requiring enemies.
1378 sdelay_small_count = 1;
1382 // Players play AGAINST each other. Enemies required.
1383 sdelay_small_count = 2;
1386 if (sdelay_large_count == 0)
1388 if (IS_INDEPENDENT_PLAYER(this))
1390 // Players play independently. No point in requiring enemies.
1391 sdelay_large_count = 1;
1395 // Players play AGAINST each other. Enemies required.
1396 sdelay_large_count = 2;
1403 if (pcount <= sdelay_small_count)
1404 sdelay = sdelay_small;
1405 else if (pcount >= sdelay_large_count)
1406 sdelay = sdelay_large;
1407 else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
1408 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
1411 this.respawn_time = ceil((time + sdelay) / waves) * waves;
1413 this.respawn_time = time + sdelay;
1415 if(sdelay < sdelay_max)
1416 this.respawn_time_max = time + sdelay_max;
1418 this.respawn_time_max = this.respawn_time;
1420 if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
1421 this.respawn_countdown = 10; // first number to count down from is 10
1423 this.respawn_countdown = -1; // do not count down
1425 if(autocvar_g_forced_respawn)
1426 this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
1429 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1430 // added to the model skins
1431 /*void UpdateColorModHack()
1434 c = this.clientcolors & 15;
1435 // LordHavoc: only bothering to support white, green, red, yellow, blue
1436 if (!teamplay) this.colormod = '0 0 0';
1437 else if (c == 0) this.colormod = '1.00 1.00 1.00';
1438 else if (c == 3) this.colormod = '0.10 1.73 0.10';
1439 else if (c == 4) this.colormod = '1.73 0.10 0.10';
1440 else if (c == 12) this.colormod = '1.22 1.22 0.10';
1441 else if (c == 13) this.colormod = '0.10 0.10 1.73';
1442 else this.colormod = '1 1 1';
1445 void respawn(entity this)
1447 bool damagedbycontents_prev = this.damagedbycontents;
1450 if(autocvar_g_respawn_ghosts)
1452 this.solid = SOLID_NOT;
1453 this.takedamage = DAMAGE_NO;
1454 this.damagedbycontents = false;
1455 set_movetype(this, MOVETYPE_FLY);
1456 this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1457 this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1458 this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1459 this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha);
1460 Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1);
1461 if(autocvar_g_respawn_ghosts_time > 0)
1462 SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime);
1465 SUB_SetFade (this, time, 1); // fade out the corpse immediately
1469 this.damagedbycontents = damagedbycontents_prev;
1471 this.effects |= EF_NODRAW; // prevent another CopyBody
1472 PutClientInServer(this);
1475 void play_countdown(entity this, float finished, Sound samp)
1478 float time_left = finished - time;
1479 if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
1480 sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
1483 // it removes special powerups not handled by StatusEffects
1484 void player_powerups_remove_all(entity this)
1486 if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1488 // don't play the poweroff sound when the game restarts or the player disconnects
1489 if (time > game_starttime + 1 && IS_CLIENT(this)
1490 && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
1492 sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
1494 if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1495 stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
1496 this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
1500 void player_powerups(entity this)
1502 if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
1503 this.modelflags |= MF_ROCKET;
1505 this.modelflags &= ~MF_ROCKET;
1507 this.effects &= ~EF_NODEPTHTEST;
1510 player_powerups_remove_all(this);
1512 if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
1515 // add a way to see what the items were BEFORE all of these checks for the mutator hook
1516 int items_prev = this.items;
1518 if (!MUTATOR_IS_ENABLED(mutator_instagib))
1520 // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
1521 if (this.items & IT_SUPERWEAPON)
1523 if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
1525 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
1526 this.items = this.items - (this.items & IT_SUPERWEAPON);
1527 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
1528 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1530 else if (this.items & IT_UNLIMITED_SUPERWEAPONS)
1532 // don't let them run out
1536 play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
1537 if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
1539 this.items = this.items - (this.items & IT_SUPERWEAPON);
1540 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1541 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname);
1542 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1546 else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
1548 if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
1550 this.items = this.items | IT_SUPERWEAPON;
1551 if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
1554 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
1555 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1560 if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
1561 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
1562 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1565 else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
1567 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
1571 if(autocvar_g_nodepthtestplayers)
1572 this.effects = this.effects | EF_NODEPTHTEST;
1574 if(autocvar_g_fullbrightplayers)
1575 this.effects = this.effects | EF_FULLBRIGHT;
1577 MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
1580 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1582 if(current > stable)
1584 else if(current > stable - 0.25) // when close enough, "snap"
1587 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1590 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1592 if(current < stable)
1594 else if(current < stable + 0.25) // when close enough, "snap"
1597 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1600 void RotRegen(entity this, Resource res, float limit_mod,
1601 float regenstable, float regenfactor, float regenlinear, float regenframetime,
1602 float rotstable, float rotfactor, float rotlinear, float rotframetime)
1604 float old = GetResource(this, res);
1605 float current = old;
1606 if(current > rotstable)
1608 if(rotframetime > 0)
1610 current = CalcRot(current, rotstable, rotfactor, rotframetime);
1611 current = max(rotstable, current - rotlinear * rotframetime);
1614 else if(current < regenstable)
1616 if(regenframetime > 0)
1618 current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1619 current = min(regenstable, current + regenlinear * regenframetime);
1623 float limit = GetResourceLimit(this, res) * limit_mod;
1628 SetResource(this, res, current);
1631 void player_regen(entity this)
1633 float max_mod, regen_mod, rot_mod, limit_mod;
1634 max_mod = regen_mod = rot_mod = limit_mod = 1;
1636 float regen_health = autocvar_g_balance_health_regen;
1637 float regen_health_linear = autocvar_g_balance_health_regenlinear;
1638 float regen_health_rot = autocvar_g_balance_health_rot;
1639 float regen_health_rotlinear = autocvar_g_balance_health_rotlinear;
1640 float regen_health_stable = autocvar_g_balance_health_regenstable;
1641 float regen_health_rotstable = autocvar_g_balance_health_rotstable;
1642 bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot,
1643 regen_health_rotlinear, regen_health_stable, regen_health_rotstable);
1644 max_mod = M_ARGV(1, float);
1645 regen_mod = M_ARGV(2, float);
1646 rot_mod = M_ARGV(3, float);
1647 limit_mod = M_ARGV(4, float);
1648 regen_health = M_ARGV(5, float);
1649 regen_health_linear = M_ARGV(6, float);
1650 regen_health_rot = M_ARGV(7, float);
1651 regen_health_rotlinear = M_ARGV(8, float);
1652 regen_health_stable = M_ARGV(9, float);
1653 regen_health_rotstable = M_ARGV(10, float);
1655 float rotstable, regenstable, rotframetime, regenframetime;
1657 if(!mutator_returnvalue)
1658 if(!STAT(FROZEN, this))
1660 regenstable = autocvar_g_balance_armor_regenstable;
1661 rotstable = autocvar_g_balance_armor_rotstable;
1662 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1663 rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
1664 RotRegen(this, RES_ARMOR, limit_mod,
1665 regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
1666 rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
1668 // NOTE: max_mod is only applied to health
1669 regenstable = regen_health_stable * max_mod;
1670 rotstable = regen_health_rotstable * max_mod;
1671 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1672 rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
1673 RotRegen(this, RES_HEALTH, limit_mod,
1674 regenstable, regen_health, regen_health_linear, regenframetime,
1675 rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
1678 // if player rotted to death... die!
1679 // check this outside above checks, as player may still be able to rot to death
1680 if(GetResource(this, RES_HEALTH) < 1)
1683 vehicles_exit(this.vehicle, VHEF_RELEASE);
1684 if(this.event_damage)
1685 this.event_damage(this, this, this, 1, DEATH_ROT.m_id, DMG_NOWEP, this.origin, '0 0 0');
1688 if (!(this.items & IT_UNLIMITED_AMMO))
1690 regenstable = autocvar_g_balance_fuel_regenstable;
1691 rotstable = autocvar_g_balance_fuel_rotstable;
1692 regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
1693 rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
1694 RotRegen(this, RES_FUEL, 1,
1695 regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
1696 rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
1701 void SetZoomState(entity this, float newzoom)
1703 if(newzoom != CS(this).zoomstate)
1705 CS(this).zoomstate = newzoom;
1706 ClientData_Touch(this);
1708 zoomstate_set = true;
1711 void GetPressedKeys(entity this)
1713 MUTATOR_CALLHOOK(GetPressedKeys, this);
1716 CS(this).pressedkeys = 0;
1717 STAT(PRESSED_KEYS, this) = 0;
1721 // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
1722 int keys = STAT(PRESSED_KEYS, this);
1723 keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0);
1724 keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0);
1725 keys = BITSET(keys, KEY_RIGHT, CS(this).movement.y > 0);
1726 keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
1728 keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
1729 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
1730 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
1731 keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
1732 CS(this).pressedkeys = keys; // store for other users
1734 STAT(PRESSED_KEYS, this) = keys;
1738 ======================
1739 spectate mode routines
1740 ======================
1743 void SpectateCopy(entity this, entity spectatee)
1745 TC(Client, this); TC(Client, spectatee);
1747 MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
1748 PS(this) = PS(spectatee);
1749 this.armortype = spectatee.armortype;
1750 SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
1751 SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
1752 SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
1753 SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
1754 SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
1755 SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
1756 SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
1757 this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1758 SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
1759 CS(this).impulse = 0;
1760 this.disableclientprediction = 1; // no need to run prediction on a spectator
1761 this.items = spectatee.items;
1762 STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
1763 STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
1764 STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
1765 STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
1766 STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
1767 this.punchangle = spectatee.punchangle;
1768 this.view_ofs = spectatee.view_ofs;
1769 this.velocity = spectatee.velocity;
1770 this.dmg_take = spectatee.dmg_take;
1771 this.dmg_save = spectatee.dmg_save;
1772 this.dmg_inflictor = spectatee.dmg_inflictor;
1773 this.v_angle = spectatee.v_angle;
1774 this.angles = spectatee.v_angle;
1775 STAT(FROZEN, this) = STAT(FROZEN, spectatee);
1776 STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee);
1777 this.viewloc = spectatee.viewloc;
1778 if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
1779 this.fixangle = true;
1780 setorigin(this, spectatee.origin);
1781 setsize(this, spectatee.mins, spectatee.maxs);
1782 SetZoomState(this, CS(spectatee).zoomstate);
1784 anticheat_spectatecopy(this, spectatee);
1785 STAT(HUD, this) = STAT(HUD, spectatee);
1786 if(spectatee.vehicle)
1788 this.angles = spectatee.v_angle;
1790 //this.fixangle = false;
1791 //this.velocity = spectatee.vehicle.velocity;
1792 this.vehicle_health = spectatee.vehicle_health;
1793 this.vehicle_shield = spectatee.vehicle_shield;
1794 this.vehicle_energy = spectatee.vehicle_energy;
1795 this.vehicle_ammo1 = spectatee.vehicle_ammo1;
1796 this.vehicle_ammo2 = spectatee.vehicle_ammo2;
1797 this.vehicle_reload1 = spectatee.vehicle_reload1;
1798 this.vehicle_reload2 = spectatee.vehicle_reload2;
1800 //msg_entity = this;
1802 // WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1803 //WriteAngle(MSG_ONE, spectatee.v_angle.x);
1804 // WriteAngle(MSG_ONE, spectatee.v_angle.y);
1805 // WriteAngle(MSG_ONE, spectatee.v_angle.z);
1807 //WriteByte (MSG_ONE, SVC_SETVIEW);
1808 // WriteEntity(MSG_ONE, this);
1809 //makevectors(spectatee.v_angle);
1810 //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/
1814 bool SpectateUpdate(entity this)
1819 if(!IS_PLAYER(this.enemy) || this == this.enemy)
1821 SetSpectatee(this, NULL);
1825 SpectateCopy(this, this.enemy);
1830 bool SpectateSet(entity this)
1832 if(!IS_PLAYER(this.enemy))
1835 ClientData_Touch(this.enemy);
1838 WriteByte(MSG_ONE, SVC_SETVIEW);
1839 WriteEntity(MSG_ONE, this.enemy);
1840 set_movetype(this, MOVETYPE_NONE);
1841 accuracy_resend(this);
1843 if(!SpectateUpdate(this))
1844 PutObserverInServer(this, false, true);
1849 void SetSpectatee_status(entity this, int spectatee_num)
1851 int oldspectatee_status = CS(this).spectatee_status;
1852 CS(this).spectatee_status = spectatee_num;
1854 if (CS(this).spectatee_status != oldspectatee_status)
1856 if (STAT(PRESSED_KEYS, this))
1858 CS(this).pressedkeys = 0;
1859 STAT(PRESSED_KEYS, this) = 0;
1861 ClientData_Touch(this);
1862 if (g_race || g_cts) race_InitSpectator();
1866 void SetSpectatee(entity this, entity spectatee)
1868 if(IS_BOT_CLIENT(this))
1869 return; // bots abuse .enemy, this code is useless to them
1871 entity old_spectatee = this.enemy;
1873 this.enemy = spectatee;
1876 // these are required to fix the spectator bug with arc
1879 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1881 .entity weaponentity = weaponentities[slot];
1882 if(old_spectatee.(weaponentity).arc_beam)
1883 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1888 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1890 .entity weaponentity = weaponentities[slot];
1891 if(spectatee.(weaponentity).arc_beam)
1892 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1897 SetSpectatee_status(this, etof(spectatee));
1899 // needed to update spectator list
1900 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1903 bool Spectate(entity this, entity pl)
1905 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1907 pl = M_ARGV(1, entity);
1909 SetSpectatee(this, pl);
1910 return SpectateSet(this);
1913 bool SpectateNext(entity this)
1915 entity ent = find(this.enemy, classname, STR_PLAYER);
1917 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1918 ent = M_ARGV(1, entity);
1920 ent = find(ent, classname, STR_PLAYER);
1922 if(ent) { SetSpectatee(this, ent); }
1924 return SpectateSet(this);
1927 bool SpectatePrev(entity this)
1929 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1930 entity ent = findchain(classname, STR_PLAYER);
1931 if (!ent) // no player
1935 // skip players until current spectated player
1937 while(ent && ent != this.enemy)
1940 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1942 case MUT_SPECPREV_FOUND:
1943 ent = M_ARGV(1, entity);
1945 case MUT_SPECPREV_RETURN:
1947 case MUT_SPECPREV_CONTINUE:
1958 SetSpectatee(this, ent);
1959 return SpectateSet(this);
1964 ShowRespawnCountdown()
1966 Update a respawn countdown display.
1969 void ShowRespawnCountdown(entity this)
1972 if(!IS_DEAD(this)) // just respawned?
1976 number = ceil(this.respawn_time - time);
1979 if(number <= this.respawn_countdown)
1981 this.respawn_countdown = number - 1;
1982 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
1983 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
1988 .bool team_selected;
1989 bool ShowTeamSelection(entity this)
1991 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
1993 if (frametime) // once per frame is more than enough
1994 stuffcmd(this, "_scoreboard_team_selection 1\n");
1997 void Join(entity this)
1999 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2002 TRANSMUTE(Player, this);
2004 if(!this.team_selected)
2005 if(autocvar_g_campaign || autocvar_g_balance_teams)
2006 TeamBalance_JoinBestTeam(this);
2008 if(autocvar_g_campaign)
2009 campaign_bots_may_start = true;
2011 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2013 PutClientInServer(this);
2016 if(teamplay && this.team != -1)
2020 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2021 this.team_selected = false;
2024 int GetPlayerLimit()
2027 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)
2028 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2029 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2030 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2031 player_limit = M_ARGV(0, int);
2032 return player_limit < maxclients ? player_limit : 0;
2036 * Determines whether the player is allowed to join. This depends on cvar
2037 * g_maxplayers, if it isn't used this function always return true, otherwise
2038 * it checks whether the number of currently playing players exceeds g_maxplayers.
2039 * @return int number of free slots for players, 0 if none
2041 int nJoinAllowed(entity this, entity ignore)
2044 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2045 // so report 0 free slots if restricted
2047 if(autocvar_g_forced_team_otherwise == "spectate")
2049 if(autocvar_g_forced_team_otherwise == "spectator")
2053 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2054 return 0; // forced spectators can never join
2056 static float msg_time = 0;
2057 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2061 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2062 msg_time = time + 0.5;
2067 // TODO simplify this
2068 int totalClients = 0;
2069 int currentlyPlaying = 0;
2070 FOREACH_CLIENT(true, {
2073 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2077 int player_limit = GetPlayerLimit();
2081 free_slots = maxclients - totalClients;
2082 else if(player_limit > 0 && currentlyPlaying < player_limit)
2083 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2085 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2087 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2088 msg_time = time + 0.5;
2094 bool joinAllowed(entity this)
2096 if (CS(this).version_mismatch) return false;
2097 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2098 if (!nJoinAllowed(this, this)) return false;
2099 if (teamplay && lockteams) return false;
2100 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2101 if (ShowTeamSelection(this)) return false;
2105 void show_entnum(entity this)
2107 // waypoint editor implements a similar feature for waypoints
2108 if (waypointeditor_enabled)
2111 if (wasfreed(this.wp_aimed))
2112 this.wp_aimed = NULL;
2114 WarpZone_crosshair_trace_plusvisibletriggers(this);
2119 if (ent != this.wp_aimed)
2121 string str = sprintf(
2122 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2123 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2124 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2127 if (this.wp_aimed != ent)
2128 this.wp_aimed = ent;
2131 .bool dualwielding_prev;
2132 bool PlayerThink(entity this)
2134 if (game_stopped || intermission_running) {
2135 this.modelflags &= ~MF_ROCKET;
2136 if(intermission_running)
2137 IntermissionThink(this);
2141 if (timeout_status == TIMEOUT_ACTIVE) {
2142 // don't allow the player to turn around while game is paused
2143 // FIXME turn this into CSQC stuff
2144 this.v_angle = this.lastV_angle;
2145 this.angles = this.lastV_angle;
2146 this.fixangle = true;
2149 if (frametime) player_powerups(this);
2151 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2153 if (IS_DEAD(this)) {
2154 if (this.personal && g_race_qualifying) {
2155 if (time > this.respawn_time) {
2156 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2158 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2161 if (frametime) player_anim(this);
2163 if (this.respawn_flags & RESPAWN_DENY)
2165 STAT(RESPAWN_TIME, this) = 0;
2169 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));
2171 switch(this.deadflag)
2175 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2176 this.deadflag = DEAD_RESPAWNING;
2177 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2178 this.deadflag = DEAD_DEAD;
2184 this.deadflag = DEAD_RESPAWNABLE;
2185 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2186 this.deadflag = DEAD_RESPAWNING;
2189 case DEAD_RESPAWNABLE:
2191 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2192 this.deadflag = DEAD_RESPAWNING;
2195 case DEAD_RESPAWNING:
2197 if (time > this.respawn_time)
2199 this.respawn_time = time + 1; // only retry once a second
2200 this.respawn_time_max = this.respawn_time;
2207 ShowRespawnCountdown(this);
2209 if (this.respawn_flags & RESPAWN_SILENT)
2210 STAT(RESPAWN_TIME, this) = 0;
2211 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2213 if (time < this.respawn_time)
2214 STAT(RESPAWN_TIME, this) = this.respawn_time;
2215 else if (this.deadflag != DEAD_RESPAWNING)
2216 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2219 STAT(RESPAWN_TIME, this) = this.respawn_time;
2222 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2223 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2224 STAT(RESPAWN_TIME, this) *= -1;
2229 FixPlayermodel(this);
2231 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2232 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2233 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2236 // reset gun alignment when dual wielding status changes
2237 // to ensure guns are always aligned right and left
2238 bool dualwielding = W_DualWielding(this);
2239 if(this.dualwielding_prev != dualwielding)
2241 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2242 this.dualwielding_prev = dualwielding;
2245 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2248 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2250 .entity weaponentity = weaponentities[slot];
2251 if(WEP_CVAR(vortex, charge_always))
2252 W_Vortex_Charge(this, weaponentity, frametime);
2253 W_WeaponFrame(this, weaponentity);
2259 // WEAPONTODO: Add a weapon request for this
2260 // rot vortex charge to the charge limit
2261 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2263 .entity weaponentity = weaponentities[slot];
2264 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2265 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2270 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2273 monsters_setstatus(this);
2278 .bool would_spectate;
2279 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2280 void ObserverOrSpectatorThink(entity this)
2282 bool is_spec = IS_SPEC(this);
2283 if ( CS(this).impulse )
2285 int r = MinigameImpulse(this, CS(this).impulse);
2287 CS(this).impulse = 0;
2289 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2291 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2292 CS(this).impulse = 0;
2297 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2299 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2301 CS(this).autojoin_checked = true;
2302 TRANSMUTE(Player, this);
2303 PutClientInServer(this);
2305 .entity weaponentity = weaponentities[0];
2306 if(this.(weaponentity).m_weapon == WEP_Null)
2307 W_NextWeapon(this, 0, weaponentity);
2312 if (this.flags & FL_JUMPRELEASED) {
2313 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2314 this.flags &= ~FL_JUMPRELEASED;
2315 this.flags |= FL_SPAWNING;
2316 } 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)))
2317 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2318 this.flags &= ~FL_JUMPRELEASED;
2319 if(SpectateNext(this)) {
2320 TRANSMUTE(Spectator, this);
2321 } else if (is_spec) {
2322 TRANSMUTE(Observer, this);
2323 PutClientInServer(this);
2326 this.would_spectate = false; // unable to spectate anyone
2328 CS(this).impulse = 0;
2329 } else if (is_spec) {
2330 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2331 this.flags &= ~FL_JUMPRELEASED;
2332 if(SpectatePrev(this)) {
2333 TRANSMUTE(Spectator, this);
2335 TRANSMUTE(Observer, this);
2336 PutClientInServer(this);
2338 CS(this).impulse = 0;
2339 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2340 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2341 this.would_spectate = false;
2342 this.flags &= ~FL_JUMPRELEASED;
2343 TRANSMUTE(Observer, this);
2344 PutClientInServer(this);
2346 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2347 PutObserverInServer(this, false, true);
2348 this.would_spectate = true;
2352 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2353 if (PHYS_INPUT_BUTTON_USE(this))
2354 wouldclip = !wouldclip;
2355 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2356 set_movetype(this, preferred_movetype);
2358 } else { // jump pressed
2359 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2360 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2361 this.flags |= FL_JUMPRELEASED;
2362 // primary attack pressed
2363 if(this.flags & FL_SPAWNING)
2365 this.flags &= ~FL_SPAWNING;
2366 if(joinAllowed(this))
2368 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2369 CS(this).autojoin_checked = -1;
2373 if(is_spec && !SpectateUpdate(this))
2374 PutObserverInServer(this, false, true);
2377 this.flags |= FL_CLIENT | FL_NOTARGET;
2380 void PlayerUseKey(entity this)
2382 if (!IS_PLAYER(this))
2389 vehicles_exit(this.vehicle, VHEF_NORMAL);
2393 else if(autocvar_g_vehicles_enter)
2395 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2397 entity head, closest_target = NULL;
2398 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2400 while(head) // find the closest acceptable target to enter
2402 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2403 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2407 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2408 { closest_target = head; }
2410 else { closest_target = head; }
2416 if(closest_target) { vehicles_enter(this, closest_target); return; }
2420 // a use key was pressed; call handlers
2421 MUTATOR_CALLHOOK(PlayerUseKey, this);
2429 Called every frame for each real client by DP (and for each bot by StartFrame()),
2430 and when executing every asynchronous move, so only include things that MUST be done then.
2431 Use PlayerFrame() instead for code that only needs to run once per server frame.
2432 frametime == 0 in the asynchronous code path.
2434 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2437 .float last_vehiclecheck;
2438 void PlayerPreThink (entity this)
2440 WarpZone_PlayerPhysics_FixVAngle(this);
2442 zoomstate_set = false;
2444 MUTATOR_CALLHOOK(PlayerPreThink, this);
2446 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2448 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2450 if (IS_PLAYER(this)) {
2451 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2452 error("Client can't be spawned as player on connection!");
2453 if(!PlayerThink(this))
2456 else if (game_stopped || intermission_running) {
2457 if(intermission_running)
2458 IntermissionThink(this);
2461 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2463 bool early_join_requested = (CS(this).autojoin_checked < 0);
2464 CS(this).autojoin_checked = 1;
2465 // don't do this in ClientConnect
2466 // many things can go wrong if a client is spawned as player on connection
2467 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2468 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2469 && (!teamplay || autocvar_g_balance_teams)))
2471 if(joinAllowed(this))
2476 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2477 ObserverOrSpectatorThink(this);
2480 // WEAPONTODO: Add weapon request for this
2481 if (!zoomstate_set) {
2482 bool wep_zoomed = false;
2483 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2485 .entity weaponentity = weaponentities[slot];
2486 Weapon thiswep = this.(weaponentity).m_weapon;
2487 if(thiswep != WEP_Null && thiswep.wr_zoom)
2488 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2490 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2493 // Voice sound effects
2494 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2496 CS(this).teamkill_soundtime = 0;
2498 entity e = CS(this).teamkill_soundsource;
2499 entity oldpusher = e.pusher;
2501 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2502 e.pusher = oldpusher;
2505 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2506 CS(this).taunt_soundtime = 0;
2507 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2510 target_voicescript_next(this);
2513 void DrownPlayer(entity this)
2515 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2516 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2518 STAT(AIR_FINISHED, this) = 0;
2522 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2524 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2525 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2526 STAT(AIR_FINISHED, this) = 0;
2530 if (!STAT(AIR_FINISHED, this))
2531 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2532 if (STAT(AIR_FINISHED, this) < time)
2534 if (this.pain_finished < time)
2536 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');
2537 this.pain_finished = time + 0.5;
2543 .bool move_qcphysics;
2545 void Player_Physics(entity this)
2547 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2549 if(!this.move_qcphysics)
2552 if(!frametime && !CS(this).pm_frametime)
2555 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2557 CS(this).pm_frametime = 0;
2564 Called every frame for each real client by DP (and for each bot by StartFrame()),
2565 and when executing every asynchronous move, so only include things that MUST be done then.
2566 Use PlayerFrame() instead for code that only needs to run once per server frame.
2567 frametime == 0 in the asynchronous code path.
2570 void PlayerPostThink (entity this)
2572 Player_Physics(this);
2574 if (IS_PLAYER(this)) {
2575 if(this.death_time == time && IS_DEAD(this))
2577 // player's bbox gets resized now, instead of in the damage event that killed the player,
2578 // once all the damage events of this frame have been processed with normal size
2580 setsize(this, this.mins, this.maxs);
2583 UpdateChatBubble(this);
2584 if (CS(this).impulse) ImpulseCommands(this);
2585 GetPressedKeys(this);
2588 CSQCMODEL_AUTOUPDATE(this);
2592 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2594 CS(this).pressedkeys = 0;
2595 STAT(PRESSED_KEYS, this) = 0;
2598 CSQCMODEL_AUTOUPDATE(this);
2605 Called every frame for each client by StartFrame().
2606 Use this for code that only needs to run once per server frame.
2607 frametime is always set here.
2610 void PlayerFrame (entity this)
2612 // formerly PreThink code
2613 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2614 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2616 // physics frames: update anticheat stuff
2617 anticheat_prethink(this);
2619 // Check if spectating is allowed
2620 if (blockSpectators && IS_REAL_CLIENT(this)
2621 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2622 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2624 if (dropclient_schedule(this))
2625 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2628 // Check for nameless players
2629 if (this.netname == "" || this.netname != CS(this).netname_previous)
2631 bool assume_unchanged = (CS(this).netname_previous == "");
2632 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2634 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2635 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2636 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2637 assume_unchanged = false;
2638 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2640 if (isInvisibleString(this.netname))
2642 this.netname = strzone(sprintf("Player#%d", this.playerid));
2643 sprint(this, "Warning: invisible names are not allowed.\n");
2644 assume_unchanged = false;
2645 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2647 if (!assume_unchanged && autocvar_sv_eventlog)
2648 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2649 strcpy(CS(this).netname_previous, this.netname);
2653 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2655 CS(this).version_nagtime = 0;
2656 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2660 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2663 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2667 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2668 if (r < 0) // old client
2669 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2670 else if (r > 0) // old server
2671 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2676 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2678 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2679 this.max_armorvalue = 0;
2683 if (IS_PLAYER(this) && time >= game_starttime)
2685 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2687 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2688 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2690 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2692 if (STAT(REVIVE_PROGRESS, this) >= 1)
2693 Unfreeze(this, false);
2695 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2697 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2698 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2700 if (GetResource(this, RES_HEALTH) < 1)
2703 vehicles_exit(this.vehicle, VHEF_RELEASE);
2704 if(this.event_damage)
2705 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2707 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2708 Unfreeze(this, false);
2713 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2714 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2716 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2720 if(!it.team || SAME_TEAM(this, it))
2721 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2722 else if(autocvar_g_vehicles_steal)
2723 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2725 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2727 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2731 this.last_vehiclecheck = time + 1;
2736 // formerly PostThink code
2737 if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
2738 if (IS_REAL_CLIENT(this))
2739 if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
2740 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2742 int totalClients = 0;
2743 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2745 // maxidle disabled in local matches by not counting clients (totalClients 0)
2746 if (server_is_dedicated)
2748 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2752 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2756 else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2758 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2764 if (totalClients < autocvar_sv_maxidle_minplayers)
2766 // idle kick disabled
2767 CS(this).parm_idlesince = time;
2769 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2771 if (CS(this).idlekick_lasttimeleft)
2773 CS(this).idlekick_lasttimeleft = 0;
2774 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2779 float maxidle_time = autocvar_sv_maxidle;
2780 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2781 maxidle_time = autocvar_sv_maxidle_playertospectator;
2782 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2783 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2784 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2786 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2787 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2789 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2791 if (timeleft <= 0) {
2792 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2794 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2795 PutObserverInServer(this, true, true);
2799 if (dropclient_schedule(this))
2800 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2804 else if (timeleft <= countdown_time) {
2805 if (timeleft != CS(this).idlekick_lasttimeleft)
2806 play2(this, SND(TALK2));
2807 CS(this).idlekick_lasttimeleft = timeleft;
2816 this.solid = SOLID_NOT;
2817 this.takedamage = DAMAGE_NO;
2818 set_movetype(this, MOVETYPE_NONE);
2819 CS(this).teamkill_complain = 0;
2820 CS(this).teamkill_soundtime = 0;
2821 CS(this).teamkill_soundsource = NULL;
2824 if (this.waypointsprite_attachedforcarrier) {
2825 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2826 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2830 // hack to copy the button fields from the client entity to the Client State
2831 void PM_UpdateButtons(entity this, entity store)
2834 store.impulse = this.impulse;
2837 bool typing = this.buttonchat || this.button12;
2839 store.button0 = (typing) ? 0 : this.button0;
2841 store.button2 = (typing) ? 0 : this.button2;
2842 store.button3 = (typing) ? 0 : this.button3;
2843 store.button4 = this.button4;
2844 store.button5 = (typing) ? 0 : this.button5;
2845 store.button6 = this.button6;
2846 store.button7 = this.button7;
2847 store.button8 = this.button8;
2848 store.button9 = this.button9;
2849 store.button10 = this.button10;
2850 store.button11 = this.button11;
2851 store.button12 = this.button12;
2852 store.button13 = this.button13;
2853 store.button14 = this.button14;
2854 store.button15 = this.button15;
2855 store.button16 = this.button16;
2856 store.buttonuse = this.buttonuse;
2857 store.buttonchat = this.buttonchat;
2859 store.cursor_active = this.cursor_active;
2860 store.cursor_screen = this.cursor_screen;
2861 store.cursor_trace_start = this.cursor_trace_start;
2862 store.cursor_trace_endpos = this.cursor_trace_endpos;
2863 store.cursor_trace_ent = this.cursor_trace_ent;
2865 store.ping = this.ping;
2866 store.ping_packetloss = this.ping_packetloss;
2867 store.ping_movementloss = this.ping_movementloss;
2869 store.v_angle = this.v_angle;
2870 store.movement = this.movement;
2873 NET_HANDLE(fpsreport, bool)
2875 int fps = ReadShort();
2876 PlayerScore_Set(sender, SP_FPS, fps);