From: Mario Date: Sun, 12 Jun 2016 10:43:18 +0000 (+1000) Subject: Merge branch 'master' into Mario/showspecs X-Git-Tag: xonotic-v0.8.2~750^2~7 X-Git-Url: http://de.git.xonotic.org/?p=xonotic%2Fxonotic-data.pk3dir.git;a=commitdiff_plain;h=eac60648c4017e495060dd3ba9e50ac4bad5000a Merge branch 'master' into Mario/showspecs # Conflicts: # qcsrc/client/hud.qc # qcsrc/client/main.qc # qcsrc/server/cl_client.qc --- eac60648c4017e495060dd3ba9e50ac4bad5000a diff --cc qcsrc/client/hud/panel/infomessages.qc index 0000000000,a197963e75..fe16ef1479 mode 000000,100644..100644 --- a/qcsrc/client/hud/panel/infomessages.qc +++ b/qcsrc/client/hud/panel/infomessages.qc @@@ -1,0 -1,191 +1,209 @@@ + #include "infomessages.qh" + + #include + #include + + // Info messages panel (#14) + + #define drawInfoMessage(s) MACRO_BEGIN { \ + if(autocvar_hud_panel_infomessages_flip) \ + o.x = pos.x + mySize.x - stringwidth(s, true, fontsize); \ + drawcolorcodedstring(o, s, fontsize, a, DRAWFLAG_NORMAL); \ + o.y += fontsize.y; \ + } MACRO_END + void HUD_InfoMessages() + { + if(!autocvar__hud_configure) + { + if(!autocvar_hud_panel_infomessages) return; + } + + HUD_Panel_UpdateCvars(); + vector pos, mySize; + pos = panel_pos; + mySize = panel_size; + + if (autocvar_hud_panel_infomessages_dynamichud) + HUD_Scale_Enable(); + else + HUD_Scale_Disable(); + HUD_Panel_DrawBg(1); + if(panel_bg_padding) + { + pos += '1 1 0' * panel_bg_padding; + mySize -= '2 2 0' * panel_bg_padding; + } + + // always force 5:1 aspect + vector newSize = '0 0 0'; + if(mySize.x/mySize.y > 5) + { + newSize.x = 5 * mySize.y; + newSize.y = mySize.y; + + pos.x = pos.x + (mySize.x - newSize.x) / 2; + } + else + { + newSize.y = 1/5 * mySize.x; + newSize.x = mySize.x; + + pos.y = pos.y + (mySize.y - newSize.y) / 2; + } + + mySize = newSize; + entity tm; + vector o; + o = pos; + + vector fontsize; + fontsize = '0.20 0.20 0' * mySize.y; + + float a; + a = panel_fg_alpha; + + string s; + if(!autocvar__hud_configure) + { + if(spectatee_status) + { + a = 1; + if(spectatee_status == -1) + s = _("^1Observing"); + else + s = sprintf(_("^1Spectating: ^7%s"), entcs_GetName(current_player)); + drawInfoMessage(s); + + if(spectatee_status == -1) + s = sprintf(_("^1Press ^3%s^1 to spectate"), getcommandkey("primary fire", "+fire")); + else + s = sprintf(_("^1Press ^3%s^1 or ^3%s^1 for next or previous player"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev")); + drawInfoMessage(s); + + if(spectatee_status == -1) + s = sprintf(_("^1Use ^3%s^1 or ^3%s^1 to change the speed"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev")); + else + s = sprintf(_("^1Press ^3%s^1 to observe"), getcommandkey("secondary fire", "+fire2")); + drawInfoMessage(s); + + s = sprintf(_("^1Press ^3%s^1 for gamemode info"), getcommandkey("server info", "+show_info")); + drawInfoMessage(s); + + if(gametype == MAPINFO_TYPE_LMS) + { + entity sk; + sk = playerslots[player_localnum]; + if(sk.(scores[ps_primary]) >= 666) + s = _("^1Match has already begun"); + else if(sk.(scores[ps_primary]) > 0) + s = _("^1You have no more lives left"); + else + s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump")); + } + else + s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump")); + drawInfoMessage(s); + } + + if (time < STAT(GAMESTARTTIME)) + { + //we need to ceil, otherwise the countdown would be off by .5 when using round() + float countdown = ceil(STAT(GAMESTARTTIME) - time); + s = sprintf(_("^1Game starts in ^3%d^1 seconds"), countdown); + drawInfoMessage(s); + } + + if(warmup_stage) + { + s = _("^2Currently in ^1warmup^2 stage!"); + drawInfoMessage(s); + } + ++ if(autocvar_cl_showspectators) ++ if(num_spectators) ++ //if(spectatee_status != -1) ++ { ++ s = ((spectatee_status) ? _("^1Spectating this player:") : _("^1Spectating you:")); ++ //drawInfoMessage(s) ++ int limit = min(num_spectators, MAX_SPECTATORS); ++ for(int i = 0; i < limit; ++i) ++ { ++ float slot = spectatorlist[i]; ++ if(i == 0) ++ s = strcat(s, " ^3", entcs_GetName(slot)); ++ else ++ s = strcat("^3", entcs_GetName(slot)); ++ drawInfoMessage(s); ++ } ++ } ++ + string blinkcolor; + if(time % 1 >= 0.5) + blinkcolor = "^1"; + else + blinkcolor = "^3"; + + if(ready_waiting && !spectatee_status) + { + if(ready_waiting_for_me) + { + if(warmup_stage) + s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor); + else + s = sprintf(_("%sPress ^3%s%s once you are ready"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor); + } + else + { + if(warmup_stage) + s = _("^2Waiting for others to ready up to end warmup..."); + else + s = _("^2Waiting for others to ready up..."); + } + drawInfoMessage(s); + } + else if(warmup_stage && !spectatee_status) + { + s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey("ready", "ready")); + drawInfoMessage(s); + } + + if(teamplay && !spectatee_status && gametype != MAPINFO_TYPE_CA && teamnagger) + { + float ts_min = 0, ts_max = 0; + tm = teams.sort_next; + if (tm) + { + for (; tm.sort_next; tm = tm.sort_next) + { + if(!tm.team_size || tm.team == NUM_SPECTATOR) + continue; + if(!ts_min) ts_min = tm.team_size; + else ts_min = min(ts_min, tm.team_size); + if(!ts_max) ts_max = tm.team_size; + else ts_max = max(ts_max, tm.team_size); + } + if ((ts_max - ts_min) > 1) + { + s = strcat(blinkcolor, _("Teamnumbers are unbalanced!")); + tm = GetTeam(myteam, false); + if (tm) + if (tm.team != NUM_SPECTATOR) + if (tm.team_size == ts_max) + s = strcat(s, sprintf(_(" Press ^3%s%s to adjust"), getcommandkey("team menu", "menu_showteamselect"), blinkcolor)); + drawInfoMessage(s); + } + } + } + } + else + { + s = _("^7Press ^3ESC ^7to show HUD options."); + drawInfoMessage(s); + s = _("^3Doubleclick ^7a panel for panel-specific options."); + drawInfoMessage(s); + s = _("^3CTRL ^7to disable collision testing, ^3SHIFT ^7and"); + drawInfoMessage(s); + s = _("^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments."); + drawInfoMessage(s); + } + } diff --cc qcsrc/client/main.qc index 71198ebc3e,b6cca28b14..7025b5df15 --- a/qcsrc/client/main.qc +++ b/qcsrc/client/main.qc @@@ -515,22 -516,8 +516,24 @@@ NET_HANDLE(ENT_CLIENT_CLIENTDATA, bool else angles_held_status = 0; + if(f & 16) + { + num_spectators = ReadByte(); + + float i, slot; + + for(i = 0; i < MAX_SPECTATORS; ++i) + spectatorlist[i] = 0; // reset list first + + for(i = 0; i < num_spectators; ++i) + { + slot = ReadByte(); + spectatorlist[i] = slot - 1; + } + } + + return = true; + if(newspectatee_status != spectatee_status) { // clear race stuff diff --cc qcsrc/client/main.qh index bf0bb74b0c,8601d26b58..65aad3e9f6 --- a/qcsrc/client/main.qh +++ b/qcsrc/client/main.qh @@@ -138,20 -136,9 +136,14 @@@ const int HOOK_END = 2 .float ping, ping_packetloss, ping_movementloss; - float g_balance_mortar_bouncefactor; - float g_balance_mortar_bouncestop; - float g_balance_electro_secondary_bouncefactor; - float g_balance_electro_secondary_bouncestop; float g_trueaim_minrange; - entity entcs_receiver[255]; // 255 is the engine limit on maxclients - float hud; float view_quality; + +int num_spectators; +const int MAX_SPECTATORS = 7; +int spectatorlist[MAX_SPECTATORS]; + int framecount; - #endif + .float health; diff --cc qcsrc/server/cl_client.qc index fe12ffc039,968a9899f2..1f92d24442 --- a/qcsrc/server/cl_client.qc +++ b/qcsrc/server/cl_client.qc @@@ -21,80 -51,52 +51,77 @@@ #include "../common/monsters/sv_monsters.qh" - #include "../warpzonelib/server.qh" + #include "../lib/warpzone/server.qh" + + STATIC_METHOD(Client, Add, void(Client this, int _team)) + { + WITHSELF(this, ClientConnect()); + TRANSMUTE(Player, this); + this.frame = 12; // 7 + this.team = _team; + WITHSELF(this, PutClientInServer()); + } + + void PutObserverInServer(entity this); + void ClientDisconnect(); - float c1, c2, c3, c4; + STATIC_METHOD(Client, Remove, void(Client this)) + { + TRANSMUTE(Observer, this); + WITHSELF(this, PutClientInServer()); + WITHSELF(this, ClientDisconnect()); + } void send_CSQC_teamnagger() { - WriteByte(MSG_BROADCAST, SVC_TEMPENTITY); - WriteByte(MSG_BROADCAST, TE_CSQC_TEAMNAGGER); + WriteHeader(MSG_BROADCAST, TE_CSQC_TEAMNAGGER); } +int CountSpectators(entity player, entity to) +{ + if(!player) { return 0; } // not sure how, but best to be safe + - entity head; - float spec_count = 0; - FOR_EACH_REALCLIENT(head) ++ int spec_count = 0; ++ ++ FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, + { - if(IS_SPEC(head)) - if(head != to) - if(head.enemy == player) - spec_count += 1; - } ++ spec_count++; ++ }); + + return spec_count; +} + +void WriteSpectators(entity player, entity to) +{ + if(!player) { return; } // not sure how, but best to be safe + - entity head; - FOR_EACH_REALCLIENT(head) ++ FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, + { - if(IS_SPEC(head)) - if(head != to) - if(head.enemy == player) - WriteByte(MSG_ENTITY, num_for_edict(head)); - } ++ WriteByte(MSG_ENTITY, num_for_edict(it)); ++ }); +} + - bool ClientData_Send(entity to, int sf) + bool ClientData_Send(entity this, entity to, int sf) { - if(to != self.owner) - { - error("wtf"); - return false; - } + assert(to == this.owner, return false); - entity e; - - e = to; - if(IS_SPEC(to)) - e = to.enemy; + entity e = to; + if (IS_SPEC(e)) e = e.enemy; sf = 0; + if (e.race_completed) sf |= 1; // forced scoreboard + if (to.spectatee_status) sf |= 2; // spectator ent number follows + if (e.zoomstate) sf |= 4; // zoomed + if (e.porto_v_angle_held) sf |= 8; // angles held ++ sf |= 16; // always check spectators - if(e.race_completed) - sf |= 1; // forced scoreboard - if(to.spectatee_status) - sf |= 2; // spectator ent number follows - if(e.zoomstate) - sf |= 4; // zoomed - if(e.porto_v_angle_held) - sf |= 8; // angles held - // always check spectators - sf |= 16; // spectator handling? - - WriteByte(MSG_ENTITY, ENT_CLIENT_CLIENTDATA); + WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA); WriteByte(MSG_ENTITY, sf); - if(sf & 2) + if (sf & 2) + { WriteByte(MSG_ENTITY, to.spectatee_status); - - if(sf & 8) + } + if (sf & 8) { WriteAngle(MSG_ENTITY, e.v_angle.x); WriteAngle(MSG_ENTITY, e.v_angle.y); @@@ -189,162 -176,166 +209,167 @@@ string CheckPlayerModel(string plyermod void setplayermodel(entity e, string modelname) { precache_model(modelname); - setmodel(e, modelname); - player_setupanimsformodel(); - UpdatePlayerSounds(); + _setmodel(e, modelname); + player_setupanimsformodel(e); + if(!autocvar_g_debug_globalsounds) + UpdatePlayerSounds(e); } - /* - ============= - PutObserverInServer - - putting a client as observer in the server - ============= - */ - void FixPlayermodel(); - void PutObserverInServer (void) + void FixPlayermodel(entity player); + /** putting a client as observer in the server */ + void PutObserverInServer(entity this) { - entity spot; - - SetSpectator(self, world); - - self.hud = HUD_NORMAL; - - if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); } + bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this); + PlayerState_detach(this); - spot = SelectSpawnPoint (true); - if(!spot) - error("No spawnpoints for observers?!?\n"); - RemoveGrapplingHook(self); // Wazat's Grappling Hook - - if(IS_REAL_CLIENT(self)) - { - msg_entity = self; - WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, self); - } - - self.frags = FRAGS_SPECTATOR; - - MUTATOR_CALLHOOK(MakePlayerObserver); + if (IS_PLAYER(this) && this.health >= 1) { + // despawn effect + Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); + } - Portal_ClearAll(self); + { + entity spot = SelectSpawnPoint(this, true); + if (!spot) LOG_FATAL("No spawnpoints for observers?!?"); + this.angles = spot.angles; + this.angles_z = 0; + this.fixangle = true; + // offset it so that the spectator spawns higher off the ground, looks better this way + setorigin(this, spot.origin + STAT(PL_VIEW_OFS, NULL)); + this.prevorigin = this.origin; + if (IS_REAL_CLIENT(this)) + { + msg_entity = this; + WriteByte(MSG_ONE, SVC_SETVIEW); + WriteEntity(MSG_ONE, this); + } + // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY + // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS" + if(!autocvar_g_debug_globalsounds) + { + // needed for player sounds + this.model = ""; + FixPlayermodel(this); + } + setmodel(this, MDL_Null); + setsize(this, STAT(PL_CROUCH_MIN, NULL), STAT(PL_CROUCH_MAX, NULL)); + this.view_ofs = '0 0 0'; + } - Unfreeze(self); + RemoveGrapplingHook(this); + Portal_ClearAll(this); + Unfreeze(this); ++ SetSpectatee(this, world); - if(self.alivetime) + if (this.alivetime) { - if(!warmup_stage) - PS_GR_P_ADDVAL(self, PLAYERSTATS_ALIVETIME, time - self.alivetime); - self.alivetime = 0; + if (!warmup_stage) + PS_GR_P_ADDVAL(this, PLAYERSTATS_ALIVETIME, time - this.alivetime); + this.alivetime = 0; } - if(self.vehicle) - vehicles_exit(VHEF_RELESE); + if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); - WaypointSprite_PlayerDead(); + WaypointSprite_PlayerDead(this); - if (!g_ca) // don't reset teams when moving a ca player to the spectators - self.team = -1; // move this as it is needed to log the player spectating in eventlog + if (mutator_returnvalue) { + // mutator prevents resetting teams+score + } else { + this.team = -1; // move this as it is needed to log the player spectating in eventlog + this.frags = FRAGS_SPECTATOR; + PlayerScore_Clear(this); // clear scores when needed + } - if(self.killcount != -666) + if (this.killcount != FRAGS_SPECTATOR) { - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_SPECTATE, self.netname); - if(autocvar_g_chat_nospectators == 1 || (cvar("g_warmup") && !(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2)) - Send_Notification(NOTIF_ONE_ONLY, self, MSG_INFO, INFO_CHAT_NOSPECTATORS); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_SPECTATE, this.netname); + if(!intermission_running) + if(autocvar_g_chat_nospectators == 1 || (!(warmup_stage || gameover) && autocvar_g_chat_nospectators == 2)) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS); - if(self.just_joined == false) { - LogTeamchange(self.playerid, -1, 4); + if(this.just_joined == false) { + LogTeamchange(this.playerid, -1, 4); } else - self.just_joined = false; - } - - PlayerScore_Clear(self); // clear scores when needed - - accuracy_resend(self); - - self.spectatortime = time; - - self.classname = "observer"; - self.iscreature = false; - self.teleportable = TELEPORT_SIMPLE; - self.damagedbycontents = false; - self.health = -666; - self.takedamage = DAMAGE_NO; - self.solid = SOLID_NOT; - self.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink - self.flags = FL_CLIENT | FL_NOTARGET; - self.armorvalue = 666; - self.effects = 0; - self.armorvalue = autocvar_g_balance_armor_start; - self.pauserotarmor_finished = 0; - self.pauserothealth_finished = 0; - self.pauseregen_finished = 0; - self.damageforcescale = 0; - self.death_time = 0; - self.respawn_flags = 0; - self.respawn_time = 0; - self.stat_respawn_time = 0; - self.alpha = 0; - self.scale = 0; - self.fade_time = 0; - self.pain_frame = 0; - self.pain_finished = 0; - self.strength_finished = 0; - self.invincible_finished = 0; - self.superweapons_finished = 0; - self.pushltime = 0; - self.istypefrag = 0; - self.think = func_null; - self.nextthink = 0; - self.hook_time = 0; - self.deadflag = DEAD_NO; - self.angles = spot.angles; - self.angles_z = 0; - self.fixangle = true; - self.crouch = false; - self.revival_time = 0; - - setorigin (self, (spot.origin + PL_VIEW_OFS)); // offset it so that the spectator spawns higher off the ground, looks better this way - self.prevorigin = self.origin; - self.items = 0; - self.weapons = '0 0 0'; - self.model = ""; - FixPlayermodel(); - setmodel(self, "null"); - self.drawonlytoclient = self; - - setsize (self, PL_CROUCH_MIN, PL_CROUCH_MAX); // give the spectator some space between walls for MOVETYPE_FLY_WORLDONLY - self.view_ofs = '0 0 0'; // so that your view doesn't go into the ceiling with MOVETYPE_FLY_WORLDONLY, previously "PL_VIEW_OFS" - - self.weapon = 0; - self.weaponname = ""; - self.switchingweapon = 0; - self.weaponmodel = ""; - self.weaponentity = world; - self.exteriorweaponentity = world; - self.killcount = -666; - self.velocity = '0 0 0'; - self.avelocity = '0 0 0'; - self.punchangle = '0 0 0'; - self.punchvector = '0 0 0'; - self.oldvelocity = self.velocity; - self.fire_endtime = -1; - self.event_damage = func_null; + this.just_joined = false; + } + + accuracy_resend(this); + + this.spectatortime = time; + this.bot_attack = false; + this.hud = HUD_NORMAL; + TRANSMUTE(Observer, this); + this.iscreature = false; + this.teleportable = TELEPORT_SIMPLE; + this.damagedbycontents = false; + this.health = FRAGS_SPECTATOR; + this.takedamage = DAMAGE_NO; + this.solid = SOLID_NOT; + this.movetype = MOVETYPE_FLY_WORLDONLY; // user preference is controlled by playerprethink + this.flags = FL_CLIENT | FL_NOTARGET; + this.armorvalue = 666; + this.effects = 0; + this.armorvalue = autocvar_g_balance_armor_start; + this.pauserotarmor_finished = 0; + this.pauserothealth_finished = 0; + this.pauseregen_finished = 0; + this.damageforcescale = 0; + this.death_time = 0; + this.respawn_flags = 0; + this.respawn_time = 0; + this.stat_respawn_time = 0; + this.alpha = 0; + this.scale = 0; + this.fade_time = 0; + this.pain_frame = 0; + this.pain_finished = 0; + this.strength_finished = 0; + this.invincible_finished = 0; + this.superweapons_finished = 0; + this.pushltime = 0; + this.istypefrag = 0; + setthink(this, func_null); + this.nextthink = 0; + this.hook_time = 0; + this.deadflag = DEAD_NO; + this.crouch = false; + this.revival_time = 0; + + this.items = 0; + this.weapons = '0 0 0'; + this.drawonlytoclient = this; + + this.weaponname = ""; + this.weaponmodel = ""; + for (int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) + { + this.weaponentities[slot] = NULL; + } + this.exteriorweaponentity = NULL; + this.killcount = FRAGS_SPECTATOR; + this.velocity = '0 0 0'; + this.avelocity = '0 0 0'; + this.punchangle = '0 0 0'; + this.punchvector = '0 0 0'; + this.oldvelocity = this.velocity; + this.fire_endtime = -1; + this.event_damage = func_null; } - .float model_randomizer; - void FixPlayermodel() + int player_getspecies(entity this) { - string defaultmodel; - float defaultskin, chmdl, oldskin, n, i; - vector m1, m2; - - defaultmodel = ""; - defaultskin = 0; - chmdl = false; + get_model_parameters(this.model, this.skin); + int s = get_model_parameters_species; + get_model_parameters(string_null, 0); + if (s < 0) return SPECIES_HUMAN; + return s; + } - if(autocvar_sv_defaultcharacter == 1) + .float model_randomizer; + void FixPlayermodel(entity player) + { + string defaultmodel = ""; + int defaultskin = 0; + if(autocvar_sv_defaultcharacter) { if(teamplay) { @@@ -1310,49 -1173,29 +1207,31 @@@ Called when a client disconnects from t */ .entity chatbubbleentity; void ReadyCount(); - void ClientDisconnect (void) - { - if(self.vehicle) - vehicles_exit(VHEF_RELESE); - - if (!IS_CLIENT(self)) - { - print("Warning: ClientDisconnect without ClientConnect\n"); - return; - } - - PlayerStats_GameReport_FinalizePlayer(self); - - SetSpectator(self, world); - - if(IS_PLAYER(self)) { pointparticles(particleeffectnum("spawn_event_neutral"), self.origin, '0 0 0', 1); } + void ClientDisconnect() + {ENGINE_EVENT(); + assert(IS_CLIENT(this), return); - CheatShutdownClient(); + PlayerStats_GameReport_FinalizePlayer(this); + if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); + if (this.active_minigame) part_minigame(this); + if (IS_PLAYER(this)) Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); - W_HitPlotClose(self); + if (autocvar_sv_eventlog) + GameLogEcho(strcat(":part:", ftos(this.playerid))); - anticheat_report(); - anticheat_shutdown(); + Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_DISCONNECT, this.netname); - playerdemo_shutdown(); ++ SetSpectatee(this, NULL); + - bot_clientdisconnect(); + MUTATOR_CALLHOOK(ClientDisconnect, this); - if(self.entcs) - detach_entcs(); + ClientState_detach(this); - if(autocvar_sv_eventlog) - GameLogEcho(strcat(":part:", ftos(self.playerid))); - - Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_QUIT_DISCONNECT, self.netname); - - MUTATOR_CALLHOOK(ClientDisconnect); + Portal_ClearAll(this); - Portal_ClearAll(self); + Unfreeze(this); - Unfreeze(self); - - RemoveGrapplingHook(self); + RemoveGrapplingHook(this); // Here, everything has been done that requires this player to be a client. @@@ -1839,38 -1649,35 +1685,37 @@@ void SpectateCopy(entity this, entity s } } - float SpectateUpdate() + bool SpectateUpdate(entity this) { - if(!self.enemy) - return 0; + if(!this.enemy) + return false; - if(!IS_PLAYER(self.enemy) || self == self.enemy) + if(!IS_PLAYER(this.enemy) || this == this.enemy) { - SetSpectator(self, world); - return 0; + SetSpectatee(this, NULL); + return false; } - SpectateCopy(self.enemy); + SpectateCopy(this, this.enemy); - return 1; + return true; } - float SpectateSet() + bool SpectateSet(entity this) { - if(!IS_PLAYER(self.enemy)) + if(!IS_PLAYER(this.enemy)) return false; - ClientData_Touch(self.enemy); ++ ClientData_Touch(this.enemy); + - msg_entity = self; + msg_entity = this; WriteByte(MSG_ONE, SVC_SETVIEW); - WriteEntity(MSG_ONE, self.enemy); - //stuffcmd(self, "set viewsize $tmpviewsize \n"); - self.movetype = MOVETYPE_NONE; - accuracy_resend(self); + WriteEntity(MSG_ONE, this.enemy); + this.movetype = MOVETYPE_NONE; + accuracy_resend(this); - if(!SpectateUpdate()) - PutObserverInServer(); + if(!SpectateUpdate(this)) + PutObserverInServer(this); return true; } @@@ -1884,68 -1691,37 +1729,40 @@@ void SetSpectatee(entity this, entity s // WEAPONTODO // these are required to fix the spectator bug with arc if(old_spectatee && old_spectatee.arc_beam) { old_spectatee.arc_beam.SendFlags |= ARC_SF_SETTINGS; } - if(player.enemy && player.enemy.arc_beam) { player.enemy.arc_beam.SendFlags |= ARC_SF_SETTINGS; } + if(this.enemy && this.enemy.arc_beam) { this.enemy.arc_beam.SendFlags |= ARC_SF_SETTINGS; } + + // needed to update spectator list + if(old_spectatee) { ClientData_Touch(old_spectatee); } } - float Spectate(entity pl) - { - if(g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) - if(DIFF_TEAM(pl, self)) - return 0; - - SetSpectator(self, pl); - return SpectateSet(); - } - - // Returns next available player to spectate if g_ca_spectate_enemies == 0 - entity CA_SpectateNext(entity start) + bool Spectate(entity this, entity pl) { - if(SAME_TEAM(start, self)) { return start; } - - other = start; - // continue from current player - while(other && DIFF_TEAM(other, self)) - other = find(other, classname, "player"); - - if (!other) - { - // restart from begining - other = find(other, classname, "player"); - while(other && DIFF_TEAM(other, self)) - other = find(other, classname, "player"); - } + if(MUTATOR_CALLHOOK(SpectateSet, this, pl)) + return false; + pl = M_ARGV(1, entity); - return other; + SetSpectatee(this, pl); + return SpectateSet(this); } - float SpectateNext() + bool SpectateNext(entity this) { - other = find(self.enemy, classname, "player"); + other = find(this.enemy, classname, STR_PLAYER); - if (g_ca && !autocvar_g_ca_spectate_enemies && self.caplayer) - { - // CA and ca players when spectating enemies is forbidden - other = CA_SpectateNext(other); - } - else - { - // other modes and ca spectators or spectating enemies is allowed - if (!other) - other = find(other, classname, "player"); - } + if (MUTATOR_CALLHOOK(SpectateNext, this, other)) + other = M_ARGV(1, entity); + else if (!other) + other = find(other, classname, STR_PLAYER); - if(other) { SetSpectator(self, other); } + if(other) { SetSpectatee(this, other); } - return SpectateSet(); + return SpectateSet(this); } - float SpectatePrev() + bool SpectatePrev(entity this) { // NOTE: chain order is from the highest to the lower entnum (unlike find) - other = findchain(classname, "player"); + other = findchain(classname, STR_PLAYER); if (!other) // no player return false; @@@ -2006,33 -1780,30 +1821,32 @@@ void ShowRespawnCountdown(entity this } } - void LeaveSpectatorMode() + void LeaveSpectatorMode(entity this) { - if(self.caplayer) + if(this.caplayer) return; - if(nJoinAllowed(self)) + if(nJoinAllowed(this, this)) { - if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (self.wasplayer && autocvar_g_changeteam_banned) || self.team_forced > 0) + if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || (this.wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0) { - self.classname = "player"; - nades_RemoveBonus(self); + TRANSMUTE(Player, this); - SetSpectator(self, world); ++ SetSpectatee(self, world); + if(autocvar_g_campaign || autocvar_g_balance_teams) - { JoinBestTeam(self, false, true); } + { JoinBestTeam(this, false, true); } if(autocvar_g_campaign) - { campaign_bots_may_start = 1; } + { campaign_bots_may_start = true; } - Kill_Notification(NOTIF_ONE_ONLY, self, MSG_CENTER_CPID, CPID_PREVENT_JOIN); + Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_PREVENT_JOIN); - PutClientInServer(); + WITHSELF(this, PutClientInServer()); - if(IS_PLAYER(self)) { Send_Notification(NOTIF_ALL, world, MSG_INFO, INFO_JOIN_PLAY, self.netname); } + if(IS_PLAYER(this)) { Send_Notification(NOTIF_ALL, world, MSG_INFO, ((teamplay && this.team != -1) ? APP_TEAM_ENT(this, INFO_JOIN_PLAY_TEAM) : INFO_JOIN_PLAY), this.netname); } } else - stuffcmd(self, "menu_showteamselect\n"); + stuffcmd(this, "menu_showteamselect\n"); } else {