X-Git-Url: https://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fclient.qc;h=dadc04b5bae7cda9766655e88f721ec846bd684f;hb=8ffdd1c022bdd0eec44ba42acfd8ac475f21d69c;hp=2c687286b7e503ee6b873708d21173bbeaed97eb;hpb=0409f3fe91e759cbe3b885b5c543411f02844f21;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 2c687286b..dadc04b5b 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -1,78 +1,83 @@ #include "client.qh" -#include -#include +#include +#include #include -#include "anticheat.qh" -#include "impulse.qh" -#include "player.qh" -#include "ipban.qh" -#include "miscfunctions.qh" -#include "portals.qh" -#include "teamplay.qh" -#include "spawnpoints.qh" -#include "resources.qh" -#include "g_damage.qh" -#include "handicap.qh" -#include "g_hook.qh" -#include "command/common.qh" -#include "command/vote.qh" -#include "clientkill.qh" -#include "cheats.qh" -#include "g_world.qh" -#include "race.qh" -#include "antilag.qh" -#include "campaign.qh" -#include "command/common.qh" -#include "scores_rules.qh" -#include "weapons/common.qh" - -#include "bot/api.qh" - -#include "../common/ent_cs.qh" -#include "../common/wepent.qh" -#include - -#include "compat/quake3.qh" - #include - -#include "../common/mapobjects/func/conveyor.qh" -#include "../common/mapobjects/teleporters.qh" -#include "../common/mapobjects/target/spawnpoint.qh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include #include - -#include "../common/vehicles/all.qh" - -#include "weapons/hitplot.qh" -#include "weapons/weaponsystem.qh" - -#include "../common/net_notice.qh" -#include "../common/net_linked.qh" -#include "../common/physics/player.qh" - -#include - -#include "../common/items/_mod.qh" - -#include "../common/mutators/mutator/waypoints/all.qh" -#include "../common/mutators/mutator/instagib/sv_instagib.qh" -#include - -#include "../common/mapobjects/subs.qh" -#include "../common/mapobjects/triggers.qh" -#include "../common/mapobjects/trigger/secret.qh" - -#include "../common/minigames/sv_minigames.qh" - -#include "../common/items/inventory.qh" - -#include "../common/monsters/sv_monsters.qh" - -#include "../lib/warpzone/server.qh" - +#include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include STATIC_METHOD(Client, Add, void(Client this, int _team)) { @@ -112,9 +117,13 @@ void WriteSpectators(entity player, entity to) { if(!player) { return; } // not sure how, but best to be safe + int spec_count = 0; FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_SPEC(it) && it != to && it.enemy == player, { + if(spec_count >= MAX_SPECTATORS) + break; WriteByte(MSG_ENTITY, num_for_edict(it)); + ++spec_count; }); } @@ -192,19 +201,19 @@ string CheckPlayerModel(string plyermodel) { FallbackPlayerModel = strzone(cvar_defstring("_cl_playermodel")); } // only in right path - if( substring(plyermodel,0,14) != "models/player/") + if(substring(plyermodel, 0, 14) != "models/player/") return FallbackPlayerModel; // only good file extensions - if(substring(plyermodel,-4,4) != ".zym") - if(substring(plyermodel,-4,4) != ".dpm") - if(substring(plyermodel,-4,4) != ".iqm") - if(substring(plyermodel,-4,4) != ".md3") - if(substring(plyermodel,-4,4) != ".psk") + if(substring(plyermodel, -4, 4) != ".iqm" + && substring(plyermodel, -4, 4) != ".zym" + && substring(plyermodel, -4, 4) != ".dpm" + && substring(plyermodel, -4, 4) != ".md3" + && substring(plyermodel, -4, 4) != ".psk") + { return FallbackPlayerModel; + } // forbid the LOD models - if(substring(plyermodel, -9,5) == "_lod1") - return FallbackPlayerModel; - if(substring(plyermodel, -9,5) == "_lod2") + if(substring(plyermodel, -9, 5) == "_lod1" || substring(plyermodel, -9, 5) == "_lod2") return FallbackPlayerModel; if(plyermodel != strtolower(plyermodel)) return FallbackPlayerModel; @@ -229,7 +238,7 @@ void setplayermodel(entity e, string modelname) /** putting a client as observer in the server */ void PutObserverInServer(entity this) { - bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this); + bool mutator_returnvalue = MUTATOR_CALLHOOK(MakePlayerObserver, this); PlayerState_detach(this); if (IS_PLAYER(this)) @@ -246,35 +255,34 @@ void PutObserverInServer(entity this) if (vote_called) { VoteCount(false); } ReadyCount(); } - } + entcs_update_players(this); + } - { - entity spot = SelectSpawnPoint(this, true); - if (!spot) LOG_FATAL("No spawnpoints for observers?!?"); - this.angles = vec2(spot.angles); - 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, this)); - 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, this), STAT(PL_CROUCH_MAX, this)); - this.view_ofs = '0 0 0'; - } + entity spot = SelectSpawnPoint(this, true); + if (!spot) LOG_FATAL("No spawnpoints for observers?!?"); + this.angles = vec2(spot.angles); + 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, this)); + 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, this), STAT(PL_CROUCH_MAX, this)); + this.view_ofs = '0 0 0'; - RemoveGrapplingHooks(this); + RemoveGrapplingHooks(this); Portal_ClearAll(this); Unfreeze(this, false); SetSpectatee(this, NULL); @@ -290,12 +298,8 @@ void PutObserverInServer(entity this) WaypointSprite_PlayerDead(this); - if (CS(this).killcount != FRAGS_SPECTATOR) - { - if(!game_stopped) - if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2)) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS); - } + if (CS(this).killcount != FRAGS_SPECTATOR && !game_stopped && CHAT_NOSPECTATORS()) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS); accuracy_resend(this); @@ -306,7 +310,7 @@ void PutObserverInServer(entity this) if(this.monster_attack) IL_REMOVE(g_monster_targets, this); this.monster_attack = false; - STAT(HUD, this) = HUD_NORMAL; + STAT(HUD, this) = HUD_NORMAL; TRANSMUTE(Observer, this); this.iscreature = false; this.teleportable = TELEPORT_SIMPLE; @@ -332,12 +336,8 @@ void PutObserverInServer(entity this) this.alpha = 0; this.scale = 0; this.fade_time = 0; - this.pain_frame = 0; this.pain_finished = 0; - STAT(STRENGTH_FINISHED, this) = 0; - STAT(INVINCIBLE_FINISHED, this) = 0; - STAT(SUPERWEAPONS_FINISHED, this) = 0; - this.air_finished = 0; + STAT(AIR_FINISHED, this) = 0; //this.dphitcontentsmask = 0; this.dphitcontentsmask = DPCONTENTS_SOLID; if (autocvar_g_playerclip_collisions) @@ -347,7 +347,7 @@ void PutObserverInServer(entity this) setthink(this, func_null); this.nextthink = 0; this.deadflag = DEAD_NO; - this.crouch = false; + UNSET_DUCKED(this); STAT(REVIVE_PROGRESS, this) = 0; this.revival_time = 0; this.draggable = drag_undraggable; @@ -372,7 +372,6 @@ void PutObserverInServer(entity this) this.punchangle = '0 0 0'; this.punchvector = '0 0 0'; this.oldvelocity = this.velocity; - this.fire_endtime = -1; this.event_damage = func_null; this.event_heal = func_null; @@ -394,6 +393,9 @@ void PutObserverInServer(entity this) SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); this.frags = FRAGS_SPECTATOR; } + + bot_relinkplayerlist(); + if (CS(this).just_joined) CS(this).just_joined = false; } @@ -589,11 +591,13 @@ void PutPlayerInServer(entity this) PS(this).dual_weapons = '0 0 0'; - STAT(SUPERWEAPONS_FINISHED, this) = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0; + if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) + StatusEffects_apply(STATUSEFFECT_Superweapons, this, time + autocvar_g_balance_superweapons_time, 0); this.items = start_items; - this.spawnshieldtime = time + autocvar_g_spawnshieldtime; + float shieldtime = time + autocvar_g_spawnshieldtime; + this.pauserotarmor_finished = time + autocvar_g_balance_pause_armor_rot_spawn; this.pauserothealth_finished = time + autocvar_g_balance_pause_health_rot_spawn; this.pauserotfuel_finished = time + autocvar_g_balance_pause_fuel_rot_spawn; @@ -601,12 +605,14 @@ void PutPlayerInServer(entity this) if (!sv_ready_restart_after_countdown && time < game_starttime) { float f = game_starttime - time; - this.spawnshieldtime += f; + shieldtime += f; this.pauserotarmor_finished += f; this.pauserothealth_finished += f; this.pauseregen_finished += f; } + StatusEffects_apply(STATUSEFFECT_SpawnShield, this, shieldtime, 0); + this.damageforcescale = autocvar_g_player_damageforcescale; this.death_time = 0; this.respawn_flags = 0; @@ -615,7 +621,6 @@ void PutPlayerInServer(entity this) bool q3dfcompat = autocvar_sv_q3defragcompat && autocvar_sv_q3defragcompat_changehitbox; this.scale = ((q3dfcompat) ? 0.9 : autocvar_sv_player_scale); this.fade_time = 0; - this.pain_frame = 0; this.pain_finished = 0; this.pushltime = 0; setthink(this, func_null); // players have no think function @@ -638,17 +643,10 @@ void PutPlayerInServer(entity this) this.punchangle = '0 0 0'; this.punchvector = '0 0 0'; - STAT(STRENGTH_FINISHED, this) = 0; - STAT(INVINCIBLE_FINISHED, this) = 0; - this.fire_endtime = -1; STAT(REVIVE_PROGRESS, this) = 0; this.revival_time = 0; - // TODO: we can't set these in the PlayerSpawn hook since the target code is called before it! - STAT(BUFFS, this) = 0; - STAT(BUFF_TIME, this) = 0; - - this.air_finished = 0; + STAT(AIR_FINISHED, this) = 0; this.waterlevel = WATERLEVEL_NONE; this.watertype = CONTENT_EMPTY; @@ -676,7 +674,7 @@ void PutPlayerInServer(entity this) this.spawnpoint_targ = NULL; - this.crouch = false; + UNSET_DUCKED(this); this.view_ofs = STAT(PL_VIEW_OFS, this); setsize(this, STAT(PL_MIN, this), STAT(PL_MAX, this)); this.spawnorigin = spot.origin; @@ -690,6 +688,9 @@ void PutPlayerInServer(entity this) IL_REMOVE(g_swamped, this); this.swampslug = NULL; this.swamp_interval = 0; + if(this.ladder_entity) + IL_REMOVE(g_ladderents, this); + this.ladder_entity = NULL; IL_EACH(g_counters, it.realowner == this, { delete(it); @@ -747,6 +748,10 @@ void PutPlayerInServer(entity this) } }); + Unfreeze(this, false); + + MUTATOR_CALLHOOK(PlayerSpawn, this, spot); + { string s = spot.target; if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack @@ -756,10 +761,6 @@ void PutPlayerInServer(entity this) spot.target = s; } - Unfreeze(this, false); - - MUTATOR_CALLHOOK(PlayerSpawn, this, spot); - if (autocvar_spawn_debug) { sprint(this, strcat("spawnpoint origin: ", vtos(spot.origin), "\n")); @@ -769,21 +770,22 @@ void PutPlayerInServer(entity this) for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { .entity weaponentity = weaponentities[slot]; + entity w_ent = this.(weaponentity); if(slot == 0 || autocvar_g_weaponswitch_debug == 1) - this.(weaponentity).m_switchweapon = w_getbestweapon(this, weaponentity); + w_ent.m_switchweapon = w_getbestweapon(this, weaponentity); else - this.(weaponentity).m_switchweapon = WEP_Null; - this.(weaponentity).m_weapon = WEP_Null; - this.(weaponentity).weaponname = ""; - this.(weaponentity).m_switchingweapon = WEP_Null; - this.(weaponentity).cnt = -1; + w_ent.m_switchweapon = WEP_Null; + w_ent.m_weapon = WEP_Null; + w_ent.weaponname = ""; + w_ent.m_switchingweapon = WEP_Null; + w_ent.cnt = -1; } MUTATOR_CALLHOOK(PlayerWeaponSelect, this); if (CS(this).impulse) ImpulseCommands(this); - W_ResetGunAlign(this, CS(this).cvar_cl_gunalign); + W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign); for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { .entity weaponentity = weaponentities[slot]; @@ -822,6 +824,8 @@ void PutClientInServer(entity this) } else if (IS_PLAYER(this)) { PutPlayerInServer(this); } + + bot_relinkplayerlist(); } // TODO do we need all these fields, or should we stop autodetecting runtime @@ -852,7 +856,7 @@ void ClientInit_misc(entity this) WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[2])); WriteInt24_t(channel, compressShotOrigin(arc_shotorigin[3])); - if(sv_foginterval && world.fog != "") + if(autocvar_sv_foginterval && world.fog != "") WriteString(channel, world.fog); else WriteString(channel, ""); @@ -925,7 +929,7 @@ void DecodeLevelParms(entity this) CS(this).parm_idlesince = time; // whatever happens, allow 60 seconds of idling directly after connect for map loading - CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - sv_maxidle + 60); + CS(this).parm_idlesince = max(CS(this).parm_idlesince, time - autocvar_sv_maxidle + 60); MUTATOR_CALLHOOK(DecodeLevelParms); } @@ -1038,16 +1042,12 @@ string getwelcomemessage(entity this) modifications = strcat(modifications, ", Low gravity"); if(g_weapon_stay && !g_cts) modifications = strcat(modifications, ", Weapons stay"); - if(g_jetpack) + if(autocvar_g_jetpack) modifications = strcat(modifications, ", Jet pack"); - if(autocvar_g_powerups == 0) - modifications = strcat(modifications, ", No powerups"); - if(autocvar_g_powerups > 0) - modifications = strcat(modifications, ", Powerups"); modifications = substring(modifications, 2, strlen(modifications) - 2); string versionmessage = GetClientVersionMessage(this); - string s = strcat(versionmessage, "^8\n^8\nhost is ^9", autocvar_hostname, "^8\n"); + string s = strcat(versionmessage, "^8\n^8\nserver is ^9", autocvar_hostname, "^8\n"); s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n"); @@ -1077,8 +1077,6 @@ string getwelcomemessage(entity this) return s; } -bool autocvar_sv_qcphysics = true; // TODO this is for testing - remove when qcphysics work - /** ============= ClientConnect @@ -1093,9 +1091,6 @@ void ClientConnect(entity this) this.flags |= FL_CLIENT; assert(player_count >= 0, player_count = 0); -#ifdef WATERMARK - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_WATERMARK, WATERMARK); -#endif TRANSMUTE(Client, this); CS(this).version_nagtime = time + 10 + random() * 10; @@ -1116,7 +1111,7 @@ void ClientConnect(entity this) CS(this).allowed_timeouts = autocvar_sv_timeout_number; if (autocvar_sv_eventlog) - GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this, false))); + GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? GameLog_ProcessIP(this.netaddress) : "bot"), ":", playername(this.netname, this.team, false))); CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects @@ -1157,7 +1152,7 @@ void ClientConnect(entity this) stuffcmd(this, "cl_cmd settemp chase_active 1\n"); } - if (!sv_foginterval && world.fog != "") + if (!autocvar_sv_foginterval && world.fog != "") stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n")); if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2)) @@ -1201,6 +1196,8 @@ Called when a client disconnects from the server ============= */ .entity chatbubbleentity; +void player_powerups_remove_all(entity this); + void ClientDisconnect(entity this) { assert(IS_CLIENT(this), return); @@ -1218,10 +1215,10 @@ void ClientDisconnect(entity this) if(IS_SPEC(this)) SetSpectatee(this, NULL); - MUTATOR_CALLHOOK(ClientDisconnect, this); + MUTATOR_CALLHOOK(ClientDisconnect, this); strfree(CS(this).netname_previous); // needs to be before the CS entity is removed! - strfree(CS(this).weaponorder_byimpulse); + strfree(CS_CVAR(this).weaponorder_byimpulse); ClientState_detach(this); Portal_ClearAll(this); @@ -1253,6 +1250,8 @@ void ClientDisconnect(entity this) ReadyCount(); if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false); + player_powerups_remove_all(this); // stop powerup sound + ONREMOVE(this); } @@ -1304,6 +1303,91 @@ void UpdateChatBubble(entity this) } } +void calculate_player_respawn_time(entity this) +{ + if(MUTATOR_CALLHOOK(CalculateRespawnTime, this)) + return; + + float gametype_setting_tmp; + float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max); + float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small); + float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large); + float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count); + float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count); + float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves); + + float pcount = 1; // Include myself whether or not team is already set right and I'm a "player". + if (teamplay) + { + FOREACH_CLIENT(IS_PLAYER(it) && it != this, { + if(it.team == this.team) + ++pcount; + }); + if (sdelay_small_count == 0) + sdelay_small_count = 1; + if (sdelay_large_count == 0) + sdelay_large_count = 1; + } + else + { + FOREACH_CLIENT(IS_PLAYER(it) && it != this, { + ++pcount; + }); + if (sdelay_small_count == 0) + { + if (IS_INDEPENDENT_PLAYER(this)) + { + // Players play independently. No point in requiring enemies. + sdelay_small_count = 1; + } + else + { + // Players play AGAINST each other. Enemies required. + sdelay_small_count = 2; + } + } + if (sdelay_large_count == 0) + { + if (IS_INDEPENDENT_PLAYER(this)) + { + // Players play independently. No point in requiring enemies. + sdelay_large_count = 1; + } + else + { + // Players play AGAINST each other. Enemies required. + sdelay_large_count = 2; + } + } + } + + float sdelay; + + if (pcount <= sdelay_small_count) + sdelay = sdelay_small; + else if (pcount >= sdelay_large_count) + sdelay = sdelay_large; + else // NOTE: this case implies sdelay_large_count > sdelay_small_count. + sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count); + + if(waves) + this.respawn_time = ceil((time + sdelay) / waves) * waves; + else + this.respawn_time = time + sdelay; + + if(sdelay < sdelay_max) + this.respawn_time_max = time + sdelay_max; + else + this.respawn_time_max = this.respawn_time; + + if((sdelay + waves >= 5.0) && (this.respawn_time - time > 1.75)) + this.respawn_countdown = 10; // first number to count down from is 10 + else + this.respawn_countdown = -1; // do not count down + + if(autocvar_g_forced_respawn) + this.respawn_flags = this.respawn_flags | RESPAWN_FORCE; +} // LordHavoc: this hack will be removed when proper _pants/_shirt layers are // added to the model skins @@ -1351,60 +1435,6 @@ void respawn(entity this) PutClientInServer(this); } -ERASEABLE -void PrintToChat(entity client, string text) -{ - text = strcat("\{1}^7", text, "\n"); - sprint(client, text); -} - -ERASEABLE -void DebugPrintToChat(entity client, string text) -{ - if (autocvar_developer > 0) - { - PrintToChat(client, text); - } -} - -ERASEABLE -void PrintToChatAll(string text) -{ - text = strcat("\{1}^7", text, "\n"); - bprint(text); -} - -ERASEABLE -void DebugPrintToChatAll(string text) -{ - if (autocvar_developer > 0) - { - PrintToChatAll(text); - } -} - -ERASEABLE -void PrintToChatTeam(int team_num, string text) -{ - text = strcat("\{1}^7", text, "\n"); - FOREACH_CLIENT(IS_REAL_CLIENT(it), - { - if (it.team == team_num) - { - sprint(it, text); - } - }); -} - -ERASEABLE -void DebugPrintToChatTeam(int team_num, string text) -{ - if (autocvar_developer > 0) - { - PrintToChatTeam(team_num, text); - } -} - void play_countdown(entity this, float finished, Sound samp) { TC(Sound, samp); @@ -1414,6 +1444,18 @@ void play_countdown(entity this, float finished, Sound samp) sound (this, CH_INFO, samp, VOL_BASE, ATTEN_NORM); } +void player_powerups_remove_all(entity this) +{ + if (this.items & IT_SUPERWEAPON) + { + // don't play the poweroff sound when the game restarts or the player disconnects + if (time > game_starttime + 1 && IS_CLIENT(this)) + sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM); + stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound + this.items -= (this.items & IT_SUPERWEAPON); + } +} + void player_powerups(entity this) { if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped) @@ -1421,19 +1463,10 @@ void player_powerups(entity this) else this.modelflags &= ~MF_ROCKET; - this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | EF_NODEPTHTEST); + this.effects &= ~EF_NODEPTHTEST; if (IS_DEAD(this)) - { - if (this.items & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON)) - { - sound(this, CH_INFO, SND_POWEROFF, VOL_BASE, ATTEN_NORM); - stopsound(this, CH_TRIGGER_SINGLE); // get rid of the pickup sound - this.items &= ~ITEM_Strength.m_itemid; - this.items &= ~ITEM_Shield.m_itemid; - this.items -= (this.items & IT_SUPERWEAPON); - } - } + player_powerups_remove_all(this); if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed return; @@ -1441,58 +1474,14 @@ void player_powerups(entity this) // add a way to see what the items were BEFORE all of these checks for the mutator hook int items_prev = this.items; - Fire_ApplyDamage(this); - Fire_ApplyEffect(this); - if (!MUTATOR_IS_ENABLED(mutator_instagib)) { - if (this.items & ITEM_Strength.m_itemid) - { - play_countdown(this, STAT(STRENGTH_FINISHED, this), SND_POWEROFF); - this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > STAT(STRENGTH_FINISHED, this)) - { - this.items = this.items - (this.items & ITEM_Strength.m_itemid); - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_STRENGTH, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_STRENGTH); - } - } - else - { - if (time < STAT(STRENGTH_FINISHED, this)) - { - this.items = this.items | ITEM_Strength.m_itemid; - if(!g_cts) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_STRENGTH, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_STRENGTH); - } - } - if (this.items & ITEM_Shield.m_itemid) - { - play_countdown(this, STAT(INVINCIBLE_FINISHED, this), SND_POWEROFF); - this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > STAT(INVINCIBLE_FINISHED, this)) - { - this.items = this.items - (this.items & ITEM_Shield.m_itemid); - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERDOWN_SHIELD, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERDOWN_SHIELD); - } - } - else - { - if (time < STAT(INVINCIBLE_FINISHED, this)) - { - this.items = this.items | ITEM_Shield.m_itemid; - if(!g_cts) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_POWERUP_SHIELD, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_POWERUP_SHIELD); - } - } + // NOTE: superweapons are a special case and as such are handled here instead of the status effects system if (this.items & IT_SUPERWEAPON) { if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)) { - STAT(SUPERWEAPONS_FINISHED, this) = 0; + StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_NORMAL); this.items = this.items - (this.items & IT_SUPERWEAPON); //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_LOST, this.netname); Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_LOST); @@ -1503,8 +1492,8 @@ void player_powerups(entity this) } else { - play_countdown(this, STAT(SUPERWEAPONS_FINISHED, this), SND_POWEROFF); - if (time > STAT(SUPERWEAPONS_FINISHED, this)) + play_countdown(this, StatusEffects_gettime(STATUSEFFECT_Superweapons, this), SND_POWEROFF); + if (time > StatusEffects_gettime(STATUSEFFECT_Superweapons, this)) { this.items = this.items - (this.items & IT_SUPERWEAPON); STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS; @@ -1515,7 +1504,7 @@ void player_powerups(entity this) } else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) { - if (time < STAT(SUPERWEAPONS_FINISHED, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS)) + if (time < StatusEffects_gettime(STATUSEFFECT_Superweapons, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS)) { this.items = this.items | IT_SUPERWEAPON; if(!(this.items & IT_UNLIMITED_SUPERWEAPONS)) @@ -1527,13 +1516,14 @@ void player_powerups(entity this) } else { - STAT(SUPERWEAPONS_FINISHED, this) = 0; + if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) + StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_TIMEOUT); STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS; } } - else + else if(StatusEffects_active(STATUSEFFECT_Superweapons, this)) // cheaper to check than to update each frame! { - STAT(SUPERWEAPONS_FINISHED, this) = 0; + StatusEffects_remove(STATUSEFFECT_Superweapons, this, STATUSEFFECT_REMOVE_CLEAR); } } @@ -1543,10 +1533,6 @@ void player_powerups(entity this) if(autocvar_g_fullbrightplayers) this.effects = this.effects | EF_FULLBRIGHT; - if (time >= game_starttime) - if (time < this.spawnshieldtime) - this.effects = this.effects | (EF_ADDITIVE | EF_FULLBRIGHT); - MUTATOR_CALLHOOK(PlayerPowerups, this, items_prev); } @@ -1673,6 +1659,14 @@ void SetZoomState(entity this, float newzoom) void GetPressedKeys(entity this) { MUTATOR_CALLHOOK(GetPressedKeys, this); + if (game_stopped) + { + CS(this).pressedkeys = 0; + STAT(PRESSED_KEYS, this) = 0; + return; + } + + // NOTE: GetPressedKeys and PM_dodging_GetPressedKeys use similar code int keys = STAT(PRESSED_KEYS, this); keys = BITSET(keys, KEY_FORWARD, CS(this).movement.x > 0); keys = BITSET(keys, KEY_BACKWARD, CS(this).movement.x < 0); @@ -1715,10 +1709,7 @@ void SpectateCopy(entity this, entity spectatee) this.items = spectatee.items; STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee); STAT(HIT_TIME, this) = STAT(HIT_TIME, spectatee); - STAT(STRENGTH_FINISHED, this) = STAT(STRENGTH_FINISHED, spectatee); - STAT(INVINCIBLE_FINISHED, this) = STAT(INVINCIBLE_FINISHED, spectatee); - STAT(SUPERWEAPONS_FINISHED, this) = STAT(SUPERWEAPONS_FINISHED, spectatee); - this.air_finished = spectatee.air_finished; + STAT(AIR_FINISHED, this) = STAT(AIR_FINISHED, spectatee); STAT(PRESSED_KEYS, this) = STAT(PRESSED_KEYS, spectatee); STAT(WEAPONS, this) = STAT(WEAPONS, spectatee); this.punchangle = spectatee.punchangle; @@ -1897,10 +1888,10 @@ bool SpectatePrev(entity this) switch (MUTATOR_CALLHOOK(SpectatePrev, this, ent, first)) { case MUT_SPECPREV_FOUND: - ent = M_ARGV(1, entity); - break; + ent = M_ARGV(1, entity); + break; case MUT_SPECPREV_RETURN: - return true; + return true; case MUT_SPECPREV_CONTINUE: default: { @@ -2034,23 +2025,6 @@ int nJoinAllowed(entity this, entity ignore) return free_slots; } -/** - * Checks whether the client is an observer or spectator, if so, he will get kicked after - * g_maxplayers_spectator_blocktime seconds - */ -void checkSpectatorBlock(entity this) -{ - if(IS_SPEC(this) || IS_OBSERVER(this)) - if(!this.caplayer) - if(IS_REAL_CLIENT(this)) - { - if( time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime) ) { - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING); - dropclient(this); - } - } -} - void PrintWelcomeMessage(entity this) { if(CS(this).motd_actived_time == 0) @@ -2133,7 +2107,7 @@ bool PlayerThink(entity this) } if (timeout_status == TIMEOUT_ACTIVE) { - // don't allow the player to turn around while game is paused + // don't allow the player to turn around while game is paused // FIXME turn this into CSQC stuff this.v_angle = this.lastV_angle; this.angles = this.lastV_angle; @@ -2230,7 +2204,7 @@ bool PlayerThink(entity this) bool dualwielding = W_DualWielding(this); if(this.dualwielding_prev != dualwielding) { - W_ResetGunAlign(this, CS(this).cvar_cl_gunalign); + W_ResetGunAlign(this, CS_CVAR(this).cvar_cl_gunalign); this.dualwielding_prev = dualwielding; } @@ -2262,13 +2236,13 @@ bool PlayerThink(entity this) this.dmg_team = max(0, this.dmg_team - autocvar_g_teamdamage_resetspeed * frametime); } - secrets_setstatus(this); monsters_setstatus(this); return true; } .bool would_spectate; +// merged SpectatorThink and ObserverThink (old names are here so you can grep for them) void ObserverOrSpectatorThink(entity this) { bool is_spec = IS_SPEC(this); @@ -2299,6 +2273,8 @@ void ObserverOrSpectatorThink(entity this) TRANSMUTE(Observer, this); PutClientInServer(this); } + else + this.would_spectate = false; // unable to spectate anyone if (is_spec) CS(this).impulse = 0; } else if (is_spec) { @@ -2322,10 +2298,13 @@ void ObserverOrSpectatorThink(entity this) } } else { - int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? CS(this).cvar_cl_clippedspectating : !CS(this).cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP); + bool wouldclip = CS_CVAR(this).cvar_cl_clippedspectating; + if (PHYS_INPUT_BUTTON_USE(this)) + wouldclip = !wouldclip; + int preferred_movetype = (wouldclip ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP); set_movetype(this, preferred_movetype); } - } else { + } else { // jump pressed if ((is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this))) || (!is_spec && !(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this)))) { this.flags |= FL_JUMPRELEASED; @@ -2401,20 +2380,24 @@ Called every frame for each client before the physics are run .float last_vehiclecheck; void PlayerPreThink (entity this) { - STAT(GUNALIGN, this) = CS(this).cvar_cl_gunalign; // TODO - STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS(this).cvar_cl_movement_track_canjump; + STAT(GUNALIGN, this) = CS_CVAR(this).cvar_cl_gunalign; // TODO + STAT(MOVEVARS_CL_TRACK_CANJUMP, this) = CS_CVAR(this).cvar_cl_movement_track_canjump; WarpZone_PlayerPhysics_FixVAngle(this); if (frametime) { // physics frames: update anticheat stuff anticheat_prethink(this); - } - if (blockSpectators && frametime) { // WORKAROUND: only use dropclient in server frames (frametime set). // Never use it in cl_movement frames (frametime zero). - checkSpectatorBlock(this); + if (blockSpectators && IS_REAL_CLIENT(this) + && (IS_SPEC(this) || IS_OBSERVER(this)) && !this.caplayer + && time > (CS(this).spectatortime + autocvar_g_maxplayers_spectator_blocktime)) + { + if (dropclient_schedule(this)) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_QUIT_KICK_SPECTATING); + } } zoomstate_set = false; @@ -2439,24 +2422,24 @@ void PlayerPreThink (entity this) // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe? } if (!assume_unchanged && autocvar_sv_eventlog) - GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this, false))); + GameLogEcho(strcat(":name:", ftos(this.playerid), ":", playername(this.netname, this.team, false))); strcpy(CS(this).netname_previous, this.netname); } // version nagging - if (CS(this).version_nagtime && CS(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) { + if (CS(this).version_nagtime && CS_CVAR(this).cvar_g_xonoticversion && time > CS(this).version_nagtime) { CS(this).version_nagtime = 0; - if (strstrofs(CS(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) { + if (strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(CS_CVAR(this).cvar_g_xonoticversion, "autobuild", 0) >= 0) { // git client } else if (strstrofs(autocvar_g_xonoticversion, "git", 0) >= 0 || strstrofs(autocvar_g_xonoticversion, "autobuild", 0) >= 0) { // git server - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion); + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_BETA, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion); } else { - int r = vercmp(CS(this).cvar_g_xonoticversion, autocvar_g_xonoticversion); + int r = vercmp(CS_CVAR(this).cvar_g_xonoticversion, autocvar_g_xonoticversion); if (r < 0) { // old client - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion); + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OUTDATED, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion); } else if (r > 0) { // old server - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS(this).cvar_g_xonoticversion); + Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_VERSION_OLD, autocvar_g_xonoticversion, CS_CVAR(this).cvar_g_xonoticversion); } } } @@ -2468,7 +2451,7 @@ void PlayerPreThink (entity this) this.max_armorvalue = 0; } - if (frametime && IS_PLAYER(this)) + if (frametime && IS_PLAYER(this) && time >= game_starttime) { if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING) { @@ -2520,7 +2503,7 @@ void PlayerPreThink (entity this) this.last_vehiclecheck = time + 1; } - if(!CS(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button + if(!CS_CVAR(this).cvar_cl_newusekeysupported) // FIXME remove this - it was a stupid idea to begin with, we can JUST use the button { if(PHYS_INPUT_BUTTON_USE(this) && !CS(this).usekeypressed) PlayerUseKey(this); @@ -2591,15 +2574,6 @@ void PlayerPreThink (entity this) } target_voicescript_next(this); - - // WEAPONTODO: Move into weaponsystem somehow - // if a player goes unarmed after holding a loaded weapon, empty his clip size and remove the crosshair ammo ring - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) - { - .entity weaponentity = weaponentities[slot]; - if(this.(weaponentity).m_weapon == WEP_Null) - this.(weaponentity).clip_load = this.(weaponentity).clip_size = 0; - } } void DrownPlayer(entity this) @@ -2607,21 +2581,21 @@ void DrownPlayer(entity this) if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle || STAT(FROZEN, this) || this.watertype != CONTENT_WATER) { - this.air_finished = 0; + STAT(AIR_FINISHED, this) = 0; return; } if (this.waterlevel != WATERLEVEL_SUBMERGED) { - if(this.air_finished && this.air_finished < time) + if(STAT(AIR_FINISHED, this) && STAT(AIR_FINISHED, this) < time) PlayerSound(this, playersound_gasp, CH_PLAYER, VOL_BASE, VOICETYPE_PLAYERSOUND); - this.air_finished = 0; + STAT(AIR_FINISHED, this) = 0; } else { - if (!this.air_finished) - this.air_finished = time + autocvar_g_balance_contents_drowndelay; - if (this.air_finished < time) + if (!STAT(AIR_FINISHED, this)) + STAT(AIR_FINISHED, this) = time + autocvar_g_balance_contents_drowndelay; + if (STAT(AIR_FINISHED, this) < time) { // drown! if (this.pain_finished < time) { @@ -2660,22 +2634,39 @@ void PlayerPostThink (entity this) { Player_Physics(this); - if (sv_maxidle > 0) + if (autocvar_sv_maxidle > 0 || (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0)) if (frametime) // WORKAROUND: only use dropclient in server frames (frametime set). Never use it in cl_movement frames (frametime zero). if (IS_REAL_CLIENT(this)) - if (IS_PLAYER(this) || sv_maxidle_spectatorsareidle) + if (IS_PLAYER(this) || autocvar_sv_maxidle_alsokickspectators) + if (!intermission_running) // NextLevel() kills all centerprints after setting this true { int totalClients = 0; - if(sv_maxidle_slots > 0) + if(autocvar_sv_maxidle > 0 && autocvar_sv_maxidle_slots > 0) + { + // maxidle disabled in local matches by not counting clients (totalClients 0) + if (server_is_dedicated) + { + FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots, + { + ++totalClients; + }); + if (maxclients - totalClients > autocvar_sv_maxidle_slots) + totalClients = 0; + } + } + else if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0) { - FOREACH_CLIENT(IS_REAL_CLIENT(it) || sv_maxidle_slots_countbots, + FOREACH_CLIENT(IS_REAL_CLIENT(it), { ++totalClients; }); } - if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots) - { /* do nothing */ } + if (totalClients < autocvar_sv_maxidle_minplayers) + { + // idle kick disabled + CS(this).parm_idlesince = time; + } else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10 { if (CS(this).idlekick_lasttimeleft) @@ -2686,20 +2677,37 @@ void PlayerPostThink (entity this) } else { - float timeleft = ceil(sv_maxidle - (time - CS(this).parm_idlesince)); - if (timeleft == min(10, sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10 - if (!CS(this).idlekick_lasttimeleft) + float maxidle_time = autocvar_sv_maxidle; + if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0) + maxidle_time = autocvar_sv_maxidle_playertospectator; + float timeleft = ceil(maxidle_time - (time - CS(this).parm_idlesince)); + float countdown_time = max(min(10, maxidle_time - 1), ceil(maxidle_time * 0.33)); // - 1 to support maxidle_time <= 10 + if (timeleft == countdown_time && !CS(this).idlekick_lasttimeleft) + { + if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0) + Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_MOVETOSPEC_IDLING, timeleft); + else Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft); } if (timeleft <= 0) { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname); - dropclient(this); + if (IS_PLAYER(this) && autocvar_sv_maxidle_playertospectator > 0) + { + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_MOVETOSPEC_IDLING, this.netname, maxidle_time); + if (this.caplayer) + this.caplayer = 0; + this.lms_spectate_warning = 2; // TODO: mutator hook for players forcibly moved to spectator? + PutObserverInServer(this); + } + else + { + if (dropclient_schedule(this)) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_KICK_IDLING, this.netname, maxidle_time); + } return; } - else if (timeleft <= 10) { - if (timeleft != CS(this).idlekick_lasttimeleft) { - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft)); - } + else if (timeleft <= countdown_time) { + if (timeleft != CS(this).idlekick_lasttimeleft) + play2(this, SND(TALK2)); CS(this).idlekick_lasttimeleft = timeleft; } } @@ -2728,12 +2736,12 @@ void PlayerPostThink (entity this) DrownPlayer(this); UpdateChatBubble(this); if (CS(this).impulse) ImpulseCommands(this); + GetPressedKeys(this); if (game_stopped) { CSQCMODEL_AUTOUPDATE(this); return; } - GetPressedKeys(this); } else if (IS_OBSERVER(this) && STAT(PRESSED_KEYS, this)) { @@ -2749,337 +2757,6 @@ void PlayerPostThink (entity this) CSQCMODEL_AUTOUPDATE(this); } -/** - * message "": do not say, just test flood control - * return value: - * 1 = accept - * 0 = reject - * -1 = fake accept - */ -int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodcontrol) -{ - if (!teamsay && !privatesay && substring(msgin, 0, 1) == " ") - msgin = substring(msgin, 1, -1); // work around DP say bug (say_team does not have this!) - - if (source) - msgin = formatmessage(source, msgin); - - string colorstr; - if (!(IS_PLAYER(source) || source.caplayer)) - colorstr = "^0"; // black for spectators - else if(teamplay) - colorstr = Team_ColorCode(source.team); - else - { - colorstr = ""; - teamsay = false; - } - - if (!source) { - colorstr = ""; - teamsay = false; - } - - if(msgin != "") - msgin = trigger_magicear_processmessage_forallears(source, teamsay, privatesay, msgin); - - /* - * using bprint solves this... me stupid - // how can we prevent the message from appearing in a listen server? - // for now, just give "say" back and only handle say_team - if(!teamsay) - { - clientcommand(source, strcat("say ", msgin)); - return; - } - */ - - string namestr = ""; - if (source) - namestr = playername(source, autocvar_g_chat_teamcolors); - - string colorprefix = (strdecolorize(namestr) == namestr) ? "^3" : "^7"; - - string msgstr = "", cmsgstr = ""; - string privatemsgprefix = string_null; - int privatemsgprefixlen = 0; - if (msgin != "") - { - bool found_me = false; - if(strstrofs(msgin, "/me", 0) >= 0) - { - string newmsgin = ""; - string newnamestr = ((teamsay) ? strcat(colorstr, "(", colorprefix, namestr, colorstr, ")", "^7") : strcat(colorprefix, namestr, "^7")); - FOREACH_WORD(msgin, true, - { - if(strdecolorize(it) == "/me") - { - found_me = true; - newmsgin = cons(newmsgin, newnamestr); - } - else - newmsgin = cons(newmsgin, it); - }); - msgin = newmsgin; - } - - if(privatesay) - { - msgstr = strcat("\{1}\{13}* ", colorprefix, namestr, "^3 tells you: ^7"); - privatemsgprefixlen = strlen(msgstr); - msgstr = strcat(msgstr, msgin); - cmsgstr = strcat(colorstr, colorprefix, namestr, "^3 tells you:\n^7", msgin); - privatemsgprefix = strcat("\{1}\{13}* ^3You tell ", playername(privatesay, autocvar_g_chat_teamcolors), ": ^7"); - } - else if(teamsay) - { - if(found_me) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - //msgin = strreplace("/me", strcat(colorstr, "(", colorprefix, namestr, colorstr, ")^7"), msgin); - msgstr = strcat("\{1}\{13}^4* ", "^7", msgin); - } - else - msgstr = strcat("\{1}\{13}", colorstr, "(", colorprefix, namestr, colorstr, ") ^7", msgin); - cmsgstr = strcat(colorstr, "(", colorprefix, namestr, colorstr, ")\n^7", msgin); - } - else - { - if(found_me) - { - //msgin = strreplace("/me", "", msgin); - //msgin = substring(msgin, 3, strlen(msgin)); - //msgin = strreplace("/me", strcat(colorprefix, namestr), msgin); - msgstr = strcat("\{1}^4* ^7", msgin); - } - else { - msgstr = "\{1}"; - msgstr = strcat(msgstr, (namestr != "") ? strcat(colorprefix, namestr, "^7: ") : "^7"); - msgstr = strcat(msgstr, msgin); - } - cmsgstr = ""; - } - msgstr = strcat(strreplace("\n", " ", msgstr), "\n"); // newlines only are good for centerprint - } - - string fullmsgstr = msgstr; - string fullcmsgstr = cmsgstr; - - // FLOOD CONTROL - int flood = 0; - var .float flood_field = floodcontrol_chat; - if(floodcontrol && source) - { - float flood_spl; - float flood_burst; - float flood_lmax; - float lines; - if(privatesay) - { - flood_spl = autocvar_g_chat_flood_spl_tell; - flood_burst = autocvar_g_chat_flood_burst_tell; - flood_lmax = autocvar_g_chat_flood_lmax_tell; - flood_field = floodcontrol_chattell; - } - else if(teamsay) - { - flood_spl = autocvar_g_chat_flood_spl_team; - flood_burst = autocvar_g_chat_flood_burst_team; - flood_lmax = autocvar_g_chat_flood_lmax_team; - flood_field = floodcontrol_chatteam; - } - else - { - flood_spl = autocvar_g_chat_flood_spl; - flood_burst = autocvar_g_chat_flood_burst; - flood_lmax = autocvar_g_chat_flood_lmax; - flood_field = floodcontrol_chat; - } - flood_burst = max(0, flood_burst - 1); - // to match explanation in default.cfg, a value of 3 must allow three-line bursts and not four! - - // do flood control for the default line size - if(msgstr != "") - { - getWrappedLine_remaining = msgstr; - msgstr = ""; - lines = 0; - while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) - { - msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width - ++lines; - } - msgstr = substring(msgstr, 1, strlen(msgstr) - 1); - - if(getWrappedLine_remaining != "") - { - msgstr = strcat(msgstr, "\n"); - flood = 2; - } - - if (time >= source.(flood_field)) - { - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + lines * flood_spl; - } - else - { - flood = 1; - msgstr = fullmsgstr; - } - } - else - { - if (time >= source.(flood_field)) - source.(flood_field) = max(time - flood_burst * flood_spl, source.(flood_field)) + flood_spl; - else - flood = 1; - } - - if (timeout_status == TIMEOUT_ACTIVE) // when game is paused, no flood protection - source.(flood_field) = flood = 0; - } - - string sourcemsgstr, sourcecmsgstr; - if(flood == 2) // cannot happen for empty msgstr - { - if(autocvar_g_chat_flood_notify_flooder) - { - sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n"); - sourcecmsgstr = ""; - } - else - { - sourcemsgstr = fullmsgstr; - sourcecmsgstr = fullcmsgstr; - } - cmsgstr = ""; - } - else - { - sourcemsgstr = msgstr; - sourcecmsgstr = cmsgstr; - } - - if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer)) - { - if (!game_stopped) - if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) - teamsay = -1; // spectators - } - - if(flood) - LOG_INFO("NOTE: ", playername(source, true), "^7 is flooding."); - - // build sourcemsgstr by cutting off a prefix and replacing it by the other one - if(privatesay) - sourcemsgstr = strcat(privatemsgprefix, substring(sourcemsgstr, privatemsgprefixlen, -1)); - - int ret; - if(source && CS(source).muted) - { - // always fake the message - ret = -1; - } - else if(flood == 1) - { - if (autocvar_g_chat_flood_notify_flooder) - { - sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n")); - ret = 0; - } - else - ret = -1; - } - else - { - ret = 1; - } - - if (privatesay && source && !(IS_PLAYER(source) || source.caplayer)) - { - if (!game_stopped) - if ((privatesay && (IS_PLAYER(privatesay) || privatesay.caplayer)) && ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage))) - ret = -1; // just hide the message completely - } - - MUTATOR_CALLHOOK(ChatMessage, source, ret); - ret = M_ARGV(1, int); - - string event_log_msg = ""; - - if(sourcemsgstr != "" && ret != 0) - { - if(ret < 0) // faked message, because the player is muted - { - sprint(source, sourcemsgstr); - if(sourcecmsgstr != "" && !privatesay) - centerprint(source, sourcecmsgstr); - } - else if(privatesay) // private message, between 2 people only - { - sprint(source, sourcemsgstr); - if (!autocvar_g_chat_tellprivacy) { dedicated_print(msgstr); } // send to server console too if "tellprivacy" is disabled - if(!MUTATOR_CALLHOOK(ChatMessageTo, privatesay, source)) - { - sprint(privatesay, msgstr); - if(cmsgstr != "") - centerprint(privatesay, cmsgstr); - } - } - else if ( teamsay && CS(source).active_minigame ) - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && CS(it).active_minigame == CS(source).active_minigame && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat_minigame:%d:%s:%s", source.playerid, CS(source).active_minigame.netname, msgin); - - } - else if(teamsay > 0) // team message, only sent to team mates - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - if(sourcecmsgstr != "") - centerprint(source, sourcecmsgstr); - FOREACH_CLIENT((IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && it.team == source.team && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - if(cmsgstr != "") - centerprint(it, cmsgstr); - }); - event_log_msg = sprintf(":chat_team:%d:%d:%s", source.playerid, source.team, strreplace("\n", " ", msgin)); - } - else if(teamsay < 0) // spectator message, only sent to spectators - { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - FOREACH_CLIENT(!(IS_PLAYER(it) || it.caplayer) && IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat_spec:%d:%s", source.playerid, strreplace("\n", " ", msgin)); - } - else - { - if (source) { - sprint(source, sourcemsgstr); - dedicated_print(msgstr); // send to server console too - MX_Say(strcat(playername(source, true), "^7: ", msgin)); - } - FOREACH_CLIENT(IS_REAL_CLIENT(it) && it != source && !MUTATOR_CALLHOOK(ChatMessageTo, it, source), { - sprint(it, msgstr); - }); - event_log_msg = sprintf(":chat:%d:%s", source.playerid, strreplace("\n", " ", msgin)); - } - } - - if (autocvar_sv_eventlog && (event_log_msg != "")) { - GameLogEcho(event_log_msg); - } - - return ret; -} - // hack to copy the button fields from the client entity to the Client State void PM_UpdateButtons(entity this, entity store) {