X-Git-Url: https://de.git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fclient.qc;h=8261f36b39c391e77feb62374397d671f3bf549a;hb=750f4d1b8394736fbaeb8d36a087b28443dc0c0d;hp=909e89768b866bad6e8516c50ba7e66946301064;hpb=338f8f4862627b3ee04cbdd20dc5590973614e01;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 909e89768..8261f36b3 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -17,6 +17,7 @@ #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" @@ -31,11 +32,14 @@ #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 "../common/vehicles/all.qh" @@ -228,7 +232,7 @@ void PutObserverInServer(entity this) if (IS_PLAYER(this)) { - if(GetResourceAmount(this, RESOURCE_HEALTH) >= 1) + if(GetResource(this, RES_HEALTH) >= 1) { // despawn effect Send_Effect(EFFECT_SPAWN_NEUTRAL, this.origin, '0 0 0', 1); @@ -270,7 +274,7 @@ void PutObserverInServer(entity this) RemoveGrapplingHooks(this); Portal_ClearAll(this); - Unfreeze(this); + Unfreeze(this, false); SetSpectatee(this, NULL); if (this.alivetime) @@ -284,25 +288,11 @@ void PutObserverInServer(entity this) WaypointSprite_PlayerDead(this); - if (mutator_returnvalue) { - // mutator prevents resetting teams+score - } else { - Player_SetTeamIndex(this, -1); - this.frags = FRAGS_SPECTATOR; - PlayerScore_Clear(this); // clear scores when needed - } - if (CS(this).killcount != FRAGS_SPECTATOR) { - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname); 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).just_joined) - LogTeamchange(this.playerid, -1, TEAM_CHANGE_SPECTATOR); - else - CS(this).just_joined = false; } accuracy_resend(this); @@ -321,14 +311,14 @@ void PutObserverInServer(entity this) if(this.damagedbycontents) IL_REMOVE(g_damagedbycontents, this); this.damagedbycontents = false; - SetResourceAmountExplicit(this, RESOURCE_HEALTH, FRAGS_SPECTATOR); + SetResourceExplicit(this, RES_HEALTH, FRAGS_SPECTATOR); SetSpectatee_status(this, etof(this)); this.takedamage = DAMAGE_NO; this.solid = SOLID_NOT; set_movetype(this, MOVETYPE_FLY_WORLDONLY); // user preference is controlled by playerprethink this.flags = FL_CLIENT | FL_NOTARGET; this.effects = 0; - SetResourceAmountExplicit(this, RESOURCE_ARMOR, autocvar_g_balance_armor_start); // was 666?! + SetResourceExplicit(this, RES_ARMOR, autocvar_g_balance_armor_start); // was 666?! this.pauserotarmor_finished = 0; this.pauserothealth_finished = 0; this.pauseregen_finished = 0; @@ -345,7 +335,10 @@ void PutObserverInServer(entity this) this.strength_finished = 0; this.invincible_finished = 0; this.superweapons_finished = 0; - this.dphitcontentsmask = 0; + //this.dphitcontentsmask = 0; + this.dphitcontentsmask = DPCONTENTS_SOLID; + if (autocvar_g_playerclip_collisions) + this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP; this.pushltime = 0; this.istypefrag = 0; setthink(this, func_null); @@ -354,6 +347,7 @@ void PutObserverInServer(entity this) this.crouch = false; STAT(REVIVE_PROGRESS, this) = 0; this.revival_time = 0; + this.draggable = drag_undraggable; this.items = 0; STAT(WEAPONS, this) = '0 0 0'; @@ -387,6 +381,18 @@ void PutObserverInServer(entity this) if(axh.owner == this && axh != NULL && !wasfreed(axh)) delete(axh); } + + if (mutator_returnvalue) + { + // mutator prevents resetting teams+score + } + else + { + SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); + this.frags = FRAGS_SPECTATOR; + } + if (CS(this).just_joined) + CS(this).just_joined = false; } int player_getspecies(entity this) @@ -551,24 +557,24 @@ void PutPlayerInServer(entity this) this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT; if (warmup_stage) { - SetResourceAmount(this, RESOURCE_SHELLS, warmup_start_ammo_shells); - SetResourceAmount(this, RESOURCE_BULLETS, warmup_start_ammo_nails); - SetResourceAmount(this, RESOURCE_ROCKETS, warmup_start_ammo_rockets); - SetResourceAmount(this, RESOURCE_CELLS, warmup_start_ammo_cells); - SetResourceAmount(this, RESOURCE_PLASMA, warmup_start_ammo_plasma); - SetResourceAmount(this, RESOURCE_FUEL, warmup_start_ammo_fuel); - SetResourceAmount(this, RESOURCE_HEALTH, warmup_start_health); - SetResourceAmount(this, RESOURCE_ARMOR, warmup_start_armorvalue); + SetResource(this, RES_SHELLS, warmup_start_ammo_shells); + SetResource(this, RES_BULLETS, warmup_start_ammo_nails); + SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets); + SetResource(this, RES_CELLS, warmup_start_ammo_cells); + SetResource(this, RES_PLASMA, warmup_start_ammo_plasma); + SetResource(this, RES_FUEL, warmup_start_ammo_fuel); + SetResource(this, RES_HEALTH, warmup_start_health); + SetResource(this, RES_ARMOR, warmup_start_armorvalue); STAT(WEAPONS, this) = WARMUP_START_WEAPONS; } else { - SetResourceAmount(this, RESOURCE_SHELLS, start_ammo_shells); - SetResourceAmount(this, RESOURCE_BULLETS, start_ammo_nails); - SetResourceAmount(this, RESOURCE_ROCKETS, start_ammo_rockets); - SetResourceAmount(this, RESOURCE_CELLS, start_ammo_cells); - SetResourceAmount(this, RESOURCE_PLASMA, start_ammo_plasma); - SetResourceAmount(this, RESOURCE_FUEL, start_ammo_fuel); - SetResourceAmount(this, RESOURCE_HEALTH, start_health); - SetResourceAmount(this, RESOURCE_ARMOR, start_armorvalue); + SetResource(this, RES_SHELLS, start_ammo_shells); + SetResource(this, RES_BULLETS, start_ammo_nails); + SetResource(this, RES_ROCKETS, start_ammo_rockets); + SetResource(this, RES_CELLS, start_ammo_cells); + SetResource(this, RES_PLASMA, start_ammo_plasma); + SetResource(this, RES_FUEL, start_ammo_fuel); + SetResource(this, RES_HEALTH, start_health); + SetResource(this, RES_ARMOR, start_armorvalue); STAT(WEAPONS, this) = start_weapons; if (MUTATOR_CALLHOOK(ForbidRandomStartWeapons, this) == false) { @@ -618,7 +624,10 @@ void PutPlayerInServer(entity this) this.angles = spot.angles; this.angles_z = 0; // never spawn tilted even if the spot says to if (IS_BOT_CLIENT(this)) + { this.v_angle = this.angles; + bot_aim_reset(this); + } this.fixangle = true; // turn this way immediately this.oldvelocity = this.velocity = '0 0 0'; this.avelocity = '0 0 0'; @@ -674,6 +683,8 @@ void PutPlayerInServer(entity this) this.event_damage = PlayerDamage; this.event_heal = PlayerHeal; + this.draggable = func_null; + if(!this.bot_attack) IL_PUSH(g_bot_targets, this); this.bot_attack = true; @@ -705,6 +716,9 @@ void PutPlayerInServer(entity this) this.speedrunning = false; + this.counter_cnt = 0; + this.fragsfilter_cnt = 0; + target_voicescript_clear(this); // reset fields the weapons may use @@ -722,12 +736,14 @@ void PutPlayerInServer(entity this) { string s = spot.target; - spot.target = string_null; + if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack + spot.target = string_null; SUB_UseTargets(spot, this, NULL); - spot.target = s; + if(g_assault || g_race) + spot.target = s; } - Unfreeze(this); + Unfreeze(this, false); MUTATOR_CALLHOOK(PlayerSpawn, this, spot); @@ -900,204 +916,6 @@ void DecodeLevelParms(entity this) MUTATOR_CALLHOOK(DecodeLevelParms); } -/* -============= -ClientKill - -Called when a client types 'kill' in the console -============= -*/ - -.float clientkill_nexttime; -void ClientKill_Now_TeamChange(entity this) -{ - if(this.killindicator_teamchange == -1) - { - TeamBalance_JoinBestTeam(this); - } - else if(this.killindicator_teamchange == -2) - { - if(blockSpectators) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime); - PutObserverInServer(this); - } - else - SV_ChangeTeam(this, this.killindicator_teamchange - 1); - this.killindicator_teamchange = 0; -} - -void ClientKill_Now(entity this) -{ - if(this.vehicle) - { - vehicles_exit(this.vehicle, VHEF_RELEASE); - if(!this.killindicator_teamchange) - { - this.vehicle_health = -1; - Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - } - - if(this.killindicator && !wasfreed(this.killindicator)) - delete(this.killindicator); - - this.killindicator = NULL; - - if(this.killindicator_teamchange) - ClientKill_Now_TeamChange(this); - - if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false) - { - Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0'); - } - - // now I am sure the player IS dead -} -void KillIndicator_Think(entity this) -{ - if (game_stopped) - { - this.owner.killindicator = NULL; - delete(this); - return; - } - - if (this.owner.alpha < 0 && !this.owner.vehicle) - { - this.owner.killindicator = NULL; - delete(this); - return; - } - - if(this.cnt <= 0) - { - ClientKill_Now(this.owner); - return; - } - else if(this.count == 1) // count == 1 means that it's silent - { - this.nextthink = time + 1; - this.cnt -= 1; - } - else - { - if(this.cnt <= 10) - setmodel(this, MDL_NUM(this.cnt)); - if(IS_REAL_CLIENT(this.owner)) - { - if(this.cnt <= 10) - { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); } - } - this.nextthink = time + 1; - this.cnt -= 1; - } -} - -float clientkilltime; -void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec -{ - float killtime; - float starttime; - - if (game_stopped) - return; - - killtime = autocvar_g_balance_kill_delay; - - if(MUTATOR_CALLHOOK(ClientKill, this, killtime)) - return; - killtime = M_ARGV(1, float); - - this.killindicator_teamchange = targetteam; - - if(!this.killindicator) - { - if(!IS_DEAD(this)) - { - killtime = max(killtime, this.clientkill_nexttime - time); - this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam; - } - - if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this)) - { - ClientKill_Now(this); - } - else - { - starttime = max(time, clientkilltime); - - this.killindicator = spawn(); - this.killindicator.owner = this; - this.killindicator.scale = 0.5; - setattachment(this.killindicator, this, ""); - setorigin(this.killindicator, '0 0 52'); - setthink(this.killindicator, KillIndicator_Think); - this.killindicator.nextthink = starttime + (this.lip) * 0.05; - clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05); - this.killindicator.cnt = ceil(killtime); - this.killindicator.count = bound(0, ceil(killtime), 10); - //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n")); - - IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST), - { - it.killindicator = spawn(); - it.killindicator.owner = it; - it.killindicator.scale = 0.5; - setattachment(it.killindicator, it, ""); - setorigin(it.killindicator, '0 0 52'); - setthink(it.killindicator, KillIndicator_Think); - it.killindicator.nextthink = starttime + (it.lip) * 0.05; - //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05); - it.killindicator.cnt = ceil(killtime); - }); - this.lip = 0; - } - } - if(this.killindicator) - { - if(targetteam == 0) // just die - { - this.killindicator.colormod = '0 0 0'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt); - } - else if(targetteam == -1) // auto - { - this.killindicator.colormod = '0 1 0'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt); - } - else if(targetteam == -2) // spectate - { - this.killindicator.colormod = '0.5 0.5 0.5'; - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt); - } - else - { - this.killindicator.colormod = Team_ColorRGB(targetteam); - if(IS_REAL_CLIENT(this)) - if(this.killindicator.cnt > 0) - Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt); - } - } - -} - -void ClientKill (entity this) -{ - // TODO: once .health is removed, will need to check it here for the "already dead" message! - - if(game_stopped) return; - if(this.player_blocked) return; - if(STAT(FROZEN, this)) return; - - ClientKill_TeamChange(this, 0); -} - void FixClientCvars(entity e) { // send prediction settings to the client @@ -1216,6 +1034,7 @@ string getwelcomemessage(entity this) string versionmessage = GetClientVersionMessage(this); string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n"); + s = strcat(s, "^8\nCurrent map: ^2", GetMapname(), "^8\n"); if(modifications != "") s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n"); @@ -1243,6 +1062,8 @@ string getwelcomemessage(entity this) return s; } +bool autocvar_sv_qcphysics = false; // TODO this is for testing - remove when qcphysics work + /** ============= ClientConnect @@ -1267,38 +1088,7 @@ void ClientConnect(entity this) bot_clientconnect(this); - // identify the right forced team - if (autocvar_g_campaign) - { - if (IS_REAL_CLIENT(this)) // only players, not bots - { - switch (autocvar_g_campaign_forceteam) - { - case 1: this.team_forced = NUM_TEAM_1; break; - case 2: this.team_forced = NUM_TEAM_2; break; - case 3: this.team_forced = NUM_TEAM_3; break; - case 4: this.team_forced = NUM_TEAM_4; break; - default: this.team_forced = 0; - } - } - } - else if (PlayerInList(this, autocvar_g_forced_team_red)) this.team_forced = NUM_TEAM_1; - else if (PlayerInList(this, autocvar_g_forced_team_blue)) this.team_forced = NUM_TEAM_2; - else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3; - else if (PlayerInList(this, autocvar_g_forced_team_pink)) this.team_forced = NUM_TEAM_4; - else switch (autocvar_g_forced_team_otherwise) - { - default: this.team_forced = 0; break; - case "red": this.team_forced = NUM_TEAM_1; break; - case "blue": this.team_forced = NUM_TEAM_2; break; - case "yellow": this.team_forced = NUM_TEAM_3; break; - case "pink": this.team_forced = NUM_TEAM_4; break; - case "spectate": - case "spectator": - this.team_forced = -1; - break; - } - if (!teamplay && this.team_forced > 0) this.team_forced = 0; + Player_DetermineForcedTeam(this); TRANSMUTE(Observer, this); @@ -1366,6 +1156,8 @@ void ClientConnect(entity this) if (IS_REAL_CLIENT(this)) sv_notice_join(this); + this.move_qcphysics = autocvar_sv_qcphysics; + // update physics stats (players can spawn before physics runs) Physics_UpdateStats(this); @@ -1419,7 +1211,7 @@ void ClientDisconnect(entity this) Portal_ClearAll(this); - Unfreeze(this); + Unfreeze(this, false); RemoveGrapplingHooks(this); @@ -1459,7 +1251,7 @@ void ChatBubbleThink(entity this) if ( !IS_DEAD(this.owner) && IS_PLAYER(this.owner) ) { - if ( CS(this.owner).active_minigame ) + if ( CS(this.owner).active_minigame && PHYS_INPUT_BUTTON_MINIGAME(this.owner) ) this.mdl = "models/sprites/minigame_busy.iqm"; else if (PHYS_INPUT_BUTTON_CHAT(this.owner)) this.mdl = "models/misc/chatbubble.spr"; @@ -1530,12 +1322,14 @@ 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) @@ -1544,12 +1338,14 @@ void DebugPrintToChat(entity client, string text) } } +ERASEABLE void PrintToChatAll(string text) { text = strcat("\{1}^7", text, "\n"); bprint(text); } +ERASEABLE void DebugPrintToChatAll(string text) { if (autocvar_developer) @@ -1558,6 +1354,7 @@ void DebugPrintToChatAll(string text) } } +ERASEABLE void PrintToChatTeam(int team_num, string text) { text = strcat("\{1}^7", text, "\n"); @@ -1570,6 +1367,7 @@ void PrintToChatTeam(int team_num, string text) }); } +ERASEABLE void DebugPrintToChatTeam(int team_num, string text) { if (autocvar_developer) @@ -1679,9 +1477,12 @@ void player_powerups(entity this) if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS)) { this.items = this.items | IT_SUPERWEAPON; - if(!g_cts) - Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname); - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP); + if(!(this.items & IT_UNLIMITED_SUPERWEAPONS)) + { + if(!g_cts) + Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname); + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP); + } } else { @@ -1783,25 +1584,25 @@ void player_regen(entity this) float mina, maxa, limith, limita; maxa = autocvar_g_balance_armor_rotstable; mina = autocvar_g_balance_armor_regenstable; - limith = GetResourceLimit(this, RESOURCE_HEALTH); - limita = GetResourceLimit(this, RESOURCE_ARMOR); + limith = GetResourceLimit(this, RES_HEALTH); + limita = GetResourceLimit(this, RES_ARMOR); regen_health_rotstable = regen_health_rotstable * max_mod; regen_health_stable = regen_health_stable * max_mod; limith = limith * limit_mod; limita = limita * limit_mod; - SetResourceAmount(this, RESOURCE_ARMOR, CalcRotRegen(GetResourceAmount(this, RESOURCE_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, + SetResource(this, RES_ARMOR, CalcRotRegen(GetResource(this, RES_ARMOR), mina, autocvar_g_balance_armor_regen, autocvar_g_balance_armor_regenlinear, regen_mod * frametime * (time > this.pauseregen_finished), maxa, autocvar_g_balance_armor_rot, autocvar_g_balance_armor_rotlinear, rot_mod * frametime * (time > this.pauserotarmor_finished), limita)); - SetResourceAmount(this, RESOURCE_HEALTH, CalcRotRegen(GetResourceAmount(this, RESOURCE_HEALTH), regen_health_stable, regen_health, regen_health_linear, + SetResource(this, RES_HEALTH, CalcRotRegen(GetResource(this, RES_HEALTH), regen_health_stable, regen_health, regen_health_linear, regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable, regen_health_rot, regen_health_rotlinear, rot_mod * frametime * (time > this.pauserothealth_finished), limith)); } // if player rotted to death... die! // check this outside above checks, as player may still be able to rot to death - if(GetResourceAmount(this, RESOURCE_HEALTH) < 1) + if(GetResource(this, RES_HEALTH) < 1) { if(this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); @@ -1815,9 +1616,9 @@ void player_regen(entity this) maxf = autocvar_g_balance_fuel_rotstable; minf = autocvar_g_balance_fuel_regenstable; - limitf = GetResourceLimit(this, RESOURCE_FUEL); + limitf = GetResourceLimit(this, RES_FUEL); - SetResourceAmount(this, RESOURCE_FUEL, CalcRotRegen(GetResourceAmount(this, RESOURCE_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, + SetResource(this, RES_FUEL, CalcRotRegen(GetResource(this, RES_FUEL), minf, autocvar_g_balance_fuel_regen, autocvar_g_balance_fuel_regenlinear, frametime * (time > this.pauseregen_finished) * ((this.items & ITEM_JetpackRegen.m_itemid) != 0), maxf, autocvar_g_balance_fuel_rot, autocvar_g_balance_fuel_rotlinear, frametime * (time > this.pauserotfuel_finished), limitf)); } @@ -1844,7 +1645,7 @@ void GetPressedKeys(entity this) keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0); keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this)); - keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this)); + 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 keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this)); keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this)); CS(this).pressedkeys = keys; // store for other users @@ -1865,15 +1666,15 @@ void SpectateCopy(entity this, entity spectatee) MUTATOR_CALLHOOK(SpectateCopy, spectatee, this); PS(this) = PS(spectatee); this.armortype = spectatee.armortype; - SetResourceAmountExplicit(this, RESOURCE_ARMOR, GetResourceAmount(spectatee, RESOURCE_ARMOR)); - SetResourceAmountExplicit(this, RESOURCE_CELLS, GetResourceAmount(spectatee, RESOURCE_CELLS)); - SetResourceAmountExplicit(this, RESOURCE_PLASMA, GetResourceAmount(spectatee, RESOURCE_PLASMA)); - SetResourceAmountExplicit(this, RESOURCE_SHELLS, GetResourceAmount(spectatee, RESOURCE_SHELLS)); - SetResourceAmountExplicit(this, RESOURCE_BULLETS, GetResourceAmount(spectatee, RESOURCE_BULLETS)); - SetResourceAmountExplicit(this, RESOURCE_ROCKETS, GetResourceAmount(spectatee, RESOURCE_ROCKETS)); - SetResourceAmountExplicit(this, RESOURCE_FUEL, GetResourceAmount(spectatee, RESOURCE_FUEL)); + SetResourceExplicit(this, RES_ARMOR, GetResource(spectatee, RES_ARMOR)); + SetResourceExplicit(this, RES_CELLS, GetResource(spectatee, RES_CELLS)); + SetResourceExplicit(this, RES_PLASMA, GetResource(spectatee, RES_PLASMA)); + SetResourceExplicit(this, RES_SHELLS, GetResource(spectatee, RES_SHELLS)); + SetResourceExplicit(this, RES_BULLETS, GetResource(spectatee, RES_BULLETS)); + SetResourceExplicit(this, RES_ROCKETS, GetResource(spectatee, RES_ROCKETS)); + SetResourceExplicit(this, RES_FUEL, GetResource(spectatee, RES_FUEL)); this.effects = spectatee.effects & EFMASK_CHEAP; // eat performance - SetResourceAmountExplicit(this, RESOURCE_HEALTH, GetResourceAmount(spectatee, RESOURCE_HEALTH)); + SetResourceExplicit(this, RES_HEALTH, GetResource(spectatee, RES_HEALTH)); CS(this).impulse = 0; this.items = spectatee.items; STAT(LAST_PICKUP, this) = STAT(LAST_PICKUP, spectatee); @@ -2102,7 +1903,7 @@ void ShowRespawnCountdown(entity this) .bool team_selected; bool ShowTeamSelection(entity this) { - if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0) + if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this)) return false; stuffcmd(this, "menu_showteamselect\n"); return true; @@ -2125,13 +1926,20 @@ void Join(entity this) if(IS_PLAYER(this)) if(teamplay && this.team != -1) { - //Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname); } else Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname); this.team_selected = false; } +int GetPlayerLimit() +{ + int player_limit = autocvar_g_maxplayers; + MUTATOR_CALLHOOK(GetPlayerLimit, player_limit); + player_limit = M_ARGV(0, int); + return player_limit; +} + /** * Determines whether the player is allowed to join. This depends on cvar * g_maxplayers, if it isn't used this function always return true, otherwise @@ -2150,7 +1958,7 @@ int nJoinAllowed(entity this, entity ignore) return 0; } - if(this && this.team_forced < 0) + if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR)) return 0; // forced spectators can never join // TODO simplify this @@ -2164,11 +1972,13 @@ int nJoinAllowed(entity this, entity ignore) ++currentlyPlaying; }); + int player_limit = GetPlayerLimit(); + float free_slots = 0; - if (!autocvar_g_maxplayers) + if (!player_limit) free_slots = maxclients - totalClients; - else if(currentlyPlaying < autocvar_g_maxplayers) - free_slots = min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying); + else if(currentlyPlaying < player_limit) + free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying); static float join_prevent_msg_time = 0; if(this && ignore && !free_slots && time > join_prevent_msg_time) @@ -2372,7 +2182,7 @@ bool PlayerThink(entity this) } this.items_added = 0; - if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResourceAmount(this, RESOURCE_FUEL) >= 0.01)) + if ((this.items & ITEM_Jetpack.m_itemid) && ((this.items & ITEM_JetpackRegen.m_itemid) || GetResource(this, RES_FUEL) >= 0.01)) this.items_added |= IT_FUEL; this.items |= this.items_added; @@ -2521,19 +2331,15 @@ void PlayerUseKey(entity this) } else if(autocvar_g_vehicles_enter) { - if(!STAT(FROZEN, this)) - if(!IS_DEAD(this)) - if(!game_stopped) + if(!game_stopped && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) { entity head, closest_target = NULL; head = WarpZone_FindRadius(this.origin, autocvar_g_vehicles_enter_radius, true); while(head) // find the closest acceptable target to enter { - if(IS_VEHICLE(head)) - if(!IS_DEAD(head)) + if(IS_VEHICLE(head) && !IS_DEAD(head) && head.takedamage != DAMAGE_NO) if(!head.owner || ((head.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(head.owner, this))) - if(head.takedamage != DAMAGE_NO) { if(closest_target) { @@ -2587,9 +2393,18 @@ void PlayerPreThink (entity this) if (this.netname == "" || this.netname != CS(this).netname_previous) { bool assume_unchanged = (CS(this).netname_previous == ""); + if (autocvar_name_maxlength > 0 && strlennocol(this.netname) > autocvar_name_maxlength) + { + int new_length = textLengthUpToLength(this.netname, autocvar_name_maxlength, strlennocol); + this.netname = strzone(strcat(substring(this.netname, 0, new_length), "^7")); + sprint(this, sprintf("Warning: your name is longer than %d characters, it has been truncated.\n", autocvar_name_maxlength)); + assume_unchanged = false; + // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe? + } if (isInvisibleString(this.netname)) { this.netname = strzone(sprintf("Player#%d", this.playerid)); + sprint(this, "Warning: invisible names are not allowed.\n"); assume_unchanged = false; // stuffcmd(this, strcat("name ", this.netname, "\n")); // maybe? } @@ -2625,21 +2440,21 @@ void PlayerPreThink (entity this) if(IS_PLAYER(this)) { - if (STAT(FROZEN, this) == 2) + if (STAT(FROZEN, this) == FROZEN_TEMP_REVIVING) { STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1); - SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health)); + SetResourceExplicit(this, RES_HEALTH, max(1, STAT(REVIVE_PROGRESS, this) * start_health)); this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1); if (STAT(REVIVE_PROGRESS, this) >= 1) - Unfreeze(this); + Unfreeze(this, false); } - else if (STAT(FROZEN, this) == 3) + else if (STAT(FROZEN, this) == FROZEN_TEMP_DYING) { STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) - frametime * this.revive_speed, 1); - SetResourceAmountExplicit(this, RESOURCE_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this))); + SetResourceExplicit(this, RES_HEALTH, max(0, autocvar_g_nades_ice_health + (start_health-autocvar_g_nades_ice_health) * STAT(REVIVE_PROGRESS, this))); - if (GetResourceAmount(this, RESOURCE_HEALTH) < 1) + if (GetResource(this, RES_HEALTH) < 1) { if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE); @@ -2647,29 +2462,28 @@ void PlayerPreThink (entity this) this.event_damage(this, this, this.frozen_by, 1, DEATH_NADE_ICE_FREEZE.m_id, DMG_NOWEP, this.origin, '0 0 0'); } else if (STAT(REVIVE_PROGRESS, this) <= 0) - Unfreeze(this); + Unfreeze(this, false); } } MUTATOR_CALLHOOK(PlayerPreThink, this); if(autocvar_g_vehicles_enter && (time > this.last_vehiclecheck) && !game_stopped && !this.vehicle) - if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this)) + if(IS_PLAYER(this) && !STAT(FROZEN, this) && !IS_DEAD(this) && !IS_INDEPENDENT_PLAYER(this)) { - FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it), + FOREACH_ENTITY_RADIUS(this.origin, autocvar_g_vehicles_enter_radius, IS_VEHICLE(it) && !IS_DEAD(it) && it.takedamage != DAMAGE_NO, { - if(!IS_DEAD(it) && it.takedamage != DAMAGE_NO) - if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this)) - { - Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER); - } - else if(!it.owner) + if(!it.owner) { if(!it.team || SAME_TEAM(this, it)) Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER); else if(autocvar_g_vehicles_steal) Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_STEAL); } + else if((it.vehicle_flags & VHF_MULTISLOT) && SAME_TEAM(it.owner, this)) + { + Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_VEHICLE_ENTER_GUNNER); + } }); this.last_vehiclecheck = time + 1; @@ -2702,7 +2516,7 @@ void PlayerPreThink (entity this) // don't do this in ClientConnect // many things can go wrong if a client is spawned as player on connection if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this) - || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0) + || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR)) && (!teamplay || autocvar_g_balance_teams))) { campaign_bots_may_start = true; @@ -2783,7 +2597,7 @@ void DrownPlayer(entity this) void Player_Physics(entity this) { - set_movetype(this, this.move_movetype); + this.movetype = (this.move_qcphysics) ? MOVETYPE_QCPLAYER : this.move_movetype; if(!this.move_qcphysics) return; @@ -2881,13 +2695,344 @@ void PlayerPostThink (entity this) } if (this.waypointsprite_attachedforcarrier) { - vector v = healtharmor_maxdamage(GetResourceAmount(this, RESOURCE_HEALTH), GetResourceAmount(this, RESOURCE_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id); - WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, '1 0 0' * v); - } + float hp = healtharmor_maxdamage(GetResource(this, RES_HEALTH), GetResource(this, RES_ARMOR), autocvar_g_balance_armor_blockpercent, DEATH_WEAPON.m_id).x; + WaypointSprite_UpdateHealth(this.waypointsprite_attachedforcarrier, hp); + } 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) { @@ -2895,7 +3040,7 @@ void PM_UpdateButtons(entity this, entity store) store.impulse = this.impulse; this.impulse = 0; - bool typing = this.buttonchat; + bool typing = this.buttonchat || this.button12; store.button0 = (typing) ? 0 : this.button0; //button1?!