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
1139 stuffcmd(this, clientstuff, "\n");
1140 stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
1142 FixClientCvars(this);
1144 // get version info from player
1145 stuffcmd(this, "cmd clientversion $gameversion\n");
1147 // notify about available teams
1150 entity balance = TeamBalance_CheckAllowedTeams(this);
1151 int t = TeamBalance_GetAllowedTeams(balance);
1152 TeamBalance_Destroy(balance);
1153 stuffcmd(this, sprintf("set _teams_available %d\n", t));
1157 stuffcmd(this, "set _teams_available 0\n");
1160 bot_relinkplayerlist();
1162 CS(this).spectatortime = time;
1163 if (blockSpectators)
1165 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
1168 CS(this).jointime = time;
1170 if (IS_REAL_CLIENT(this))
1172 if (g_weaponarena_weapons == WEPSET(TUBA))
1173 stuffcmd(this, "cl_cmd settemp chase_active 1\n");
1174 // quickmenu file must be put in a subfolder with an unique name
1175 // to reduce chances of overriding custom client quickmenus
1176 if (waypointeditor_enabled)
1177 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
1178 else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
1179 stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
1182 if (!autocvar_sv_foginterval && world.fog != "")
1183 stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
1185 if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
1186 if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
1187 send_CSQC_teamnagger();
1189 CSQCMODEL_AUTOINIT(this);
1191 CS(this).model_randomizer = random();
1193 if (IS_REAL_CLIENT(this))
1194 sv_notice_join(this);
1196 this.move_qcphysics = true;
1198 // update physics stats (players can spawn before physics runs)
1199 Physics_UpdateStats(this);
1201 IL_EACH(g_initforplayer, it.init_for_player, {
1202 it.init_for_player(it, this);
1205 Handicap_Initialize(this);
1208 if (PlayerInList(this, autocvar_g_playban_list))
1209 TRANSMUTE(Observer, this);
1211 if (PlayerInList(this, autocvar_g_chatban_list)) // chatban
1212 CS(this).muted = true;
1214 MUTATOR_CALLHOOK(ClientConnect, this);
1216 if (player_count == 1)
1218 if (autocvar_sv_autopause && server_is_dedicated)
1220 localcmd("\nsv_hook_firstjoin\n");
1224 .string shootfromfixedorigin;
1225 .entity chatbubbleentity;
1226 void player_powerups_remove_all(entity this);
1232 Called when a client disconnects from the server
1235 void ClientDisconnect(entity this)
1237 assert(IS_CLIENT(this), return);
1239 /* from "ignore" command */
1240 strfree(this.ignore_list);
1241 FOREACH_CLIENT(IS_REAL_CLIENT(it) && it.ignore_list,
1243 if(it.crypto_idfp && it.crypto_idfp != "")
1245 string mylist = ignore_removefromlist(it, this);
1247 strunzone(it.ignore_list);
1249 it.ignore_list = strzone(mylist);
1251 /* from "ignore" command */
1253 PlayerStats_GameReport_FinalizePlayer(this);
1254 if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
1255 if (CS(this).active_minigame) part_minigame(this);
1256 if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1);
1258 if (autocvar_sv_eventlog)
1259 GameLogEcho(strcat(":part:", ftos(this.playerid)));
1261 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname);
1264 SetSpectatee(this, NULL);
1266 MUTATOR_CALLHOOK(ClientDisconnect, this);
1268 strfree(CS(this).netname_previous); // needs to be before the CS entity is removed!
1269 strfree(CS_CVAR(this).weaponorder_byimpulse);
1270 ClientState_detach(this);
1272 Portal_ClearAll(this);
1274 Unfreeze(this, false);
1276 RemoveGrapplingHooks(this);
1278 strfree(this.shootfromfixedorigin);
1280 // Here, everything has been done that requires this player to be a client.
1282 this.flags &= ~FL_CLIENT;
1284 if (this.chatbubbleentity) delete(this.chatbubbleentity);
1285 if (this.killindicator) delete(this.killindicator);
1287 IL_EACH(g_counters, it.realowner == this,
1292 WaypointSprite_PlayerGone(this);
1294 bot_relinkplayerlist();
1296 strfree(this.clientstatus);
1297 if (this.personal) delete(this.personal);
1300 if (warmup_stage || game_starttime > time) ReadyCount();
1301 if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
1303 player_powerups_remove_all(this); // stop powerup sound
1307 if (player_count == 0)
1308 localcmd("\nsv_hook_lastleave\n");
1311 void ChatBubbleThink(entity this)
1313 this.nextthink = time;
1314 if ((this.owner.alpha < 0) || this.owner.chatbubbleentity != this)
1316 if(this.owner) // but why can that ever be NULL?
1317 this.owner.chatbubbleentity = NULL;
1324 if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) )
1326 if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) )
1327 this.mdl = "models/sprites/minigame_busy.iqm";
1328 else if (PHYS_INPUT_BUTTON_CHAT(this.owner))
1329 this.mdl = "models/misc/chatbubble.spr";
1332 if ( this.model != this.mdl )
1333 _setmodel(this, this.mdl);
1337 void UpdateChatBubble(entity this)
1341 // spawn a chatbubble entity if needed
1342 if (!this.chatbubbleentity)
1344 this.chatbubbleentity = new(chatbubbleentity);
1345 this.chatbubbleentity.owner = this;
1346 this.chatbubbleentity.exteriormodeltoclient = this;
1347 setthink(this.chatbubbleentity, ChatBubbleThink);
1348 this.chatbubbleentity.nextthink = time;
1349 setmodel(this.chatbubbleentity, MDL_CHAT); // precision set below
1350 //setorigin(this.chatbubbleentity, this.origin + '0 0 15' + this.maxs_z * '0 0 1');
1351 setorigin(this.chatbubbleentity, '0 0 15' + this.maxs_z * '0 0 1');
1352 setattachment(this.chatbubbleentity, this, ""); // sticks to moving player better, also conserves bandwidth
1353 this.chatbubbleentity.mdl = this.chatbubbleentity.model;
1354 //this.chatbubbleentity.model = "";
1355 this.chatbubbleentity.effects = EF_LOWPRECISION;
1359 void calculate_player_respawn_time(entity this)
1361 if(MUTATOR_CALLHOOK(CalculateRespawnTime, this))
1364 float gametype_setting_tmp;
1365 float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
1366 float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
1367 float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
1368 float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
1369 float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
1370 float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
1372 float pcount = 1; // Include myself whether or not team is already set right and I'm a "player".
1375 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1376 if(it.team == this.team)
1379 if (sdelay_small_count == 0)
1380 sdelay_small_count = 1;
1381 if (sdelay_large_count == 0)
1382 sdelay_large_count = 1;
1386 FOREACH_CLIENT(IS_PLAYER(it) && it != this, {
1389 if (sdelay_small_count == 0)
1391 if (IS_INDEPENDENT_PLAYER(this))
1393 // Players play independently. No point in requiring enemies.
1394 sdelay_small_count = 1;
1398 // Players play AGAINST each other. Enemies required.
1399 sdelay_small_count = 2;
1402 if (sdelay_large_count == 0)
1404 if (IS_INDEPENDENT_PLAYER(this))
1406 // Players play independently. No point in requiring enemies.
1407 sdelay_large_count = 1;
1411 // Players play AGAINST each other. Enemies required.
1412 sdelay_large_count = 2;
1419 if (pcount <= sdelay_small_count)
1420 sdelay = sdelay_small;
1421 else if (pcount >= sdelay_large_count)
1422 sdelay = sdelay_large;
1423 else // NOTE: this case implies sdelay_large_count > sdelay_small_count.
1424 sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
1427 this.respawn_time = ceil((time + sdelay) / waves) * waves;
1429 this.respawn_time = time + sdelay;
1431 if(sdelay < sdelay_max)
1432 this.respawn_time_max = time + sdelay_max;
1434 this.respawn_time_max = this.respawn_time;
1436 if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75))
1437 this.respawn_countdown = 10; // first number to count down from is 10
1439 this.respawn_countdown = -1; // do not count down
1441 if(autocvar_g_forced_respawn)
1442 this.respawn_flags = this.respawn_flags | RESPAWN_FORCE;
1445 // LordHavoc: this hack will be removed when proper _pants/_shirt layers are
1446 // added to the model skins
1447 /*void UpdateColorModHack()
1450 c = this.clientcolors & 15;
1451 // LordHavoc: only bothering to support white, green, red, yellow, blue
1452 if (!teamplay) this.colormod = '0 0 0';
1453 else if (c == 0) this.colormod = '1.00 1.00 1.00';
1454 else if (c == 3) this.colormod = '0.10 1.73 0.10';
1455 else if (c == 4) this.colormod = '1.73 0.10 0.10';
1456 else if (c == 12) this.colormod = '1.22 1.22 0.10';
1457 else if (c == 13) this.colormod = '0.10 0.10 1.73';
1458 else this.colormod = '1 1 1';
1461 void respawn(entity this)
1463 bool damagedbycontents_prev = this.damagedbycontents;
1466 if(autocvar_g_respawn_ghosts)
1468 this.solid = SOLID_NOT;
1469 this.takedamage = DAMAGE_NO;
1470 this.damagedbycontents = false;
1471 set_movetype(this, MOVETYPE_FLY);
1472 this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed;
1473 this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3;
1474 this.effects |= CSQCMODEL_EF_RESPAWNGHOST;
1475 this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha);
1476 Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1);
1477 if(autocvar_g_respawn_ghosts_time > 0)
1478 SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime);
1481 SUB_SetFade (this, time, 1); // fade out the corpse immediately
1485 this.damagedbycontents = damagedbycontents_prev;
1487 this.effects |= EF_NODRAW; // prevent another CopyBody
1488 PutClientInServer(this);
1491 void play_countdown(entity this, float finished, Sound samp)
1494 float time_left = finished - time;
1495 if(IS_REAL_CLIENT(this) && time_left < 6 && floor(time_left - frametime) != floor(time_left))
1496 sound(this, CH_INFO, samp, VOL_BASE, ATTEN_NORM);
1499 // it removes special powerups not handled by StatusEffects
1500 void player_powerups_remove_all(entity this)
1502 if (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1504 // don't play the poweroff sound when the game restarts or the player disconnects
1505 if (time > game_starttime + 1 && IS_CLIENT(this)
1506 && !(start_items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS)))
1508 sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM);
1510 if (this.items & (IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS))
1511 stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound
1512 this.items -= (this.items & (IT_SUPERWEAPON | IT_UNLIMITED_AMMO | IT_UNLIMITED_SUPERWEAPONS));
1516 void player_powerups(entity this)
1518 if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped)
1519 this.modelflags |= MF_ROCKET;
1521 this.modelflags &= ~MF_ROCKET;
1523 this.effects &= ~EF_NODEPTHTEST;
1526 player_powerups_remove_all(this);
1528 if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed
1531 // add a way to see what the items were BEFORE all of these checks for the mutator hook
1532 int items_prev = this.items;
1534 if (!MUTATOR_IS_ENABLED(mutator_instagib))
1536 // NOTE: superweapons are a special case and as such are handled here instead of the status effects system
1537 if (this.items & IT_SUPERWEAPON)
1539 if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS))
1541 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL);
1542 this.items = this.items - (this.items & IT_SUPERWEAPON);
1543 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname);
1544 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST);
1546 else if (this.items & IT_UNLIMITED_SUPERWEAPONS)
1548 // don't let them run out
1552 play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF);
1553 if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this))
1555 this.items = this.items - (this.items & IT_SUPERWEAPON);
1556 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1557 //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_BROKEN, this.netname);
1558 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_BROKEN);
1562 else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)
1564 if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS))
1566 this.items = this.items | IT_SUPERWEAPON;
1567 if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
1570 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
1571 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
1576 if(StatusEffects_active(STATUSEFFECT_Superweapons, this))
1577 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT);
1578 STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS;
1581 else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame!
1583 StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR);
1587 if(autocvar_g_nodepthtestplayers)
1588 this.effects = this.effects | EF_NODEPTHTEST;
1590 if(autocvar_g_fullbrightplayers)
1591 this.effects = this.effects | EF_FULLBRIGHT;
1593 MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev);
1596 float CalcRegen(float current, float stable, float regenfactor, float regenframetime)
1598 if(current > stable)
1600 else if(current > stable - 0.25) // when close enough, "snap"
1603 return min(stable, current + (stable - current) * regenfactor * regenframetime);
1606 float CalcRot(float current, float stable, float rotfactor, float rotframetime)
1608 if(current < stable)
1610 else if(current < stable + 0.25) // when close enough, "snap"
1613 return max(stable, current + (stable - current) * rotfactor * rotframetime);
1616 void RotRegen(entity this, Resource res, float limit_mod,
1617 float regenstable, float regenfactor, float regenlinear, float regenframetime,
1618 float rotstable, float rotfactor, float rotlinear, float rotframetime)
1620 float old = GetResource(this, res);
1621 float current = old;
1622 if(current > rotstable)
1624 if(rotframetime > 0)
1626 current = CalcRot(current, rotstable, rotfactor, rotframetime);
1627 current = max(rotstable, current - rotlinear * rotframetime);
1630 else if(current < regenstable)
1632 if(regenframetime > 0)
1634 current = CalcRegen(current, regenstable, regenfactor, regenframetime);
1635 current = min(regenstable, current + regenlinear * regenframetime);
1639 float limit = GetResourceLimit(this, res) * limit_mod;
1644 SetResource(this, res, current);
1647 void player_regen(entity this)
1649 float max_mod, regen_mod, rot_mod, limit_mod;
1650 max_mod = regen_mod = rot_mod = limit_mod = 1;
1652 float regen_health = autocvar_g_balance_health_regen;
1653 float regen_health_linear = autocvar_g_balance_health_regenlinear;
1654 float regen_health_rot = autocvar_g_balance_health_rot;
1655 float regen_health_rotlinear = autocvar_g_balance_health_rotlinear;
1656 float regen_health_stable = autocvar_g_balance_health_regenstable;
1657 float regen_health_rotstable = autocvar_g_balance_health_rotstable;
1658 bool mutator_returnvalue = MUTATOR_CALLHOOK(PlayerRegen, this, max_mod, regen_mod, rot_mod, limit_mod, regen_health, regen_health_linear, regen_health_rot,
1659 regen_health_rotlinear, regen_health_stable, regen_health_rotstable);
1660 max_mod = M_ARGV(1, float);
1661 regen_mod = M_ARGV(2, float);
1662 rot_mod = M_ARGV(3, float);
1663 limit_mod = M_ARGV(4, float);
1664 regen_health = M_ARGV(5, float);
1665 regen_health_linear = M_ARGV(6, float);
1666 regen_health_rot = M_ARGV(7, float);
1667 regen_health_rotlinear = M_ARGV(8, float);
1668 regen_health_stable = M_ARGV(9, float);
1669 regen_health_rotstable = M_ARGV(10, float);
1671 float rotstable, regenstable, rotframetime, regenframetime;
1673 if(!mutator_returnvalue)
1674 if(!STAT(FROZEN, this))
1676 regenstable = autocvar_g_balance_armor_regenstable;
1677 rotstable = autocvar_g_balance_armor_rotstable;
1678 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1679 rotframetime = (time > this.pauserotarmor_finished) ? (rot_mod * frametime) : 0;
1680 RotRegen(this, RES_ARMOR, limit_mod,
1681 regenstable, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regenframetime,
1682 rotstable, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rotframetime);
1684 // NOTE: max_mod is only applied to health
1685 regenstable = regen_health_stable * max_mod;
1686 rotstable = regen_health_rotstable * max_mod;
1687 regenframetime = (time > this.pauseregen_finished) ? (regen_mod * frametime) : 0;
1688 rotframetime = (time > this.pauserothealth_finished) ? (rot_mod * frametime) : 0;
1689 RotRegen(this, RES_HEALTH, limit_mod,
1690 regenstable, regen_health, regen_health_linear, regenframetime,
1691 rotstable, regen_health_rot, regen_health_rotlinear, rotframetime);
1694 // if player rotted to death... die!
1695 // check this outside above checks, as player may still be able to rot to death
1696 if(GetResource(this, RES_HEALTH) < 1)
1699 vehicles_exit(this.vehicle, VHEF_RELEASE);
1700 if(this.event_damage)
1701 this.event_damage(this, this, this, 1, DEATH_ROT.m_id, DMG_NOWEP, this.origin, '0 0 0');
1704 if (!(this.items & IT_UNLIMITED_AMMO))
1706 regenstable = autocvar_g_balance_fuel_regenstable;
1707 rotstable = autocvar_g_balance_fuel_rotstable;
1708 regenframetime = ((time > this.pauseregen_finished) && (this.items & ITEM_JetpackRegen.m_itemid)) ? frametime : 0;
1709 rotframetime = (time > this.pauserotfuel_finished) ? frametime : 0;
1710 RotRegen(this, RES_FUEL, 1,
1711 regenstable, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, regenframetime,
1712 rotstable, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, rotframetime);
1717 void SetZoomState(entity this, float newzoom)
1719 if(newzoom != CS(this).zoomstate)
1721 CS(this).zoomstate = newzoom;
1722 ClientData_Touch(this);
1724 zoomstate_set = true;
1727 void GetPressedKeys(entity this)
1729 MUTATOR_CALLHOOK(GetPressedKeys, this);
1732 CS(this).pressedkeys = 0;
1733 STAT(PRESSED_KEYS, this) = 0;
1737 // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code
1738 int keys = STAT(PRESSED_KEYS, this);
1739 keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0);
1740 keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0);
1741 keys = BITSET(keys, KEY_RIGHT, CS(this).movement.y > 0);
1742 keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
1744 keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
1745 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
1746 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
1747 keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
1748 CS(this).pressedkeys = keys; // store for other users
1750 STAT(PRESSED_KEYS, this) = keys;
1754 ======================
1755 spectate mode routines
1756 ======================
1759 void SpectateCopy(entity this, entity spectatee)
1761 TC(Client, this); TC(Client, spectatee);
1763 MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
1764 PS(this) = PS(spectatee);
1765 this.armortype = spectatee.armortype;
1766 SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR));
1767 SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS));
1768 SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA));
1769 SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS));
1770 SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS));
1771 SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS));
1772 SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL));
1773 this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance
1774 SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH));
1775 CS(this).impulse = 0;
1776 this.disableclientprediction = 1; // no need to run prediction on a spectator
1777 this.items = spectatee.items;
1778 STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee);
1779 STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee);
1780 STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee);
1781 STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee);
1782 STAT(WEAPONS, this) = STAT(WEAPONS, spectatee);
1783 this.punchangle = spectatee.punchangle;
1784 this.view_ofs = spectatee.view_ofs;
1785 this.velocity = spectatee.velocity;
1786 this.dmg_take = spectatee.dmg_take;
1787 this.dmg_save = spectatee.dmg_save;
1788 this.dmg_inflictor = spectatee.dmg_inflictor;
1789 this.v_angle = spectatee.v_angle;
1790 this.angles = spectatee.v_angle;
1791 STAT(FROZEN, this) = STAT(FROZEN, spectatee);
1792 STAT(REVIVE_PROGRESS, this) = STAT(REVIVE_PROGRESS, spectatee);
1793 this.viewloc = spectatee.viewloc;
1794 if(!PHYS_INPUT_BUTTON_USE(this) && STAT(CAMERA_SPECTATOR, this) != 2)
1795 this.fixangle = true;
1796 setorigin(this, spectatee.origin);
1797 setsize(this, spectatee.mins, spectatee.maxs);
1798 SetZoomState(this, CS(spectatee).zoomstate);
1800 anticheat_spectatecopy(this, spectatee);
1801 STAT(HUD, this) = STAT(HUD, spectatee);
1802 if(spectatee.vehicle)
1804 this.angles = spectatee.v_angle;
1806 //this.fixangle = false;
1807 //this.velocity = spectatee.vehicle.velocity;
1808 this.vehicle_health = spectatee.vehicle_health;
1809 this.vehicle_shield = spectatee.vehicle_shield;
1810 this.vehicle_energy = spectatee.vehicle_energy;
1811 this.vehicle_ammo1 = spectatee.vehicle_ammo1;
1812 this.vehicle_ammo2 = spectatee.vehicle_ammo2;
1813 this.vehicle_reload1 = spectatee.vehicle_reload1;
1814 this.vehicle_reload2 = spectatee.vehicle_reload2;
1816 //msg_entity = this;
1818 // WriteByte (MSG_ONE, SVC_SETVIEWANGLES);
1819 //WriteAngle(MSG_ONE, spectatee.v_angle.x);
1820 // WriteAngle(MSG_ONE, spectatee.v_angle.y);
1821 // WriteAngle(MSG_ONE, spectatee.v_angle.z);
1823 //WriteByte (MSG_ONE, SVC_SETVIEW);
1824 // WriteEntity(MSG_ONE, this);
1825 //makevectors(spectatee.v_angle);
1826 //setorigin(this, spectatee.origin - v_forward * 400 + v_up * 300);*/
1830 bool SpectateUpdate(entity this)
1835 if(!IS_PLAYER(this.enemy) || this == this.enemy)
1837 SetSpectatee(this, NULL);
1841 SpectateCopy(this, this.enemy);
1846 bool SpectateSet(entity this)
1848 if(!IS_PLAYER(this.enemy))
1851 ClientData_Touch(this.enemy);
1854 WriteByte(MSG_ONE, SVC_SETVIEW);
1855 WriteEntity(MSG_ONE, this.enemy);
1856 set_movetype(this, MOVETYPE_NONE);
1857 accuracy_resend(this);
1859 if(!SpectateUpdate(this))
1860 PutObserverInServer(this, false, true);
1865 void SetSpectatee_status(entity this, int spectatee_num)
1867 int oldspectatee_status = CS(this).spectatee_status;
1868 CS(this).spectatee_status = spectatee_num;
1870 if (CS(this).spectatee_status != oldspectatee_status)
1872 if (STAT(PRESSED_KEYS, this))
1874 CS(this).pressedkeys = 0;
1875 STAT(PRESSED_KEYS, this) = 0;
1877 ClientData_Touch(this);
1878 if (g_race || g_cts) race_InitSpectator();
1882 void SetSpectatee(entity this, entity spectatee)
1884 if(IS_BOT_CLIENT(this))
1885 return; // bots abuse .enemy, this code is useless to them
1887 entity old_spectatee = this.enemy;
1889 this.enemy = spectatee;
1892 // these are required to fix the spectator bug with arc
1895 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1897 .entity weaponentity = weaponentities[slot];
1898 if(old_spectatee.(weaponentity).arc_beam)
1899 old_spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1904 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
1906 .entity weaponentity = weaponentities[slot];
1907 if(spectatee.(weaponentity).arc_beam)
1908 spectatee.(weaponentity).arc_beam.SendFlags |= ARC_SF_SETTINGS;
1913 SetSpectatee_status(this, etof(spectatee));
1915 // needed to update spectator list
1916 if(old_spectatee) { ClientData_Touch(old_spectatee); }
1919 bool Spectate(entity this, entity pl)
1921 if(MUTATOR_CALLHOOK(SpectateSet, this, pl))
1923 pl = M_ARGV(1, entity);
1925 SetSpectatee(this, pl);
1926 return SpectateSet(this);
1929 bool SpectateNext(entity this)
1931 entity ent = find(this.enemy, classname, STR_PLAYER);
1933 if (MUTATOR_CALLHOOK(SpectateNext, this, ent))
1934 ent = M_ARGV(1, entity);
1936 ent = find(ent, classname, STR_PLAYER);
1938 if(ent) { SetSpectatee(this, ent); }
1940 return SpectateSet(this);
1943 bool SpectatePrev(entity this)
1945 // NOTE: chain order is from the highest to the lower entnum (unlike find)
1946 entity ent = findchain(classname, STR_PLAYER);
1947 if (!ent) // no player
1951 // skip players until current spectated player
1953 while(ent && ent != this.enemy)
1956 switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first))
1958 case MUT_SPECPREV_FOUND:
1959 ent = M_ARGV(1, entity);
1961 case MUT_SPECPREV_RETURN:
1963 case MUT_SPECPREV_CONTINUE:
1974 SetSpectatee(this, ent);
1975 return SpectateSet(this);
1980 ShowRespawnCountdown()
1982 Update a respawn countdown display.
1985 void ShowRespawnCountdown(entity this)
1988 if(!IS_DEAD(this)) // just respawned?
1992 number = ceil(this.respawn_time - time);
1995 if(number <= this.respawn_countdown)
1997 this.respawn_countdown = number - 1;
1998 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
1999 { Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_RESPAWN, number)); }
2004 .bool team_selected;
2005 bool ShowTeamSelection(entity this)
2007 if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
2009 if (frametime) // once per frame is more than enough
2010 stuffcmd(this, "_scoreboard_team_selection 1\n");
2013 void Join(entity this)
2015 if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
2018 TRANSMUTE(Player, this);
2020 if(!this.team_selected)
2021 if(autocvar_g_campaign || autocvar_g_balance_teams)
2022 TeamBalance_JoinBestTeam(this);
2024 if(autocvar_g_campaign)
2025 campaign_bots_may_start = true;
2027 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN);
2029 PutClientInServer(this);
2032 if(teamplay && this.team != -1)
2036 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
2037 this.team_selected = false;
2040 int GetPlayerLimit()
2043 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)
2044 // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
2045 int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
2046 MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
2047 player_limit = M_ARGV(0, int);
2048 return player_limit < maxclients ? player_limit : 0;
2052 * Determines whether the player is allowed to join. This depends on cvar
2053 * g_maxplayers, if it isn't used this function always return true, otherwise
2054 * it checks whether the number of currently playing players exceeds g_maxplayers.
2055 * @return int number of free slots for players, 0 if none
2057 int nJoinAllowed(entity this, entity ignore)
2060 // this is called that way when checking if anyone may be able to join (to build qcstatus)
2061 // so report 0 free slots if restricted
2063 if(autocvar_g_forced_team_otherwise == "spectate")
2065 if(autocvar_g_forced_team_otherwise == "spectator")
2069 if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2070 return 0; // forced spectators can never join
2072 static float msg_time = 0;
2073 if(this && !INGAME(this) && ignore && PlayerInList(this, autocvar_g_playban_list))
2077 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PLAYBAN);
2078 msg_time = time + 0.5;
2083 // TODO simplify this
2084 int totalClients = 0;
2085 int currentlyPlaying = 0;
2086 FOREACH_CLIENT(true, {
2089 if(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME(it)))
2093 int player_limit = GetPlayerLimit();
2097 free_slots = maxclients - totalClients;
2098 else if(player_limit > 0 && currentlyPlaying < player_limit)
2099 free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
2101 if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
2103 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
2104 msg_time = time + 0.5;
2110 bool joinAllowed(entity this)
2112 if (CS(this).version_mismatch) return false;
2113 if (time < CS(this).jointime + MIN_SPEC_TIME) return false;
2114 if (!nJoinAllowed(this, this)) return false;
2115 if (teamplay && lockteams) return false;
2116 if (MUTATOR_CALLHOOK(ForbidSpawn, this)) return false;
2117 if (ShowTeamSelection(this)) return false;
2121 void show_entnum(entity this)
2123 // waypoint editor implements a similar feature for waypoints
2124 if (waypointeditor_enabled)
2127 if (wasfreed(this.wp_aimed))
2128 this.wp_aimed = NULL;
2130 WarpZone_crosshair_trace_plusvisibletriggers(this);
2135 if (ent != this.wp_aimed)
2137 string str = sprintf(
2138 "^7ent #%d\n^8 netname: ^3%s\n^8 classname: ^5%s\n^8 origin: ^2'%s'",
2139 etof(ent), ent.netname, ent.classname, vtos(ent.origin));
2140 debug_text_3d((ent.absmin + ent.absmax) * 0.5, str, 0, 7, '0 0 0');
2143 if (this.wp_aimed != ent)
2144 this.wp_aimed = ent;
2147 .bool dualwielding_prev;
2148 bool PlayerThink(entity this)
2150 if (game_stopped || intermission_running) {
2151 this.modelflags &= ~MF_ROCKET;
2152 if(intermission_running)
2153 IntermissionThink(this);
2157 if (timeout_status == TIMEOUT_ACTIVE) {
2158 // don't allow the player to turn around while game is paused
2159 // FIXME turn this into CSQC stuff
2160 this.v_angle = this.lastV_angle;
2161 this.angles = this.lastV_angle;
2162 this.fixangle = true;
2165 if (frametime) player_powerups(this);
2167 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2169 if (IS_DEAD(this)) {
2170 if (this.personal && g_race_qualifying) {
2171 if (time > this.respawn_time) {
2172 STAT(RESPAWN_TIME, this) = this.respawn_time = time + 1; // only retry once a second
2174 CS(this).impulse = CHIMPULSE_SPEEDRUN.impulse;
2177 if (frametime) player_anim(this);
2179 if (this.respawn_flags & RESPAWN_DENY)
2181 STAT(RESPAWN_TIME, this) = 0;
2185 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));
2187 switch(this.deadflag)
2191 if ((this.respawn_flags & RESPAWN_FORCE) && !(this.respawn_time < this.respawn_time_max))
2192 this.deadflag = DEAD_RESPAWNING;
2193 else if (!button_pressed || (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE)))
2194 this.deadflag = DEAD_DEAD;
2200 this.deadflag = DEAD_RESPAWNABLE;
2201 else if (time >= this.respawn_time_max && (this.respawn_flags & RESPAWN_FORCE))
2202 this.deadflag = DEAD_RESPAWNING;
2205 case DEAD_RESPAWNABLE:
2207 if (!button_pressed || (this.respawn_flags & RESPAWN_FORCE))
2208 this.deadflag = DEAD_RESPAWNING;
2211 case DEAD_RESPAWNING:
2213 if (time > this.respawn_time)
2215 this.respawn_time = time + 1; // only retry once a second
2216 this.respawn_time_max = this.respawn_time;
2223 ShowRespawnCountdown(this);
2225 if (this.respawn_flags & RESPAWN_SILENT)
2226 STAT(RESPAWN_TIME, this) = 0;
2227 else if ((this.respawn_flags & RESPAWN_FORCE) && this.respawn_time < this.respawn_time_max)
2229 if (time < this.respawn_time)
2230 STAT(RESPAWN_TIME, this) = this.respawn_time;
2231 else if (this.deadflag != DEAD_RESPAWNING)
2232 STAT(RESPAWN_TIME, this) = -this.respawn_time_max;
2235 STAT(RESPAWN_TIME, this) = this.respawn_time;
2238 // if respawning, invert stat_respawn_time to indicate this, the client translates it
2239 if (this.deadflag == DEAD_RESPAWNING && STAT(RESPAWN_TIME, this) > 0)
2240 STAT(RESPAWN_TIME, this) *= -1;
2245 FixPlayermodel(this);
2247 if (this.shootfromfixedorigin != autocvar_g_shootfromfixedorigin) {
2248 strcpy(this.shootfromfixedorigin, autocvar_g_shootfromfixedorigin);
2249 stuffcmd(this, sprintf("\ncl_shootfromfixedorigin \"%s\"\n", autocvar_g_shootfromfixedorigin));
2252 // reset gun alignment when dual wielding status changes
2253 // to ensure guns are always aligned right and left
2254 bool dualwielding = W_DualWielding(this);
2255 if(this.dualwielding_prev != dualwielding)
2257 W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign);
2258 this.dualwielding_prev = dualwielding;
2261 // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers
2264 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2266 .entity weaponentity = weaponentities[slot];
2267 if(WEP_CVAR(vortex, charge_always))
2268 W_Vortex_Charge(this, weaponentity, frametime);
2269 W_WeaponFrame(this, weaponentity);
2275 // WEAPONTODO: Add a weapon request for this
2276 // rot vortex charge to the charge limit
2277 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2279 .entity weaponentity = weaponentities[slot];
2280 if (WEP_CVAR(vortex, charge_rot_rate) && this.(weaponentity).vortex_charge > WEP_CVAR(vortex, charge_limit) && this.(weaponentity).vortex_charge_rottime < time)
2281 this.(weaponentity).vortex_charge = bound(WEP_CVAR(vortex, charge_limit), this.(weaponentity).vortex_charge - WEP_CVAR(vortex, charge_rot_rate) * frametime / W_TICSPERFRAME, 1);
2286 this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime);
2289 monsters_setstatus(this);
2294 .bool would_spectate;
2295 // merged SpectatorThink and ObserverThink (old names are here so you can grep for them)
2296 void ObserverOrSpectatorThink(entity this)
2298 bool is_spec = IS_SPEC(this);
2299 if ( CS(this).impulse )
2301 int r = MinigameImpulse(this, CS(this).impulse);
2303 CS(this).impulse = 0;
2305 if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse)
2307 STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3;
2308 CS(this).impulse = 0;
2313 if (frametime && autocvar_sv_show_entnum) show_entnum(this);
2315 if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
2317 CS(this).autojoin_checked = true;
2318 TRANSMUTE(Player, this);
2319 PutClientInServer(this);
2321 .entity weaponentity = weaponentities[0];
2322 if(this.(weaponentity).m_weapon == WEP_Null)
2323 W_NextWeapon(this, 0, weaponentity);
2328 if (this.flags & FL_JUMPRELEASED) {
2329 if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
2330 this.flags &= ~FL_JUMPRELEASED;
2331 this.flags |= FL_SPAWNING;
2332 } 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)))
2333 || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) {
2334 this.flags &= ~FL_JUMPRELEASED;
2335 if(SpectateNext(this)) {
2336 TRANSMUTE(Spectator, this);
2337 } else if (is_spec) {
2338 TRANSMUTE(Observer, this);
2339 PutClientInServer(this);
2342 this.would_spectate = false; // unable to spectate anyone
2344 CS(this).impulse = 0;
2345 } else if (is_spec) {
2346 if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) {
2347 this.flags &= ~FL_JUMPRELEASED;
2348 if(SpectatePrev(this)) {
2349 TRANSMUTE(Spectator, this);
2351 TRANSMUTE(Observer, this);
2352 PutClientInServer(this);
2354 CS(this).impulse = 0;
2355 } else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
2356 if(!observe_blocked_if_eliminated || !INGAME(this)) {
2357 this.would_spectate = false;
2358 this.flags &= ~FL_JUMPRELEASED;
2359 TRANSMUTE(Observer, this);
2360 PutClientInServer(this);
2362 } else if(!SpectateUpdate(this) && !SpectateNext(this)) {
2363 PutObserverInServer(this, false, true);
2364 this.would_spectate = true;
2368 bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating;
2369 if (PHYS_INPUT_BUTTON_USE(this))
2370 wouldclip = !wouldclip;
2371 int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP);
2372 set_movetype(this, preferred_movetype);
2374 } else { // jump pressed
2375 if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this)))
2376 || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) {
2377 this.flags |= FL_JUMPRELEASED;
2378 // primary attack pressed
2379 if(this.flags & FL_SPAWNING)
2381 this.flags &= ~FL_SPAWNING;
2382 if(joinAllowed(this))
2384 else if(time < CS(this).jointime + MIN_SPEC_TIME)
2385 CS(this).autojoin_checked = -1;
2389 if(is_spec && !SpectateUpdate(this))
2390 PutObserverInServer(this, false, true);
2393 this.flags |= FL_CLIENT | FL_NOTARGET;
2396 void PlayerUseKey(entity this)
2398 if (!IS_PLAYER(this))
2405 vehicles_exit(this.vehicle, VHEF_NORMAL);
2409 else if(autocvar_g_vehicles_enter)
2411 if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2413 entity head, closest_target = NULL;
2414 head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true);
2416 while(head) // find the closest acceptable target to enter
2418 if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO)
2419 if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this)))
2423 if(vlen2(this.origin - head.origin) < vlen2(this.origin - closest_target.origin))
2424 { closest_target = head; }
2426 else { closest_target = head; }
2432 if(closest_target) { vehicles_enter(this, closest_target); return; }
2436 // a use key was pressed; call handlers
2437 MUTATOR_CALLHOOK(PlayerUseKey, this);
2445 Called every frame for each real client by DP (and for each bot by StartFrame()),
2446 and when executing every asynchronous move, so only include things that MUST be done then.
2447 Use PlayerFrame() instead for code that only needs to run once per server frame.
2448 frametime == 0 in the asynchronous code path.
2450 TODO: move more stuff from here and PlayerThink() and ObserverOrSpectatorThink() to PlayerFrame() (frametime is always set there)
2453 .float last_vehiclecheck;
2454 void PlayerPreThink (entity this)
2456 WarpZone_PlayerPhysics_FixVAngle(this);
2458 zoomstate_set = false;
2460 MUTATOR_CALLHOOK(PlayerPreThink, this);
2462 if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed)
2464 CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
2466 if (IS_PLAYER(this)) {
2467 if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
2468 error("Client can't be spawned as player on connection!");
2469 if(!PlayerThink(this))
2472 else if (game_stopped || intermission_running) {
2473 if(intermission_running)
2474 IntermissionThink(this);
2477 else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME)
2479 bool early_join_requested = (CS(this).autojoin_checked < 0);
2480 CS(this).autojoin_checked = 1;
2481 // don't do this in ClientConnect
2482 // many things can go wrong if a client is spawned as player on connection
2483 if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
2484 || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
2485 && (!teamplay || autocvar_g_balance_teams)))
2487 if(joinAllowed(this))
2492 else if (IS_OBSERVER(this) || IS_SPEC(this)) {
2493 ObserverOrSpectatorThink(this);
2496 // WEAPONTODO: Add weapon request for this
2497 if (!zoomstate_set) {
2498 bool wep_zoomed = false;
2499 for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot)
2501 .entity weaponentity = weaponentities[slot];
2502 Weapon thiswep = this.(weaponentity).m_weapon;
2503 if(thiswep != WEP_Null && thiswep.wr_zoom)
2504 wep_zoomed += thiswep.wr_zoom(thiswep, this);
2506 SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed);
2509 // Voice sound effects
2510 if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime)
2512 CS(this).teamkill_soundtime = 0;
2514 entity e = CS(this).teamkill_soundsource;
2515 entity oldpusher = e.pusher;
2517 PlayerSound(e, playersound_teamshoot, CH_VOICE, VOL_BASEVOICE, VOICETYPE_LASTATTACKER_ONLY);
2518 e.pusher = oldpusher;
2521 if (CS(this).taunt_soundtime && time > CS(this).taunt_soundtime) {
2522 CS(this).taunt_soundtime = 0;
2523 PlayerSound(this, playersound_taunt, CH_VOICE, VOL_BASEVOICE, VOICETYPE_AUTOTAUNT);
2526 target_voicescript_next(this);
2529 void DrownPlayer(entity this)
2531 if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle
2532 || STAT(FROZEN, this) || this.watertype != CONTENT_WATER)
2534 STAT(AIR_FINISHED, this) = 0;
2538 if (this.waterlevel != WATERLEVEL_SUBMERGED)
2540 if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time)
2541 PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND);
2542 STAT(AIR_FINISHED, this) = 0;
2546 if (!STAT(AIR_FINISHED, this))
2547 STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay;
2548 if (STAT(AIR_FINISHED, this) < time)
2550 if (this.pain_finished < time)
2552 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');
2553 this.pain_finished = time + 0.5;
2559 .bool move_qcphysics;
2561 void Player_Physics(entity this)
2563 this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype;
2565 if(!this.move_qcphysics)
2568 if(!frametime && !CS(this).pm_frametime)
2571 Movetype_Physics_NoMatchTicrate(this, CS(this).pm_frametime, true);
2573 CS(this).pm_frametime = 0;
2580 Called every frame for each real client by DP (and for each bot by StartFrame()),
2581 and when executing every asynchronous move, so only include things that MUST be done then.
2582 Use PlayerFrame() instead for code that only needs to run once per server frame.
2583 frametime == 0 in the asynchronous code path.
2586 void PlayerPostThink (entity this)
2588 Player_Physics(this);
2590 if (IS_PLAYER(this)) {
2591 if(this.death_time == time && IS_DEAD(this))
2593 // player's bbox gets resized now, instead of in the damage event that killed the player,
2594 // once all the damage events of this frame have been processed with normal size
2596 setsize(this, this.mins, this.maxs);
2599 UpdateChatBubble(this);
2600 if (CS(this).impulse) ImpulseCommands(this);
2601 GetPressedKeys(this);
2604 CSQCMODEL_AUTOUPDATE(this);
2608 else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this))
2610 CS(this).pressedkeys = 0;
2611 STAT(PRESSED_KEYS, this) = 0;
2614 CSQCMODEL_AUTOUPDATE(this);
2621 Called every frame for each client by StartFrame().
2622 Use this for code that only needs to run once per server frame.
2623 frametime is always set here.
2626 void PlayerFrame (entity this)
2628 // formerly PreThink code
2629 STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO
2630 STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump;
2632 // physics frames: update anticheat stuff
2633 anticheat_prethink(this);
2635 // Check if spectating is allowed
2636 if (blockSpectators && IS_REAL_CLIENT(this)
2637 && (IS_SPEC(this) || IS_OBSERVER(this)) && !INGAME(this)
2638 && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime))
2640 if (dropclient_schedule(this))
2641 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING);
2644 // Check for nameless players
2645 if (this.netname == "" || this.netname != CS(this).netname_previous)
2647 bool assume_unchanged = (CS(this).netname_previous == "");
2648 if (autocvar_sv_name_maxlength > 0 && strlennocol(this.netname) > autocvar_sv_name_maxlength)
2650 int new_length = textLengthUpToLength(this.netname, autocvar_sv_name_maxlength, strlennocol);
2651 this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7"));
2652 sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_sv_name_maxlength));
2653 assume_unchanged = false;
2654 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2656 if (isInvisibleString(this.netname))
2658 this.netname = strzone(sprintf("Player#%d", this.playerid));
2659 sprint(this, "Warning: invisible names are not allowed.\n");
2660 assume_unchanged = false;
2661 // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe?
2663 if (!assume_unchanged && autocvar_sv_eventlog)
2664 GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false)));
2665 strcpy(CS(this).netname_previous, this.netname);
2669 if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime)
2671 CS(this).version_nagtime = 0;
2672 if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0)
2676 else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0)
2679 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2683 int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion);
2684 if (r < 0) // old client
2685 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2686 else if (r > 0) // old server
2687 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion);
2692 if (!(this.flags & FL_GODMODE) && this.max_armorvalue)
2694 Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_GODMODE_OFF, this.max_armorvalue);
2695 this.max_armorvalue = 0;
2699 if (IS_PLAYER(this) && time >= game_starttime)
2701 if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING)
2703 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1);
2704 SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health));
2706 this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1);
2708 if (STAT(REVIVE_PROGRESS, this) >= 1)
2709 Unfreeze(this, false);
2711 else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING)
2713 STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1);
2714 SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this)));
2716 if (GetResource(this, RES_HEALTH) < 1)
2719 vehicles_exit(this.vehicle, VHEF_RELEASE);
2720 if(this.event_damage)
2721 this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0');
2723 else if (STAT(REVIVE_PROGRESS, this) <= 0)
2724 Unfreeze(this, false);
2729 if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle)
2730 if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this))
2732 FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO,
2736 if(!it.team || SAME_TEAM(this, it))
2737 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER);
2738 else if(autocvar_g_vehicles_steal)
2739 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL);
2741 else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this))
2743 Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER);
2747 this.last_vehiclecheck = time + 1;
2752 // formerly PostThink code
2753 if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0))
2754 if (IS_REAL_CLIENT(this))
2755 if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators)
2756 if (!intermission_running) // NextLevel() kills all centerprints after setting this true
2758 int totalClients = 0;
2759 if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0)
2761 // maxidle disabled in local matches by not counting clients (totalClients 0)
2762 if (server_is_dedicated)
2764 FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots,
2768 if (maxclients - totalClients > autocvar_sv_maxidle_slots)
2772 else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2774 FOREACH_CLIENT(IS_REAL_CLIENT(it),
2780 if (totalClients < autocvar_sv_maxidle_minplayers)
2782 // idle kick disabled
2783 CS(this).parm_idlesince = time;
2785 else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10
2787 if (CS(this).idlekick_lasttimeleft)
2789 CS(this).idlekick_lasttimeleft = 0;
2790 Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_IDLING);
2795 float maxidle_time = autocvar_sv_maxidle;
2796 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2797 maxidle_time = autocvar_sv_maxidle_playertospectator;
2798 float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince));
2799 float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10
2800 if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft)
2802 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2803 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft);
2805 Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft);
2807 if (timeleft <= 0) {
2808 if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)
2810 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time);
2811 PutObserverInServer(this, true, true);
2815 if (dropclient_schedule(this))
2816 Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time);
2820 else if (timeleft <= countdown_time) {
2821 if (timeleft != CS(this).idlekick_lasttimeleft)
2822 play2(this, SND(TALK2));
2823 CS(this).idlekick_lasttimeleft = timeleft;
2832 this.solid = SOLID_NOT;
2833 this.takedamage = DAMAGE_NO;
2834 set_movetype(this, MOVETYPE_NONE);
2835 CS(this).teamkill_complain = 0;
2836 CS(this).teamkill_soundtime = 0;
2837 CS(this).teamkill_soundsource = NULL;
2840 if (this.waypointsprite_attachedforcarrier) {
2841 float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x;
2842 WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp);
2846 // hack to copy the button fields from the client entity to the Client State
2847 void PM_UpdateButtons(entity this, entity store)
2850 store.impulse = this.impulse;
2853 bool typing = this.buttonchat || this.button12;
2855 store.button0 = (typing) ? 0 : this.button0;
2857 store.button2 = (typing) ? 0 : this.button2;
2858 store.button3 = (typing) ? 0 : this.button3;
2859 store.button4 = this.button4;
2860 store.button5 = (typing) ? 0 : this.button5;
2861 store.button6 = this.button6;
2862 store.button7 = this.button7;
2863 store.button8 = this.button8;
2864 store.button9 = this.button9;
2865 store.button10 = this.button10;
2866 store.button11 = this.button11;
2867 store.button12 = this.button12;
2868 store.button13 = this.button13;
2869 store.button14 = this.button14;
2870 store.button15 = this.button15;
2871 store.button16 = this.button16;
2872 store.buttonuse = this.buttonuse;
2873 store.buttonchat = this.buttonchat;
2875 store.cursor_active = this.cursor_active;
2876 store.cursor_screen = this.cursor_screen;
2877 store.cursor_trace_start = this.cursor_trace_start;
2878 store.cursor_trace_endpos = this.cursor_trace_endpos;
2879 store.cursor_trace_ent = this.cursor_trace_ent;
2881 store.ping = this.ping;
2882 store.ping_packetloss = this.ping_packetloss;
2883 store.ping_movementloss = this.ping_movementloss;
2885 store.v_angle = this.v_angle;
2886 store.movement = this.movement;
2889 NET_HANDLE(fpsreport, bool)
2891 int fps = ReadShort();
2892 PlayerScore_Set(sender, SP_FPS, fps);