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;
1883 ClientData_Touch(this);
1885 // init or clear race data
1886 if ((g_race || g_cts) && g_race_qualifying && IS_REAL_CLIENT(this))
1890 if (this.enemy && this.enemy.race_laptime)
1893 race_SendNextCheckpoint(this.enemy, 1);
1897 // send reset to this spectator
1898 WriteHeader(MSG_ONE, TE_CSQC_RACE);
1899 WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_CLEAR);
1905 void SetSpectatee(entity this, entity spectatee)
1907 if(IS_BOT_CLIENT(this))
1908 return; // bots abuse .enemy, this code is useless to them
1910 entity old_spectatee = this.enemy;
1912 this.enemy = spectatee;
1915 // these are required to fix the spectator bug with arc
1918 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1920 .entity weaponentity = weaponentities[slot];
1921 if(old_spectatee.(weaponentity).arc_beam)
1922 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1927 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1929 .entity weaponentity = weaponentities[slot];
1930 if(spectatee.(weaponentity).arc_beam)
1931 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1936 SetSpectatee_status(this, etof(spectatee));
1938 // needed to update spectator list
1939 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1942 bool Spectate(entity this, entity pl)
1944 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1946 pl = M_ARGV(1, entity);
1948 SetSpectatee(this, pl);
1949 return SpectateSet(this);
1952 bool SpectateNext(entity this)
1954 entity ent = find(this.enemy, classname, STR_PLAYER);
1956 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1957 ent = M_ARGV(1, entity);
1959 ent = find(ent, classname, STR_PLAYER);
1961 if(ent) { SetSpectatee(this, ent); }
1963 return SpectateSet(this);
1966 bool SpectatePrev(entity this)
1968 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1969 entity ent = findchain(classname, STR_PLAYER);
1970 if (!ent) // no player
1974 // skip players until current spectated player
1976 while(ent && ent != this.enemy)
1979 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1981 case MUT_SPECPREV_FOUND:
1982 ent = M_ARGV(1, entity);
1984 case MUT_SPECPREV_RETURN:
1986 case MUT_SPECPREV_CONTINUE:
1997 SetSpectatee(this, ent);
1998 return SpectateSet(this);
2003 ShowRespawnCountdown()
2005 Update a respawn countdown display.
2008 void ShowRespawnCountdown(entity this)
2011 if(!IS_DEAD(this)) // just respawned?
2015 number = ceil(this.respawn_time - time);
2018 if(number <= this.respawn_countdown)
2020 this.respawn_countdown = number - 1;
2021 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
2022 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
2027 bool ShowTeamSelection(entity this)
2029 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
2031 if (QueuedPlayersReady(this, true))
2033 if (frametime) // once per frame is more than enough
2034 stuffcmd(this, "_scoreboard_team_selection 1\n");
2038 void Join(entity this, bool queued_join)
2040 bool teamautoselect = autocvar_g_campaign || autocvar_g_balance_teams || this.wants_join < 0;
2042 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2045 TRANSMUTE(Player, this);
2049 // First we must put queued player(s) in their team(s) (they chose first).
2050 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != this && it.wants_join,
2053 // ensure TeamBalance_JoinBestTeam will run if necessary for `this`
2054 teamautoselect = true;
2058 if(!this.team_selected && teamautoselect)
2059 TeamBalance_JoinBestTeam(this);
2061 if(autocvar_g_campaign)
2062 campaign_bots_may_start = true;
2064 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2066 PutClientInServer(this);
2069 if(teamplay && this.team != -1)
2072 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
2075 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2076 this.team_selected = false;
2077 this.wants_join = 0;
2080 int GetPlayerLimit()
2083 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)
2084 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2085 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2086 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2087 player_limit = M_ARGV(0, int);
2088 return player_limit < maxclients ? player_limit : 0;
2092 * Determines whether the player is allowed to join. This depends on cvar
2093 * g_maxplayers, if it isn't used this function always return true, otherwise
2094 * it checks whether the number of currently playing players exceeds g_maxplayers.
2095 * @return int number of free slots for players, 0 if none
2097 int nJoinAllowed(entity this, entity ignore)
2100 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2101 // so report 0 free slots if restricted
2103 if(autocvar_g_forced_team_otherwise == "spectate")
2105 if(autocvar_g_forced_team_otherwise == "spectator")
2109 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2110 return 0; // forced spectators can never join
2112 static float msg_time = 0;
2113 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2117 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2118 msg_time = time + 0.5;
2123 // TODO simplify this
2124 int totalClients = 0;
2125 int currentlyPlaying = 0;
2126 FOREACH_CLIENT(true, {
2129 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2133 int player_limit = GetPlayerLimit();
2137 free_slots = maxclients - totalClients;
2138 else if(player_limit > 0 && currentlyPlaying < player_limit)
2139 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2141 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2143 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2144 msg_time = time + 0.5;
2150 bool queuePlayer(entity this, int team_index)
2152 if(IS_BOT_CLIENT(this) || !IS_QUEUE_NEEDED(this) || QueuedPlayersReady(this, false))
2157 // defer team selection until Join()
2158 this.wants_join = -1;
2159 this.team_selected = false;
2161 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_WANTS, this.netname);
2162 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_JOIN_PREVENT_QUEUE);
2166 this.wants_join = team_index; // Player queued to join
2167 this.team_selected = true; // no autoselect in Join()
2168 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_WANTS_TEAM), this.netname);
2169 Send_Notification(NOTIF_ONE, this, MSG_CENTER, APP_TEAM_NUM(this.team, CENTER_JOIN_PREVENT_QUEUE_TEAM));
2175 bool joinAllowed(entity this)
2177 if (CS(this).version_mismatch) return false;
2178 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2179 if (!nJoinAllowed(this, this)) return false;
2180 if (teamplay && lockteams) return false;
2181 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2182 if (ShowTeamSelection(this)) return false;
2183 if (this.wants_join) return false;
2184 if (queuePlayer(this, 0)) return false;
2188 void show_entnum(entity this)
2190 // waypoint editor implements a similar feature for waypoints
2191 if (waypointeditor_enabled)
2194 if (wasfreed(this.wp_aimed))
2195 this.wp_aimed = NULL;
2197 WarpZone_crosshair_trace_plusvisibletriggers(this);
2202 if (ent != this.wp_aimed)
2204 string str = sprintf(
2205 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2206 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2207 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2210 if (this.wp_aimed != ent)
2211 this.wp_aimed = ent;
2214 .bool dualwielding_prev;
2215 bool PlayerThink(entity this)
2217 if (game_stopped || intermission_running) {
2218 this.modelflags &= ~MF_ROCKET;
2219 if(intermission_running)
2220 IntermissionThink(this);
2224 if (timeout_status == TIMEOUT_ACTIVE) {
2225 // don't allow the player to turn around while game is paused
2226 // FIXME turn this into CSQC stuff
2227 this.v_angle = this.lastV_angle;
2228 this.angles = this.lastV_angle;
2229 this.fixangle = true;
2232 if (frametime) player_powerups(this);
2234 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2236 if (IS_DEAD(this)) {
2237 if (this.personal && g_race_qualifying) {
2238 if (time > this.respawn_time) {
2239 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2241 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2244 if (frametime) player_anim(this);
2246 if (this.respawn_flags & RESPAWN_DENY)
2248 STAT(RESPAWN_TIME, this) = 0;
2252 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));
2254 switch(this.deadflag)
2258 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2259 this.deadflag = DEAD_RESPAWNING;
2260 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2261 this.deadflag = DEAD_DEAD;
2267 this.deadflag = DEAD_RESPAWNABLE;
2268 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2269 this.deadflag = DEAD_RESPAWNING;
2272 case DEAD_RESPAWNABLE:
2274 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2275 this.deadflag = DEAD_RESPAWNING;
2278 case DEAD_RESPAWNING:
2280 if (time > this.respawn_time)
2282 this.respawn_time = time + 1; // only retry once a second
2283 this.respawn_time_max = this.respawn_time;
2290 ShowRespawnCountdown(this);
2292 if (this.respawn_flags & RESPAWN_SILENT)
2293 STAT(RESPAWN_TIME, this) = 0;
2294 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2296 if (time < this.respawn_time)
2297 STAT(RESPAWN_TIME, this) = this.respawn_time;
2298 else if (this.deadflag != DEAD_RESPAWNING)
2299 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2302 STAT(RESPAWN_TIME, this) = this.respawn_time;
2305 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2306 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2307 STAT(RESPAWN_TIME, this) *= -1;
2312 FixPlayermodel(this);
2314 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2315 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2316 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2319 // reset gun alignment when dual wielding status changes
2320 // to ensure guns are always aligned right and left
2321 bool dualwielding = W_DualWielding(this);
2322 if(this.dualwielding_prev != dualwielding)
2324 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2325 this.dualwielding_prev = dualwielding;
2328 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2331 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2333 .entity weaponentity = weaponentities[slot];
2334 if(WEP_CVAR(vortex, charge_always))
2335 W_Vortex_Charge(this, weaponentity, frametime);
2336 W_WeaponFrame(this, weaponentity);
2342 // WEAPONTODO: Add a weapon request for this
2343 // rot vortex charge to the charge limit
2344 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2346 .entity weaponentity = weaponentities[slot];
2347 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2348 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2353 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2356 monsters_setstatus(this);
2361 .bool would_spectate;
2362 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2363 void ObserverOrSpectatorThink(entity this)
2365 bool is_spec = IS_SPEC(this);
2366 if ( CS(this).impulse )
2368 int r = MinigameImpulse(this, CS(this).impulse);
2370 CS(this).impulse = 0;
2372 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2374 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2375 CS(this).impulse = 0;
2380 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2382 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2384 CS(this).autojoin_checked = true;
2385 TRANSMUTE(Player, this);
2386 PutClientInServer(this);
2388 .entity weaponentity = weaponentities[0];
2389 if(this.(weaponentity).m_weapon == WEP_Null)
2390 W_NextWeapon(this, 0, weaponentity);
2395 if (this.flags & FL_JUMPRELEASED) {
2396 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2397 this.flags &= ~FL_JUMPRELEASED;
2398 this.flags |= FL_SPAWNING;
2399 } 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)))
2400 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2401 this.flags &= ~FL_JUMPRELEASED;
2402 if(SpectateNext(this)) {
2403 TRANSMUTE(Spectator, this);
2404 } else if (is_spec) {
2405 TRANSMUTE(Observer, this);
2406 PutClientInServer(this);
2409 this.would_spectate = false; // unable to spectate anyone
2411 CS(this).impulse = 0;
2412 } else if (is_spec) {
2413 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2414 this.flags &= ~FL_JUMPRELEASED;
2415 if(SpectatePrev(this)) {
2416 TRANSMUTE(Spectator, this);
2418 TRANSMUTE(Observer, this);
2419 PutClientInServer(this);
2421 CS(this).impulse = 0;
2422 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2423 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2424 this.would_spectate = false;
2425 this.flags &= ~FL_JUMPRELEASED;
2426 TRANSMUTE(Observer, this);
2427 PutClientInServer(this);
2429 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2430 PutObserverInServer(this, false, true);
2431 this.would_spectate = true;
2435 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2436 if (PHYS_INPUT_BUTTON_USE(this))
2437 wouldclip = !wouldclip;
2438 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2439 set_movetype(this, preferred_movetype);
2441 } else { // jump pressed
2442 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2443 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2444 this.flags |= FL_JUMPRELEASED;
2445 // primary attack pressed
2446 if(this.flags & FL_SPAWNING)
2448 this.flags &= ~FL_SPAWNING;
2449 if(joinAllowed(this))
2451 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2452 CS(this).autojoin_checked = -1;
2456 if(is_spec && !SpectateUpdate(this))
2457 PutObserverInServer(this, false, true);
2460 this.flags |= FL_CLIENT | FL_NOTARGET;
2463 void PlayerUseKey(entity this)
2465 if (!IS_PLAYER(this))
2472 vehicles_exit(this.vehicle, VHEF_NORMAL);
2476 else if(autocvar_g_vehicles_enter)
2478 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2480 entity head, closest_target = NULL;
2481 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2483 while(head) // find the closest acceptable target to enter
2485 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2486 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2490 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2491 { closest_target = head; }
2493 else { closest_target = head; }
2499 if(closest_target) { vehicles_enter(this, closest_target); return; }
2503 // a use key was pressed; call handlers
2504 MUTATOR_CALLHOOK(PlayerUseKey, this);
2512 Called every frame for each real client by DP (and for each bot by StartFrame()),
2513 and when executing every asynchronous move, so only include things that MUST be done then.
2514 Use PlayerFrame() instead for code that only needs to run once per server frame.
2515 frametime == 0 in the asynchronous code path.
2517 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2520 .float last_vehiclecheck;
2521 void PlayerPreThink (entity this)
2523 WarpZone_PlayerPhysics_FixVAngle(this);
2525 zoomstate_set = false;
2527 MUTATOR_CALLHOOK(PlayerPreThink, this);
2529 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2531 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2533 if (IS_PLAYER(this)) {
2534 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2535 error("Client can't be spawned as player on connection!");
2536 if(!PlayerThink(this))
2539 else if (game_stopped || intermission_running) {
2540 if(intermission_running)
2541 IntermissionThink(this);
2544 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2546 bool early_join_requested = (CS(this).autojoin_checked < 0);
2547 CS(this).autojoin_checked = 1;
2548 // don't do this in ClientConnect
2549 // many things can go wrong if a client is spawned as player on connection
2550 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2551 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2552 && (!teamplay || autocvar_g_balance_teams)))
2554 if(joinAllowed(this))
2559 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2560 ObserverOrSpectatorThink(this);
2563 // WEAPONTODO: Add weapon request for this
2564 if (!zoomstate_set) {
2565 bool wep_zoomed = false;
2566 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2568 .entity weaponentity = weaponentities[slot];
2569 Weapon thiswep = this.(weaponentity).m_weapon;
2570 if(thiswep != WEP_Null && thiswep.wr_zoom)
2571 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2573 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2576 // Voice sound effects
2577 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2579 CS(this).teamkill_soundtime = 0;
2581 entity e = CS(this).teamkill_soundsource;
2582 entity oldpusher = e.pusher;
2584 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2585 e.pusher = oldpusher;
2588 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2589 CS(this).taunt_soundtime = 0;
2590 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2593 target_voicescript_next(this);
2596 void DrownPlayer(entity this)
2598 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2599 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2601 STAT(AIR_FINISHED, this) = 0;
2605 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2607 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2608 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2609 STAT(AIR_FINISHED, this) = 0;
2613 if (!STAT(AIR_FINISHED, this))
2614 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2615 if (STAT(AIR_FINISHED, this) < time)
2617 if (this.pain_finished < time)
2619 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');
2620 this.pain_finished = time + 0.5;
2626 .bool move_qcphysics;
2628 void Player_Physics(entity this)
2630 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2632 if(!this.move_qcphysics)
2635 if(!frametime && !CS(this).pm_frametime)
2638 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2640 CS(this).pm_frametime = 0;
2647 Called every frame for each real client by DP (and for each bot by StartFrame()),
2648 and when executing every asynchronous move, so only include things that MUST be done then.
2649 Use PlayerFrame() instead for code that only needs to run once per server frame.
2650 frametime == 0 in the asynchronous code path.
2653 void PlayerPostThink (entity this)
2655 Player_Physics(this);
2657 if (IS_PLAYER(this)) {
2658 if(this.death_time == time && IS_DEAD(this))
2660 // player's bbox gets resized now, instead of in the damage event that killed the player,
2661 // once all the damage events of this frame have been processed with normal size
2663 setsize(this, this.mins, this.maxs);
2666 UpdateChatBubble(this);
2667 if (CS(this).impulse) ImpulseCommands(this);
2668 GetPressedKeys(this);
2671 CSQCMODEL_AUTOUPDATE(this);
2675 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2677 CS(this).pressedkeys = 0;
2678 STAT(PRESSED_KEYS, this) = 0;
2681 CSQCMODEL_AUTOUPDATE(this);
2688 Called every frame for each client by StartFrame().
2689 Use this for code that only needs to run once per server frame.
2690 frametime is always set here.
2693 void PlayerFrame (entity this)
2695 // formerly PreThink code
2696 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2697 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2699 // physics frames: update anticheat stuff
2700 anticheat_prethink(this);
2702 // Check if spectating is allowed
2703 if (blockSpectators && IS_REAL_CLIENT(this)
2704 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2705 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2707 if (dropclient_schedule(this))
2708 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2711 // Check for nameless players
2712 if (this.netname == "" || this.netname != CS(this).netname_previous)
2714 bool assume_unchanged = (CS(this).netname_previous == "");
2715 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2717 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2718 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2719 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2720 assume_unchanged = false;
2721 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2723 if (isInvisibleString(this.netname))
2725 this.netname = strzone(sprintf("Player#%d", this.playerid));
2726 sprint(this, "Warning: invisible names are not allowed.\n");
2727 assume_unchanged = false;
2728 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2730 if (!assume_unchanged && autocvar_sv_eventlog)
2731 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2732 strcpy(CS(this).netname_previous, this.netname);
2736 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2738 CS(this).version_nagtime = 0;
2739 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2743 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2746 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2750 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2751 if (r < 0) // old client
2752 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2753 else if (r > 0) // old server
2754 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2759 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2761 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2762 this.max_armorvalue = 0;
2766 if (IS_PLAYER(this) && time >= game_starttime)
2768 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2770 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2771 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2773 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2775 if (STAT(REVIVE_PROGRESS, this) >= 1)
2776 Unfreeze(this, false);
2778 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2780 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2781 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2783 if (GetResource(this, RES_HEALTH) < 1)
2786 vehicles_exit(this.vehicle, VHEF_RELEASE);
2787 if(this.event_damage)
2788 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2790 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2791 Unfreeze(this, false);
2796 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2797 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2799 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2803 if(!it.team || SAME_TEAM(this, it))
2804 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2805 else if(autocvar_g_vehicles_steal)
2806 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2808 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2810 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2814 this.last_vehiclecheck = time + 1;
2819 // formerly PostThink code
2820 if (autocvar_sv_maxidle > 0 || ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0))
2821 if (IS_REAL_CLIENT(this))
2822 if (IS_PLAYER(this) || this.wants_join || autocvar_sv_maxidle_alsokickspectators)
2823 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2825 int totalClients = 0;
2826 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2828 // maxidle disabled in local matches by not counting clients (totalClients 0)
2829 if (server_is_dedicated)
2831 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2835 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2839 else if ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0)
2841 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2847 if (totalClients < autocvar_sv_maxidle_minplayers)
2849 // idle kick disabled
2850 CS(this).parm_idlesince = time;
2852 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2854 if (CS(this).idlekick_lasttimeleft)
2856 CS(this).idlekick_lasttimeleft = 0;
2857 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2862 float maxidle_time = autocvar_sv_maxidle;
2863 if ((IS_PLAYER(this) || this.wants_join)
2864 && autocvar_sv_maxidle_playertospectator > 0)
2865 maxidle_time = autocvar_sv_maxidle_playertospectator;
2866 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2867 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2868 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2870 if ((IS_PLAYER(this) || this.wants_join) && autocvar_sv_maxidle_playertospectator > 0)
2872 if (!this.wants_join) // no countdown centreprint when getting kicked off the join queue
2873 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2876 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2880 if ((IS_PLAYER(this) || this.wants_join)
2881 && autocvar_sv_maxidle_playertospectator > 0)
2883 if (this.wants_join)
2884 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING_QUEUE, this.netname, maxidle_time);
2886 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2887 PutObserverInServer(this, true, true);
2888 // when the player is kicked off the server, these are called in ClientDisconnect()
2889 if (!TeamBalance_QueuedPlayersTagIn(this))
2890 if (autocvar_g_balance_teams_remove)
2891 TeamBalance_RemoveExcessPlayers(this);
2895 if (dropclient_schedule(this))
2896 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2900 else if (timeleft <= countdown_time
2901 && !this.wants_join) // no countdown bangs when getting kicked off the join queue
2903 if (timeleft != CS(this).idlekick_lasttimeleft)
2904 play2(this, SND(TALK2));
2905 CS(this).idlekick_lasttimeleft = timeleft;
2914 this.solid = SOLID_NOT;
2915 this.takedamage = DAMAGE_NO;
2916 set_movetype(this, MOVETYPE_NONE);
2917 CS(this).teamkill_complain = 0;
2918 CS(this).teamkill_soundtime = 0;
2919 CS(this).teamkill_soundsource = NULL;
2922 if (this.waypointsprite_attachedforcarrier) {
2923 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2924 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2928 // hack to copy the button fields from the client entity to the Client State
2929 void PM_UpdateButtons(entity this, entity store)
2932 store.impulse = this.impulse;
2935 bool typing = this.buttonchat || this.button12;
2937 store.button0 = (typing) ? 0 : this.button0;
2939 store.button2 = (typing) ? 0 : this.button2;
2940 store.button3 = (typing) ? 0 : this.button3;
2941 store.button4 = this.button4;
2942 store.button5 = (typing) ? 0 : this.button5;
2943 store.button6 = this.button6;
2944 store.button7 = this.button7;
2945 store.button8 = this.button8;
2946 store.button9 = this.button9;
2947 store.button10 = this.button10;
2948 store.button11 = this.button11;
2949 store.button12 = this.button12;
2950 store.button13 = this.button13;
2951 store.button14 = this.button14;
2952 store.button15 = this.button15;
2953 store.button16 = this.button16;
2954 store.buttonuse = this.buttonuse;
2955 store.buttonchat = this.buttonchat;
2957 store.cursor_active = this.cursor_active;
2958 store.cursor_screen = this.cursor_screen;
2959 store.cursor_trace_start = this.cursor_trace_start;
2960 store.cursor_trace_endpos = this.cursor_trace_endpos;
2961 store.cursor_trace_ent = this.cursor_trace_ent;
2963 store.ping = this.ping;
2964 store.ping_packetloss = this.ping_packetloss;
2965 store.ping_movementloss = this.ping_movementloss;
2967 store.v_angle = this.v_angle;
2968 store.movement = this.movement;
2971 NET_HANDLE(fpsreport, bool)
2973 int fps = ReadShort();
2974 PlayerScore_Set(sender, SP_FPS, fps);