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 this.scale = ((q3compat && autocvar_sv_q3compat_changehitbox) || !autocvar_sv_mapformat_is_quake3)
647 ? 0.8125 // DP model scaling uses 1/16 accuracy and 13/16 is closest to 56/69
648 : autocvar_sv_player_scale;
650 this.pain_finished = 0;
652 setthink(this, func_null); // players have no think function
655 PS(this).ballistics_density = autocvar_g_ballistics_density_player;
657 this.deadflag = DEAD_NO;
659 this.angles = spot.angles;
660 this.angles_z = 0; // never spawn tilted even if the spot says to
661 if (IS_BOT_CLIENT(this))
663 this.v_angle = this.angles;
666 this.fixangle = true; // turn this way immediately
667 this.oldvelocity = this.velocity = '0 0 0';
668 this.avelocity = '0 0 0';
669 this.punchangle = '0 0 0';
670 this.punchvector = '0 0 0';
672 STAT(REVIVE_PROGRESS, this) = 0;
673 this.revival_time = 0;
675 STAT(AIR_FINISHED, this) = 0;
676 this.waterlevel = WATERLEVEL_NONE;
677 this.watertype = CONTENT_EMPTY;
679 entity spawnevent = new_pure(spawnevent);
680 spawnevent.owner = this;
681 Net_LinkEntity(spawnevent, false, 0.5, SpawnEvent_Send);
683 // Cut off any still running player sounds.
684 stopsound(this, CH_PLAYER_SINGLE);
687 FixPlayermodel(this);
688 this.drawonlytoclient = NULL;
692 for(int slot = 0; slot < MAX_AXH; ++slot)
694 entity axh = this.(AuxiliaryXhair[slot]);
695 this.(AuxiliaryXhair[slot]) = NULL;
697 if(axh.owner == this && axh != NULL && !wasfreed(axh))
701 this.spawnpoint_targ = NULL;
704 this.view_ofs = STAT(PL_VIEW_OFS, this);
705 setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this));
706 this.spawnorigin = spot.origin;
707 setorigin(this, spot.origin + '0 0 1' * (1 - this.mins.z - 24));
708 // don't reset back to last position, even if new position is stuck in solid
709 this.oldorigin = this.origin;
711 IL_REMOVE(g_conveyed, this);
712 this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player
714 IL_REMOVE(g_swamped, this);
715 this.swampslug = NULL;
716 this.swamp_interval = 0;
717 if(this.ladder_entity)
718 IL_REMOVE(g_ladderents, this);
719 this.ladder_entity = NULL;
720 IL_EACH(g_counters, it.realowner == this,
724 STAT(HUD, this) = HUD_NORMAL;
726 this.event_damage = PlayerDamage;
727 this.event_heal = PlayerHeal;
729 this.draggable = func_null;
732 IL_PUSH(g_bot_targets, this);
733 this.bot_attack = true;
734 if(!this.monster_attack)
735 IL_PUSH(g_monster_targets, this);
736 this.monster_attack = true;
737 navigation_dynamicgoal_init(this, false);
739 PHYS_INPUT_BUTTON_ATCK(this) = PHYS_INPUT_BUTTON_JUMP(this) = PHYS_INPUT_BUTTON_ATCK2(this) = false;
741 // player was spectator
742 if (CS(this).killcount == FRAGS_SPECTATOR) {
743 PlayerScore_Clear(this);
744 CS(this).killcount = 0;
745 CS(this).startplaytime = time;
748 for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
750 .entity weaponentity = weaponentities[slot];
751 CL_SpawnWeaponentity(this, weaponentity);
753 this.alpha = default_player_alpha;
754 this.colormod = '1 1 1' * autocvar_g_player_brightness;
755 this.exteriorweaponentity.alpha = default_weapon_alpha;
757 this.speedrunning = false;
759 this.counter_cnt = 0;
760 this.fragsfilter_cnt = 0;
762 target_voicescript_clear(this);
764 // reset fields the weapons may use
765 FOREACH(Weapons, true, {
766 it.wr_resetplayer(it, this);
767 // reload all reloadable weapons
768 if (it.spawnflags & WEP_FLAG_RELOADABLE) {
769 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
771 .entity weaponentity = weaponentities[slot];
772 this.(weaponentity).weapon_load[it.m_id] = it.reloading_ammo;
777 Unfreeze(this, false);
779 MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
781 string s = spot.target;
782 if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
783 spot.target = string_null;
784 SUB_UseTargets(spot, this, NULL);
785 if(g_assault || g_race)
789 if (autocvar_spawn_debug)
791 sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n"));
792 delete(spot); // usefull for checking if there are spawnpoints, that let drop through the floor
795 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
797 .entity weaponentity = weaponentities[slot];
798 entity w_ent = this.(weaponentity);
799 if(slot == 0 || autocvar_g_weaponswitch_debug == 1)
800 w_ent.m_switchweapon = w_getbestweapon(this, weaponentity);
802 w_ent.m_switchweapon = WEP_Null;
803 w_ent.m_weapon = WEP_Null;
804 w_ent.weaponname = "";
805 w_ent.m_switchingweapon = WEP_Null;
809 MUTATOR_CALLHOOK(PlayerWeaponSelect, this);
811 if (CS(this).impulse) ImpulseCommands(this);
813 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
814 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
816 .entity weaponentity = weaponentities[slot];
817 W_WeaponFrame(this, weaponentity);
820 if (!warmup_stage && !this.alivetime)
821 this.alivetime = time;
823 antilag_clear(this, CS(this));
825 if (warmup_stage < 0 || warmup_stage > 1)
829 /** Called when a client spawns in the server */
830 void PutClientInServer(entity this)
832 if (IS_REAL_CLIENT(this)) {
834 WriteByte(MSG_ONE, SVC_SETVIEW);
835 WriteEntity(MSG_ONE, this);
838 TRANSMUTE(Observer, this);
840 bool use_spawnpoint = (!this.enemy); // check this.enemy here since SetSpectatee will clear it
841 SetSpectatee(this, NULL);
845 PS(this).itemkeys = 0;
847 MUTATOR_CALLHOOK(PutClientInServer, this);
849 if (IS_OBSERVER(this)) {
850 PutObserverInServer(this, false, use_spawnpoint);
851 } else if (IS_PLAYER(this)) {
852 PutPlayerInServer(this);
855 bot_relinkplayerlist();
858 // TODO do we need all these fields, or should we stop autodetecting runtime
859 // changes and just have a console command to update this?
860 bool ClientInit_SendEntity(entity this, entity to, int sf)
862 WriteHeader(MSG_ENTITY, _ENT_CLIENT_INIT);
865 // MSG_INIT replacement
866 // TODO: make easier to use
868 W_PROP_reload(MSG_ONE, to);
869 ClientInit_misc(this);
870 MUTATOR_CALLHOOK(Ent_Init);
872 void ClientInit_misc(entity this)
874 int channel = MSG_ONE;
875 WriteHeader(channel, ENT_CLIENT_INIT);
876 WriteByte(channel, g_nexball_meter_period * 32);
877 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[0]));
878 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[1]));
879 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[2]));
880 WriteInt24_t(channel, compressShotOrigin(hook_shotorigin[3]));
881 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[0]));
882 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[1]));
883 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2]));
884 WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3]));
886 if(autocvar_sv_foginterval && world.fog != "")
887 WriteString(channel, world.fog);
889 WriteString(channel, "");
890 WriteByte(channel, this.count * 255.0); // g_balance_armor_blockpercent
891 WriteByte(channel, this.cnt * 255.0); // g_balance_damagepush_speedfactor
892 WriteByte(channel, serverflags);
893 WriteCoord(channel, autocvar_g_trueaim_minrange);
896 void ClientInit_CheckUpdate(entity this)
898 this.nextthink = time;
899 if(this.count != autocvar_g_balance_armor_blockpercent)
901 this.count = autocvar_g_balance_armor_blockpercent;
904 if(this.cnt != autocvar_g_balance_damagepush_speedfactor)
906 this.cnt = autocvar_g_balance_damagepush_speedfactor;
911 void ClientInit_Spawn()
913 entity e = new_pure(clientinit);
914 setthink(e, ClientInit_CheckUpdate);
915 Net_LinkEntity(e, false, 0, ClientInit_SendEntity);
917 ClientInit_CheckUpdate(e);
927 // initialize parms for a new player
928 parm1 = -(86400 * 366);
930 MUTATOR_CALLHOOK(SetNewParms);
938 void SetChangeParms (entity this)
940 // save parms for level change
941 parm1 = CS(this).parm_idlesince - time;
943 MUTATOR_CALLHOOK(SetChangeParms);
951 void DecodeLevelParms(entity this)
954 CS(this).parm_idlesince = parm1;
955 if (CS(this).parm_idlesince == -(86400 * 366))
956 CS(this).parm_idlesince = time;
958 // whatever happens, allow 60 seconds of idling directly after connect for map loading
959 CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - autocvar_sv_maxidle + 60);
961 MUTATOR_CALLHOOK(DecodeLevelParms);
964 void FixClientCvars(entity e)
966 // send prediction settings to the client
967 if(autocvar_g_antilag == 3) // client side hitscan
968 stuffcmd(e, "cl_cmd settemp cl_prydoncursor_notrace 0\n");
969 if(autocvar_sv_gentle)
970 stuffcmd(e, "cl_cmd settemp cl_gentle 1\n");
972 stuffcmd(e, sprintf("\ncl_jumpspeedcap_min \"%s\"\n", autocvar_sv_jumpspeedcap_min));
973 stuffcmd(e, sprintf("\ncl_jumpspeedcap_max \"%s\"\n", autocvar_sv_jumpspeedcap_max));
975 stuffcmd(e, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
977 MUTATOR_CALLHOOK(FixClientCvars, e);
980 bool findinlist_abbrev(string tofind, string list)
982 if(list == "" || tofind == "")
983 return false; // empty list or search, just return
985 // this function allows abbreviated strings!
986 FOREACH_WORD(list, it != "" && it == substring(tofind, 0, strlen(it)),
994 bool PlayerInIPList(entity p, string iplist)
996 // some safety checks (never allow local?)
997 if(p.netaddress == "local" || p.netaddress == "" || !IS_REAL_CLIENT(p))
1000 return findinlist_abbrev(p.netaddress, iplist);
1003 bool PlayerInIDList(entity p, string idlist)
1005 // NOTE: we do NOT check crypto_idfp_signed here, an unsigned ID is fine too for this
1009 return findinlist_abbrev(p.crypto_idfp, idlist);
1012 bool PlayerInList(entity player, string list)
1016 return boolean(PlayerInIDList(player, list) || PlayerInIPList(player, list));
1019 #ifdef DP_EXT_PRECONNECT
1024 Called once (not at each match start) when a client begins a connection to the server
1027 void ClientPreConnect(entity this)
1029 if(autocvar_sv_eventlog)
1031 GameLogEcho(sprintf(":connect:%d:%d:%s",
1034 ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot")
1040 // NOTE csqc uses the active mutators list sent by this function
1041 // to understand which mutators are enabled
1042 // also note that they aren't all registered mutators, e.g. jetpack, low gravity
1043 void SendWelcomeMessage(entity this, int msg_type)
1045 if (boolean(autocvar_g_campaign))
1047 WriteByte(msg_type, 1);
1048 WriteByte(msg_type, Campaign_GetLevelNum());
1053 if (CS(this).version_mismatch)
1055 if (CS(this).version < autocvar_gameversion)
1057 MapInfo_Get_ByName(mi_shortname, 0, NULL);
1058 if (MapInfo_Map_author != "")
1060 WriteByte(msg_type, flags);
1062 WriteString(msg_type, autocvar_hostname);
1063 WriteString(msg_type, autocvar_g_xonoticversion);
1065 WriteString(msg_type, MapInfo_Map_titlestring);
1067 WriteString(msg_type, MapInfo_Map_author);
1068 MapInfo_ClearTemps();
1070 WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
1071 WriteByte(msg_type, GetPlayerLimit());
1073 MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
1074 string modifications = M_ARGV(0, string);
1076 if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
1077 modifications = strcat(modifications, ", No start weapons");
1078 if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
1079 modifications = strcat(modifications, ", Low gravity");
1080 if(g_weapon_stay && !g_cts)
1081 modifications = strcat(modifications, ", Weapons stay");
1082 if(autocvar_g_jetpack)
1083 modifications = strcat(modifications, ", Jetpack");
1084 modifications = substring(modifications, 2, strlen(modifications) - 2);
1086 WriteString(msg_type, modifications);
1088 WriteString(msg_type, g_weaponarena_list);
1090 if(cache_lastmutatormsg != autocvar_g_mutatormsg)
1092 strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
1093 strcpy(cache_mutatormsg, cache_lastmutatormsg);
1096 WriteString(msg_type, cache_mutatormsg);
1098 WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
1105 Called when a client connects to the server
1108 void ClientConnect(entity this)
1110 if (Ban_MaybeEnforceBanOnce(this)) return;
1111 assert(!IS_CLIENT(this), return);
1112 this.flags |= FL_CLIENT;
1113 assert(player_count >= 0, player_count = 0);
1115 TRANSMUTE(Client, this);
1116 CS(this).version_nagtime = time + 10 + random() * 10;
1118 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
1120 bot_clientconnect(this);
1122 Player_DetermineForcedTeam(this);
1124 TRANSMUTE(Observer, this);
1126 PlayerStats_GameReport_AddEvent(sprintf("kills-%d", this.playerid));
1128 // always track bots, don't ask for cl_allow_uidtracking
1129 if (IS_BOT_CLIENT(this))
1130 PlayerStats_GameReport_AddPlayer(this);
1132 CS(this).allowed_timeouts = autocvar_sv_timeout_number;
1134 if (autocvar_sv_eventlog)
1135 GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false)));
1137 CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
1138 this.wants_join = 0;
1140 stuffcmd(this, clientstuff, "\n");
1141 stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1143 FixClientCvars(this);
1145 // get version info from player
1146 stuffcmd(this, "cmd clientversion $gameversion\n");
1148 // notify about available teams
1151 entity balance = TeamBalance_CheckAllowedTeams(this);
1152 int t = TeamBalance_GetAllowedTeams(balance);
1153 TeamBalance_Destroy(balance);
1154 stuffcmd(this, sprintf("set _teams_available %d\n", t));
1158 stuffcmd(this, "set _teams_available 0\n");
1161 bot_relinkplayerlist();
1163 CS(this).spectatortime = time;
1164 if (blockSpectators)
1166 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1169 CS(this).jointime = time;
1171 if (IS_REAL_CLIENT(this))
1173 if (g_weaponarena_weapons == WEPSET(TUBA))
1174 stuffcmd(this, "cl_cmd settemp chase_active 1\n");
1175 // quickmenu file must be put in a subfolder with an unique name
1176 // to reduce chances of overriding custom client quickmenus
1177 if (waypointeditor_enabled)
1178 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
1179 else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
1180 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
1183 if (!autocvar_sv_foginterval && world.fog != "")
1184 stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1186 if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
1187 if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
1188 send_CSQC_teamnagger();
1190 CSQCMODEL_AUTOINIT(this);
1192 CS(this).model_randomizer = random();
1194 if (IS_REAL_CLIENT(this))
1195 sv_notice_join(this);
1197 this.move_qcphysics = true;
1199 // update physics stats (players can spawn before physics runs)
1200 Physics_UpdateStats(this);
1202 IL_EACH(g_initforplayer, it.init_for_player, {
1203 it.init_for_player(it, this);
1206 Handicap_Initialize(this);
1209 if (PlayerInList(this, autocvar_g_playban_list))
1210 TRANSMUTE(Observer, this);
1212 if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
1213 CS(this).muted = true;
1215 MUTATOR_CALLHOOK(ClientConnect, this);
1217 if (player_count == 1)
1219 if (autocvar_sv_autopause && server_is_dedicated)
1221 localcmd("\nsv_hook_firstjoin\n");
1225 .string shootfromfixedorigin;
1226 .entity chatbubbleentity;
1227 void player_powerups_remove_all(entity this);
1233 Called when a client disconnects from the server
1236 void ClientDisconnect(entity this)
1238 assert(IS_CLIENT(this), return);
1240 /* from "ignore" command */
1241 strfree(this.ignore_list);
1242 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it.ignore_list,
1244 if(it.crypto_idfp && it.crypto_idfp != "")
1246 string mylist = ignore_removefromlist(it, this);
1248 strunzone(it.ignore_list);
1250 it.ignore_list = strzone(mylist);
1252 /* from "ignore" command */
1254 PlayerStats_GameReport_FinalizePlayer(this);
1255 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
1256 if (CS(this).active_minigame) part_minigame(this);
1257 if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
1259 if (autocvar_sv_eventlog)
1260 GameLogEcho(strcat(":part:", ftos(this.playerid)));
1262 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
1265 SetSpectatee(this, NULL);
1267 MUTATOR_CALLHOOK(ClientDisconnect, this);
1269 strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
1270 strfree(CS_CVAR(this).weaponorder_byimpulse);
1271 ClientState_detach(this);
1273 Portal_ClearAll(this);
1275 Unfreeze(this, false);
1277 RemoveGrapplingHooks(this);
1279 strfree(this.shootfromfixedorigin);
1281 // Here, everything has been done that requires this player to be a client.
1283 this.flags &= ~FL_CLIENT;
1285 if (this.chatbubbleentity) delete(this.chatbubbleentity);
1286 if (this.killindicator) delete(this.killindicator);
1288 IL_EACH(g_counters, it.realowner == this,
1293 WaypointSprite_PlayerGone(this);
1295 bot_relinkplayerlist();
1297 strfree(this.clientstatus);
1298 if (this.personal) delete(this.personal);
1301 if (warmup_stage || game_starttime > time) ReadyCount();
1302 if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
1304 player_powerups_remove_all(this); // stop powerup sound
1308 if (player_count == 0)
1309 localcmd("\nsv_hook_lastleave\n");
1311 if (!TeamBalance_QueuedPlayersTagIn(this))
1312 if (autocvar_g_balance_teams_remove)
1313 TeamBalance_RemoveExcessPlayers(NULL);
1316 void ChatBubbleThink(entity this)
1318 this.nextthink = time;
1319 if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this)
1321 if(this.owner) // but why can that ever be NULL?
1322 this.owner.chatbubbleentity = NULL;
1329 if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
1331 if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
1332 this.mdl = "models/sprites/minigame_busy.iqm";
1333 else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
1334 this.mdl = "models/misc/chatbubble.spr";
1337 if ( this.model != this.mdl )
1338 _setmodel(this, this.mdl);
1342 void UpdateChatBubble(entity this)
1346 // spawn a chatbubble entity if needed
1347 if (!this.chatbubbleentity)
1349 this.chatbubbleentity = new(chatbubbleentity);
1350 this.chatbubbleentity.owner = this;
1351 this.chatbubbleentity.exteriormodeltoclient = this;
1352 setthink(this.chatbubbleentity, ChatBubbleThink);
1353 this.chatbubbleentity.nextthink = time;
1354 setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below
1355 //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1');
1356 setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1');
1357 setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth
1358 this.chatbubbleentity.mdl = this.chatbubbleentity.model;
1359 //this.chatbubbleentity.model = "";
1360 this.chatbubbleentity.effects = EF_LOWPRECISION;
1364 void calculate_player_respawn_time(entity this)
1366 if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
1369 float gametype_setting_tmp;
1370 float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
1371 float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
1372 float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
1373 float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
1374 float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
1375 float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
1377 float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
1380 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1381 if(it.team == this.team)
1384 if (sdelay_small_count == 0)
1385 sdelay_small_count = 1;
1386 if (sdelay_large_count == 0)
1387 sdelay_large_count = 1;
1391 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1394 if (sdelay_small_count == 0)
1396 if (IS_INDEPENDENT_PLAYER(this))
1398 // Players play independently. No point in requiring enemies.
1399 sdelay_small_count = 1;
1403 // Players play AGAINST each other. Enemies required.
1404 sdelay_small_count = 2;
1407 if (sdelay_large_count == 0)
1409 if (IS_INDEPENDENT_PLAYER(this))
1411 // Players play independently. No point in requiring enemies.
1412 sdelay_large_count = 1;
1416 // Players play AGAINST each other. Enemies required.
1417 sdelay_large_count = 2;
1424 if (pcount <= sdelay_small_count)
1425 sdelay = sdelay_small;
1426 else if (pcount >= sdelay_large_count)
1427 sdelay = sdelay_large;
1428 else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
1429 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
1432 this.respawn_time = ceil((time + sdelay) / waves) * waves;
1434 this.respawn_time = time + sdelay;
1436 if(sdelay < sdelay_max)
1437 this.respawn_time_max = time + sdelay_max;
1439 this.respawn_time_max = this.respawn_time;
1441 if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
1442 this.respawn_countdown = 10; // first number to count down from is 10
1444 this.respawn_countdown = -1; // do not count down
1446 if(autocvar_g_forced_respawn)
1447 this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
1450 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1451 // added to the model skins
1452 /*void UpdateColorModHack()
1455 c = this.clientcolors & 15;
1456 // LordHavoc: only bothering to support white, green, red, yellow, blue
1457 if (!teamplay) this.colormod = '0 0 0';
1458 else if (c == 0) this.colormod = '1.00 1.00 1.00';
1459 else if (c == 3) this.colormod = '0.10 1.73 0.10';
1460 else if (c == 4) this.colormod = '1.73 0.10 0.10';
1461 else if (c == 12) this.colormod = '1.22 1.22 0.10';
1462 else if (c == 13) this.colormod = '0.10 0.10 1.73';
1463 else this.colormod = '1 1 1';
1466 void respawn(entity this)
1468 bool damagedbycontents_prev = this.damagedbycontents;
1471 if(autocvar_g_respawn_ghosts)
1473 this.solid = SOLID_NOT;
1474 this.takedamage = DAMAGE_NO;
1475 this.damagedbycontents = false;
1476 set_movetype(this, MOVETYPE_FLY);
1477 this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1478 this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1479 this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1480 this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha);
1481 Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1);
1482 if(autocvar_g_respawn_ghosts_time > 0)
1483 SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime);
1486 SUB_SetFade (this, time, 1); // fade out the corpse immediately
1490 this.damagedbycontents = damagedbycontents_prev;
1492 this.effects |= EF_NODRAW; // prevent another CopyBody
1493 PutClientInServer(this);
1496 void play_countdown(entity this, float finished, Sound samp)
1499 float time_left = finished - time;
1500 if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
1501 sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
1504 // it removes special powerups not handled by StatusEffects
1505 void player_powerups_remove_all(entity this)
1507 if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1509 // don't play the poweroff sound when the game restarts or the player disconnects
1510 if (time > game_starttime + 1 && IS_CLIENT(this)
1511 && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
1513 sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
1515 if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1516 stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
1517 this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
1521 void player_powerups(entity this)
1523 if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
1524 this.modelflags |= MF_ROCKET;
1526 this.modelflags &= ~MF_ROCKET;
1528 this.effects &= ~EF_NODEPTHTEST;
1531 player_powerups_remove_all(this);
1533 if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
1536 // add a way to see what the items were BEFORE all of these checks for the mutator hook
1537 int items_prev = this.items;
1539 if (!MUTATOR_IS_ENABLED(mutator_instagib))
1541 // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
1542 if (this.items & IT_SUPERWEAPON)
1544 if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
1546 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
1547 this.items = this.items - (this.items & IT_SUPERWEAPON);
1548 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
1549 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1551 else if (this.items & IT_UNLIMITED_SUPERWEAPONS)
1553 // don't let them run out
1557 play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
1558 if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
1560 this.items = this.items - (this.items & IT_SUPERWEAPON);
1561 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1562 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname);
1563 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1567 else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
1569 if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
1571 this.items = this.items | IT_SUPERWEAPON;
1572 if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
1575 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
1576 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1581 if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
1582 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
1583 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1586 else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
1588 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
1592 if(autocvar_g_nodepthtestplayers)
1593 this.effects = this.effects | EF_NODEPTHTEST;
1595 if(autocvar_g_fullbrightplayers)
1596 this.effects = this.effects | EF_FULLBRIGHT;
1598 MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
1601 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1603 if(current > stable)
1605 else if(current > stable - 0.25) // when close enough, "snap"
1608 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1611 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1613 if(current < stable)
1615 else if(current < stable + 0.25) // when close enough, "snap"
1618 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1621 void RotRegen(entity this, Resource res, float limit_mod,
1622 float regenstable, float regenfactor, float regenlinear, float regenframetime,
1623 float rotstable, float rotfactor, float rotlinear, float rotframetime)
1625 float old = GetResource(this, res);
1626 float current = old;
1627 if(current > rotstable)
1629 if(rotframetime > 0)
1631 current = CalcRot(current, rotstable, rotfactor, rotframetime);
1632 current = max(rotstable, current - rotlinear * rotframetime);
1635 else if(current < regenstable)
1637 if(regenframetime > 0)
1639 current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1640 current = min(regenstable, current + regenlinear * regenframetime);
1644 float limit = GetResourceLimit(this, res) * limit_mod;
1649 SetResource(this, res, current);
1652 void player_regen(entity this)
1654 float max_mod, regen_mod, rot_mod, limit_mod;
1655 max_mod = regen_mod = rot_mod = limit_mod = 1;
1657 float regen_health = autocvar_g_balance_health_regen;
1658 float regen_health_linear = autocvar_g_balance_health_regenlinear;
1659 float regen_health_rot = autocvar_g_balance_health_rot;
1660 float regen_health_rotlinear = autocvar_g_balance_health_rotlinear;
1661 float regen_health_stable = autocvar_g_balance_health_regenstable;
1662 float regen_health_rotstable = autocvar_g_balance_health_rotstable;
1663 bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot,
1664 regen_health_rotlinear, regen_health_stable, regen_health_rotstable);
1665 max_mod = M_ARGV(1, float);
1666 regen_mod = M_ARGV(2, float);
1667 rot_mod = M_ARGV(3, float);
1668 limit_mod = M_ARGV(4, float);
1669 regen_health = M_ARGV(5, float);
1670 regen_health_linear = M_ARGV(6, float);
1671 regen_health_rot = M_ARGV(7, float);
1672 regen_health_rotlinear = M_ARGV(8, float);
1673 regen_health_stable = M_ARGV(9, float);
1674 regen_health_rotstable = M_ARGV(10, float);
1676 float rotstable, regenstable, rotframetime, regenframetime;
1678 if(!mutator_returnvalue)
1679 if(!STAT(FROZEN, this))
1681 regenstable = autocvar_g_balance_armor_regenstable;
1682 rotstable = autocvar_g_balance_armor_rotstable;
1683 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1684 rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
1685 RotRegen(this, RES_ARMOR, limit_mod,
1686 regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
1687 rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
1689 // NOTE: max_mod is only applied to health
1690 regenstable = regen_health_stable * max_mod;
1691 rotstable = regen_health_rotstable * max_mod;
1692 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1693 rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
1694 RotRegen(this, RES_HEALTH, limit_mod,
1695 regenstable, regen_health, regen_health_linear, regenframetime,
1696 rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
1699 // if player rotted to death... die!
1700 // check this outside above checks, as player may still be able to rot to death
1701 if(GetResource(this, RES_HEALTH) < 1)
1704 vehicles_exit(this.vehicle, VHEF_RELEASE);
1705 if(this.event_damage)
1706 this.event_damage(this, this, this, 1, DEATH_ROT.m_id, DMG_NOWEP, this.origin, '0 0 0');
1709 if (!(this.items & IT_UNLIMITED_AMMO))
1711 regenstable = autocvar_g_balance_fuel_regenstable;
1712 rotstable = autocvar_g_balance_fuel_rotstable;
1713 regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
1714 rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
1715 RotRegen(this, RES_FUEL, 1,
1716 regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
1717 rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
1722 void SetZoomState(entity this, float newzoom)
1724 if(newzoom != CS(this).zoomstate)
1726 CS(this).zoomstate = newzoom;
1727 ClientData_Touch(this);
1729 zoomstate_set = true;
1732 void GetPressedKeys(entity this)
1734 MUTATOR_CALLHOOK(GetPressedKeys, this);
1737 CS(this).pressedkeys = 0;
1738 STAT(PRESSED_KEYS, this) = 0;
1742 // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
1743 int keys = STAT(PRESSED_KEYS, this);
1744 keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0);
1745 keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0);
1746 keys = BITSET(keys, KEY_RIGHT, CS(this).movement.y > 0);
1747 keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
1749 keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
1750 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
1751 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
1752 keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
1753 CS(this).pressedkeys = keys; // store for other users
1755 STAT(PRESSED_KEYS, this) = keys;
1759 ======================
1760 spectate mode routines
1761 ======================
1764 void SpectateCopy(entity this, entity spectatee)
1766 TC(Client, this); TC(Client, spectatee);
1768 MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
1769 PS(this) = PS(spectatee);
1770 this.armortype = spectatee.armortype;
1771 SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
1772 SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
1773 SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
1774 SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
1775 SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
1776 SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
1777 SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
1778 this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1779 SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
1780 CS(this).impulse = 0;
1781 this.disableclientprediction = 1; // no need to run prediction on a spectator
1782 this.items = spectatee.items;
1783 STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
1784 STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
1785 STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
1786 STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
1787 STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
1788 this.punchangle = spectatee.punchangle;
1789 this.view_ofs = spectatee.view_ofs;
1790 this.velocity = spectatee.velocity;
1791 this.dmg_take = spectatee.dmg_take;
1792 this.dmg_save = spectatee.dmg_save;
1793 this.dmg_inflictor = spectatee.dmg_inflictor;
1794 this.v_angle = spectatee.v_angle;
1795 this.angles = spectatee.v_angle;
1796 STAT(FROZEN, this) = STAT(FROZEN, spectatee);
1797 STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee);
1798 this.viewloc = spectatee.viewloc;
1799 if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
1800 this.fixangle = true;
1801 setorigin(this, spectatee.origin);
1802 setsize(this, spectatee.mins, spectatee.maxs);
1803 SetZoomState(this, CS(spectatee).zoomstate);
1805 anticheat_spectatecopy(this, spectatee);
1806 STAT(HUD, this) = STAT(HUD, spectatee);
1807 if(spectatee.vehicle)
1809 this.angles = spectatee.v_angle;
1811 //this.fixangle = false;
1812 //this.velocity = spectatee.vehicle.velocity;
1813 this.vehicle_health = spectatee.vehicle_health;
1814 this.vehicle_shield = spectatee.vehicle_shield;
1815 this.vehicle_energy = spectatee.vehicle_energy;
1816 this.vehicle_ammo1 = spectatee.vehicle_ammo1;
1817 this.vehicle_ammo2 = spectatee.vehicle_ammo2;
1818 this.vehicle_reload1 = spectatee.vehicle_reload1;
1819 this.vehicle_reload2 = spectatee.vehicle_reload2;
1821 //msg_entity = this;
1823 // WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1824 //WriteAngle(MSG_ONE, spectatee.v_angle.x);
1825 // WriteAngle(MSG_ONE, spectatee.v_angle.y);
1826 // WriteAngle(MSG_ONE, spectatee.v_angle.z);
1828 //WriteByte (MSG_ONE, SVC_SETVIEW);
1829 // WriteEntity(MSG_ONE, this);
1830 //makevectors(spectatee.v_angle);
1831 //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/
1835 bool SpectateUpdate(entity this)
1840 if(!IS_PLAYER(this.enemy) || this == this.enemy)
1842 SetSpectatee(this, NULL);
1846 SpectateCopy(this, this.enemy);
1851 bool SpectateSet(entity this)
1853 if(!IS_PLAYER(this.enemy))
1856 ClientData_Touch(this.enemy);
1859 WriteByte(MSG_ONE, SVC_SETVIEW);
1860 WriteEntity(MSG_ONE, this.enemy);
1861 set_movetype(this, MOVETYPE_NONE);
1862 accuracy_resend(this);
1864 if(!SpectateUpdate(this))
1865 PutObserverInServer(this, false, true);
1870 void SetSpectatee_status(entity this, int spectatee_num)
1872 int oldspectatee_status = CS(this).spectatee_status;
1873 CS(this).spectatee_status = spectatee_num;
1875 if (CS(this).spectatee_status != oldspectatee_status)
1877 if (STAT(PRESSED_KEYS, this))
1879 CS(this).pressedkeys = 0;
1880 STAT(PRESSED_KEYS, this) = 0;
1882 ClientData_Touch(this);
1883 if (g_race || g_cts) race_InitSpectator();
1887 void SetSpectatee(entity this, entity spectatee)
1889 if(IS_BOT_CLIENT(this))
1890 return; // bots abuse .enemy, this code is useless to them
1892 entity old_spectatee = this.enemy;
1894 this.enemy = spectatee;
1897 // these are required to fix the spectator bug with arc
1900 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1902 .entity weaponentity = weaponentities[slot];
1903 if(old_spectatee.(weaponentity).arc_beam)
1904 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1909 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1911 .entity weaponentity = weaponentities[slot];
1912 if(spectatee.(weaponentity).arc_beam)
1913 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1918 SetSpectatee_status(this, etof(spectatee));
1920 // needed to update spectator list
1921 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1924 bool Spectate(entity this, entity pl)
1926 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1928 pl = M_ARGV(1, entity);
1930 SetSpectatee(this, pl);
1931 return SpectateSet(this);
1934 bool SpectateNext(entity this)
1936 entity ent = find(this.enemy, classname, STR_PLAYER);
1938 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1939 ent = M_ARGV(1, entity);
1941 ent = find(ent, classname, STR_PLAYER);
1943 if(ent) { SetSpectatee(this, ent); }
1945 return SpectateSet(this);
1948 bool SpectatePrev(entity this)
1950 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1951 entity ent = findchain(classname, STR_PLAYER);
1952 if (!ent) // no player
1956 // skip players until current spectated player
1958 while(ent && ent != this.enemy)
1961 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1963 case MUT_SPECPREV_FOUND:
1964 ent = M_ARGV(1, entity);
1966 case MUT_SPECPREV_RETURN:
1968 case MUT_SPECPREV_CONTINUE:
1979 SetSpectatee(this, ent);
1980 return SpectateSet(this);
1985 ShowRespawnCountdown()
1987 Update a respawn countdown display.
1990 void ShowRespawnCountdown(entity this)
1993 if(!IS_DEAD(this)) // just respawned?
1997 number = ceil(this.respawn_time - time);
2000 if(number <= this.respawn_countdown)
2002 this.respawn_countdown = number - 1;
2003 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
2004 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
2009 bool ShowTeamSelection(entity this)
2011 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
2013 if (QueuedPlayersReady(this, true))
2015 if (frametime) // once per frame is more than enough
2016 stuffcmd(this, "_scoreboard_team_selection 1\n");
2020 void Join(entity this, bool queued_join)
2022 bool teamautoselect = autocvar_g_campaign || autocvar_g_balance_teams || this.wants_join < 0;
2024 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2027 TRANSMUTE(Player, this);
2031 // First we must put queued player(s) in their team(s) (they chose first).
2032 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this && it.wants_join,
2035 // ensure TeamBalance_JoinBestTeam will run if necessary for `this`
2036 teamautoselect = true;
2040 if(!this.team_selected && teamautoselect)
2041 TeamBalance_JoinBestTeam(this);
2043 if(autocvar_g_campaign)
2044 campaign_bots_may_start = true;
2046 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2048 PutClientInServer(this);
2051 if(teamplay && this.team != -1)
2054 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
2057 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2058 this.team_selected = false;
2059 this.wants_join = 0;
2062 int GetPlayerLimit()
2065 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)
2066 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2067 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2068 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2069 player_limit = M_ARGV(0, int);
2070 return player_limit < maxclients ? player_limit : 0;
2074 * Determines whether the player is allowed to join. This depends on cvar
2075 * g_maxplayers, if it isn't used this function always return true, otherwise
2076 * it checks whether the number of currently playing players exceeds g_maxplayers.
2077 * @return int number of free slots for players, 0 if none
2079 int nJoinAllowed(entity this, entity ignore)
2082 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2083 // so report 0 free slots if restricted
2085 if(autocvar_g_forced_team_otherwise == "spectate")
2087 if(autocvar_g_forced_team_otherwise == "spectator")
2091 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2092 return 0; // forced spectators can never join
2094 static float msg_time = 0;
2095 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2099 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2100 msg_time = time + 0.5;
2105 // TODO simplify this
2106 int totalClients = 0;
2107 int currentlyPlaying = 0;
2108 FOREACH_CLIENT(true, {
2111 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2115 int player_limit = GetPlayerLimit();
2119 free_slots = maxclients - totalClients;
2120 else if(player_limit > 0 && currentlyPlaying < player_limit)
2121 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2123 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2125 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2126 msg_time = time + 0.5;
2132 bool queuePlayer(entity this, int team_index)
2134 if(IS_BOT_CLIENT(this) || !IS_QUEUE_NEEDED(this) || QueuedPlayersReady(this, false))
2139 // defer team selection until Join()
2140 this.wants_join = -1;
2141 this.team_selected = false;
2143 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_WANTS, this.netname);
2144 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_JOIN_PREVENT_QUEUE);
2148 this.wants_join = team_index; // Player queued to join
2149 this.team_selected = true; // no autoselect in Join()
2150 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_WANTS_TEAM), this.netname);
2151 Send_Notification(NOTIF_ONE, this, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_JOIN_PREVENT_QUEUE_TEAM));
2157 bool joinAllowed(entity this)
2159 if (CS(this).version_mismatch) return false;
2160 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2161 if (!nJoinAllowed(this, this)) return false;
2162 if (teamplay && lockteams) return false;
2163 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2164 if (ShowTeamSelection(this)) return false;
2165 if (this.wants_join) return false;
2166 if (queuePlayer(this, 0)) return false;
2170 void show_entnum(entity this)
2172 // waypoint editor implements a similar feature for waypoints
2173 if (waypointeditor_enabled)
2176 if (wasfreed(this.wp_aimed))
2177 this.wp_aimed = NULL;
2179 WarpZone_crosshair_trace_plusvisibletriggers(this);
2184 if (ent != this.wp_aimed)
2186 string str = sprintf(
2187 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2188 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2189 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2192 if (this.wp_aimed != ent)
2193 this.wp_aimed = ent;
2196 .bool dualwielding_prev;
2197 bool PlayerThink(entity this)
2199 if (game_stopped || intermission_running) {
2200 this.modelflags &= ~MF_ROCKET;
2201 if(intermission_running)
2202 IntermissionThink(this);
2206 if (timeout_status == TIMEOUT_ACTIVE) {
2207 // don't allow the player to turn around while game is paused
2208 // FIXME turn this into CSQC stuff
2209 this.v_angle = this.lastV_angle;
2210 this.angles = this.lastV_angle;
2211 this.fixangle = true;
2214 if (frametime) player_powerups(this);
2216 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2218 if (IS_DEAD(this)) {
2219 if (this.personal && g_race_qualifying) {
2220 if (time > this.respawn_time) {
2221 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2223 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2226 if (frametime) player_anim(this);
2228 if (this.respawn_flags & RESPAWN_DENY)
2230 STAT(RESPAWN_TIME, this) = 0;
2234 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));
2236 switch(this.deadflag)
2240 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2241 this.deadflag = DEAD_RESPAWNING;
2242 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2243 this.deadflag = DEAD_DEAD;
2249 this.deadflag = DEAD_RESPAWNABLE;
2250 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2251 this.deadflag = DEAD_RESPAWNING;
2254 case DEAD_RESPAWNABLE:
2256 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2257 this.deadflag = DEAD_RESPAWNING;
2260 case DEAD_RESPAWNING:
2262 if (time > this.respawn_time)
2264 this.respawn_time = time + 1; // only retry once a second
2265 this.respawn_time_max = this.respawn_time;
2272 ShowRespawnCountdown(this);
2274 if (this.respawn_flags & RESPAWN_SILENT)
2275 STAT(RESPAWN_TIME, this) = 0;
2276 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2278 if (time < this.respawn_time)
2279 STAT(RESPAWN_TIME, this) = this.respawn_time;
2280 else if (this.deadflag != DEAD_RESPAWNING)
2281 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2284 STAT(RESPAWN_TIME, this) = this.respawn_time;
2287 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2288 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2289 STAT(RESPAWN_TIME, this) *= -1;
2294 FixPlayermodel(this);
2296 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2297 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2298 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2301 // reset gun alignment when dual wielding status changes
2302 // to ensure guns are always aligned right and left
2303 bool dualwielding = W_DualWielding(this);
2304 if(this.dualwielding_prev != dualwielding)
2306 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2307 this.dualwielding_prev = dualwielding;
2310 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2313 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2315 .entity weaponentity = weaponentities[slot];
2316 if(WEP_CVAR(vortex, charge_always))
2317 W_Vortex_Charge(this, weaponentity, frametime);
2318 W_WeaponFrame(this, weaponentity);
2324 // WEAPONTODO: Add a weapon request for this
2325 // rot vortex charge to the charge limit
2326 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2328 .entity weaponentity = weaponentities[slot];
2329 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2330 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2335 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2338 monsters_setstatus(this);
2343 .bool would_spectate;
2344 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2345 void ObserverOrSpectatorThink(entity this)
2347 bool is_spec = IS_SPEC(this);
2348 if ( CS(this).impulse )
2350 int r = MinigameImpulse(this, CS(this).impulse);
2352 CS(this).impulse = 0;
2354 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2356 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2357 CS(this).impulse = 0;
2362 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2364 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2366 CS(this).autojoin_checked = true;
2367 TRANSMUTE(Player, this);
2368 PutClientInServer(this);
2370 .entity weaponentity = weaponentities[0];
2371 if(this.(weaponentity).m_weapon == WEP_Null)
2372 W_NextWeapon(this, 0, weaponentity);
2377 if (this.flags & FL_JUMPRELEASED) {
2378 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2379 this.flags &= ~FL_JUMPRELEASED;
2380 this.flags |= FL_SPAWNING;
2381 } 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)))
2382 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2383 this.flags &= ~FL_JUMPRELEASED;
2384 if(SpectateNext(this)) {
2385 TRANSMUTE(Spectator, this);
2386 } else if (is_spec) {
2387 TRANSMUTE(Observer, this);
2388 PutClientInServer(this);
2391 this.would_spectate = false; // unable to spectate anyone
2393 CS(this).impulse = 0;
2394 } else if (is_spec) {
2395 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2396 this.flags &= ~FL_JUMPRELEASED;
2397 if(SpectatePrev(this)) {
2398 TRANSMUTE(Spectator, this);
2400 TRANSMUTE(Observer, this);
2401 PutClientInServer(this);
2403 CS(this).impulse = 0;
2404 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2405 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2406 this.would_spectate = false;
2407 this.flags &= ~FL_JUMPRELEASED;
2408 TRANSMUTE(Observer, this);
2409 PutClientInServer(this);
2411 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2412 PutObserverInServer(this, false, true);
2413 this.would_spectate = true;
2417 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2418 if (PHYS_INPUT_BUTTON_USE(this))
2419 wouldclip = !wouldclip;
2420 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2421 set_movetype(this, preferred_movetype);
2423 } else { // jump pressed
2424 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2425 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2426 this.flags |= FL_JUMPRELEASED;
2427 // primary attack pressed
2428 if(this.flags & FL_SPAWNING)
2430 this.flags &= ~FL_SPAWNING;
2431 if(joinAllowed(this))
2433 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2434 CS(this).autojoin_checked = -1;
2438 if(is_spec && !SpectateUpdate(this))
2439 PutObserverInServer(this, false, true);
2442 this.flags |= FL_CLIENT | FL_NOTARGET;
2445 void PlayerUseKey(entity this)
2447 if (!IS_PLAYER(this))
2454 vehicles_exit(this.vehicle, VHEF_NORMAL);
2458 else if(autocvar_g_vehicles_enter)
2460 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2462 entity head, closest_target = NULL;
2463 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2465 while(head) // find the closest acceptable target to enter
2467 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2468 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2472 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2473 { closest_target = head; }
2475 else { closest_target = head; }
2481 if(closest_target) { vehicles_enter(this, closest_target); return; }
2485 // a use key was pressed; call handlers
2486 MUTATOR_CALLHOOK(PlayerUseKey, this);
2494 Called every frame for each real client by DP (and for each bot by StartFrame()),
2495 and when executing every asynchronous move, so only include things that MUST be done then.
2496 Use PlayerFrame() instead for code that only needs to run once per server frame.
2497 frametime == 0 in the asynchronous code path.
2499 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2502 .float last_vehiclecheck;
2503 void PlayerPreThink (entity this)
2505 WarpZone_PlayerPhysics_FixVAngle(this);
2507 zoomstate_set = false;
2509 MUTATOR_CALLHOOK(PlayerPreThink, this);
2511 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2513 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2515 if (IS_PLAYER(this)) {
2516 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2517 error("Client can't be spawned as player on connection!");
2518 if(!PlayerThink(this))
2521 else if (game_stopped || intermission_running) {
2522 if(intermission_running)
2523 IntermissionThink(this);
2526 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2528 bool early_join_requested = (CS(this).autojoin_checked < 0);
2529 CS(this).autojoin_checked = 1;
2530 // don't do this in ClientConnect
2531 // many things can go wrong if a client is spawned as player on connection
2532 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2533 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2534 && (!teamplay || autocvar_g_balance_teams)))
2536 if(joinAllowed(this))
2541 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2542 ObserverOrSpectatorThink(this);
2545 // WEAPONTODO: Add weapon request for this
2546 if (!zoomstate_set) {
2547 bool wep_zoomed = false;
2548 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2550 .entity weaponentity = weaponentities[slot];
2551 Weapon thiswep = this.(weaponentity).m_weapon;
2552 if(thiswep != WEP_Null && thiswep.wr_zoom)
2553 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2555 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2558 // Voice sound effects
2559 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2561 CS(this).teamkill_soundtime = 0;
2563 entity e = CS(this).teamkill_soundsource;
2564 entity oldpusher = e.pusher;
2566 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2567 e.pusher = oldpusher;
2570 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2571 CS(this).taunt_soundtime = 0;
2572 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2575 target_voicescript_next(this);
2578 void DrownPlayer(entity this)
2580 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2581 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2583 STAT(AIR_FINISHED, this) = 0;
2587 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2589 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2590 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2591 STAT(AIR_FINISHED, this) = 0;
2595 if (!STAT(AIR_FINISHED, this))
2596 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2597 if (STAT(AIR_FINISHED, this) < time)
2599 if (this.pain_finished < time)
2601 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');
2602 this.pain_finished = time + 0.5;
2608 .bool move_qcphysics;
2610 void Player_Physics(entity this)
2612 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2614 if(!this.move_qcphysics)
2617 if(!frametime && !CS(this).pm_frametime)
2620 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2622 CS(this).pm_frametime = 0;
2629 Called every frame for each real client by DP (and for each bot by StartFrame()),
2630 and when executing every asynchronous move, so only include things that MUST be done then.
2631 Use PlayerFrame() instead for code that only needs to run once per server frame.
2632 frametime == 0 in the asynchronous code path.
2635 void PlayerPostThink (entity this)
2637 Player_Physics(this);
2639 if (IS_PLAYER(this)) {
2640 if(this.death_time == time && IS_DEAD(this))
2642 // player's bbox gets resized now, instead of in the damage event that killed the player,
2643 // once all the damage events of this frame have been processed with normal size
2645 setsize(this, this.mins, this.maxs);
2648 UpdateChatBubble(this);
2649 if (CS(this).impulse) ImpulseCommands(this);
2650 GetPressedKeys(this);
2653 CSQCMODEL_AUTOUPDATE(this);
2657 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2659 CS(this).pressedkeys = 0;
2660 STAT(PRESSED_KEYS, this) = 0;
2663 CSQCMODEL_AUTOUPDATE(this);
2670 Called every frame for each client by StartFrame().
2671 Use this for code that only needs to run once per server frame.
2672 frametime is always set here.
2675 void PlayerFrame (entity this)
2677 // formerly PreThink code
2678 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2679 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2681 // physics frames: update anticheat stuff
2682 anticheat_prethink(this);
2684 // Check if spectating is allowed
2685 if (blockSpectators && IS_REAL_CLIENT(this)
2686 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2687 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2689 if (dropclient_schedule(this))
2690 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2693 // Check for nameless players
2694 if (this.netname == "" || this.netname != CS(this).netname_previous)
2696 bool assume_unchanged = (CS(this).netname_previous == "");
2697 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2699 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2700 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2701 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2702 assume_unchanged = false;
2703 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2705 if (isInvisibleString(this.netname))
2707 this.netname = strzone(sprintf("Player#%d", this.playerid));
2708 sprint(this, "Warning: invisible names are not allowed.\n");
2709 assume_unchanged = false;
2710 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2712 if (!assume_unchanged && autocvar_sv_eventlog)
2713 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2714 strcpy(CS(this).netname_previous, this.netname);
2718 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2720 CS(this).version_nagtime = 0;
2721 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2725 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2728 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2732 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2733 if (r < 0) // old client
2734 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2735 else if (r > 0) // old server
2736 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2741 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2743 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2744 this.max_armorvalue = 0;
2748 if (IS_PLAYER(this) && time >= game_starttime)
2750 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2752 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2753 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2755 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2757 if (STAT(REVIVE_PROGRESS, this) >= 1)
2758 Unfreeze(this, false);
2760 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2762 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2763 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2765 if (GetResource(this, RES_HEALTH) < 1)
2768 vehicles_exit(this.vehicle, VHEF_RELEASE);
2769 if(this.event_damage)
2770 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2772 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2773 Unfreeze(this, false);
2778 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2779 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2781 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2785 if(!it.team || SAME_TEAM(this, it))
2786 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2787 else if(autocvar_g_vehicles_steal)
2788 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2790 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2792 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2796 this.last_vehiclecheck = time + 1;
2801 // formerly PostThink code
2802 if (autocvar_sv_maxidle > 0 || ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0))
2803 if (IS_REAL_CLIENT(this))
2804 if (IS_PLAYER(this) || this.wants_join || autocvar_sv_maxidle_alsokickspectators)
2805 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2807 int totalClients = 0;
2808 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2810 // maxidle disabled in local matches by not counting clients (totalClients 0)
2811 if (server_is_dedicated)
2813 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2817 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2821 else if ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0)
2823 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2829 if (totalClients < autocvar_sv_maxidle_minplayers)
2831 // idle kick disabled
2832 CS(this).parm_idlesince = time;
2834 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2836 if (CS(this).idlekick_lasttimeleft)
2838 CS(this).idlekick_lasttimeleft = 0;
2839 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2844 float maxidle_time = autocvar_sv_maxidle;
2845 if ((IS_PLAYER(this) || this.wants_join)
2846 && autocvar_sv_maxidle_playertospectator > 0)
2847 maxidle_time = autocvar_sv_maxidle_playertospectator;
2848 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2849 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2850 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2852 if ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0)
2854 if (!this.wants_join) // no countdown centreprint when getting kicked off the join queue
2855 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2858 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2862 if ((IS_PLAYER(this) || this.wants_join)
2863 && autocvar_sv_maxidle_playertospectator > 0)
2865 if (this.wants_join)
2866 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING_QUEUE, this.netname, maxidle_time);
2868 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2869 PutObserverInServer(this, true, true);
2870 // when the player is kicked off the server, these are called in ClientDisconnect()
2871 if (!TeamBalance_QueuedPlayersTagIn(this))
2872 if (autocvar_g_balance_teams_remove)
2873 TeamBalance_RemoveExcessPlayers(this);
2877 if (dropclient_schedule(this))
2878 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2882 else if (timeleft <= countdown_time
2883 && !this.wants_join) // no countdown bangs when getting kicked off the join queue
2885 if (timeleft != CS(this).idlekick_lasttimeleft)
2886 play2(this, SND(TALK2));
2887 CS(this).idlekick_lasttimeleft = timeleft;
2896 this.solid = SOLID_NOT;
2897 this.takedamage = DAMAGE_NO;
2898 set_movetype(this, MOVETYPE_NONE);
2899 CS(this).teamkill_complain = 0;
2900 CS(this).teamkill_soundtime = 0;
2901 CS(this).teamkill_soundsource = NULL;
2904 if (this.waypointsprite_attachedforcarrier) {
2905 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2906 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2910 // hack to copy the button fields from the client entity to the Client State
2911 void PM_UpdateButtons(entity this, entity store)
2914 store.impulse = this.impulse;
2917 bool typing = this.buttonchat || this.button12;
2919 store.button0 = (typing) ? 0 : this.button0;
2921 store.button2 = (typing) ? 0 : this.button2;
2922 store.button3 = (typing) ? 0 : this.button3;
2923 store.button4 = this.button4;
2924 store.button5 = (typing) ? 0 : this.button5;
2925 store.button6 = this.button6;
2926 store.button7 = this.button7;
2927 store.button8 = this.button8;
2928 store.button9 = this.button9;
2929 store.button10 = this.button10;
2930 store.button11 = this.button11;
2931 store.button12 = this.button12;
2932 store.button13 = this.button13;
2933 store.button14 = this.button14;
2934 store.button15 = this.button15;
2935 store.button16 = this.button16;
2936 store.buttonuse = this.buttonuse;
2937 store.buttonchat = this.buttonchat;
2939 store.cursor_active = this.cursor_active;
2940 store.cursor_screen = this.cursor_screen;
2941 store.cursor_trace_start = this.cursor_trace_start;
2942 store.cursor_trace_endpos = this.cursor_trace_endpos;
2943 store.cursor_trace_ent = this.cursor_trace_ent;
2945 store.ping = this.ping;
2946 store.ping_packetloss = this.ping_packetloss;
2947 store.ping_movementloss = this.ping_movementloss;
2949 store.v_angle = this.v_angle;
2950 store.movement = this.movement;
2953 NET_HANDLE(fpsreport, bool)
2955 int fps = ReadShort();
2956 PlayerScore_Set(sender, SP_FPS, fps);