X-Git-Url: http://git.xonotic.org/?a=blobdiff_plain;f=qcsrc%2Fserver%2Fclient.qc;h=3afbf4dedf58873d0c1540d8ad667c575036a07d;hb=d9d13627b0c82a96dab08c2f3cd2d966d5425ea8;hp=120f4c92b3368705ecf246bc58d1d3297209fdfc;hpb=b721a3ee61a4d3d2d894fe817fb1efdb551052b6;p=xonotic%2Fxonotic-data.pk3dir.git diff --git a/qcsrc/server/client.qc b/qcsrc/server/client.qc index 120f4c92b..3afbf4ded 100644 --- a/qcsrc/server/client.qc +++ b/qcsrc/server/client.qc @@ -1,6 +1,7 @@ #include "client.qh" -#include +#include +#include #include #include #include "anticheat.qh" @@ -12,15 +13,17 @@ #include "teamplay.qh" #include "spawnpoints.qh" #include "resources.qh" -#include "g_damage.qh" +#include "damage.qh" #include "handicap.qh" -#include "g_hook.qh" +#include "hook.qh" #include "command/common.qh" #include "command/vote.qh" #include "clientkill.qh" #include "cheats.qh" -#include "g_world.qh" +#include "world.qh" +#include #include "race.qh" +#include #include "antilag.qh" #include "campaign.qh" #include "command/common.qh" @@ -38,6 +41,7 @@ #include #include "../common/mapobjects/func/conveyor.qh" +#include #include "../common/mapobjects/teleporters.qh" #include "../common/mapobjects/target/spawnpoint.qh" #include @@ -46,6 +50,7 @@ #include "../common/vehicles/all.qh" #include "weapons/hitplot.qh" +#include "weapons/selection.qh" #include "weapons/weaponsystem.qh" #include "../common/net_notice.qh" @@ -56,6 +61,8 @@ #include "../common/items/_mod.qh" +#include + #include "../common/mutators/mutator/waypoints/all.qh" #include "../common/mutators/mutator/instagib/sv_instagib.qh" #include @@ -74,6 +81,10 @@ #include +#include + +#define CHAT_NOSPECTATORS() ((autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) + STATIC_METHOD(Client, Add, void(Client this, int _team)) { ClientConnect(this); @@ -112,9 +123,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; }); } @@ -229,7 +244,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 +261,33 @@ void PutObserverInServer(entity this) if (vote_called) { VoteCount(false); } ReadyCount(); } - } + } - { - 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 +303,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 +315,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,11 +341,11 @@ void PutObserverInServer(entity this) 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; + STAT(STRENGTH_FINISHED, this) = 0; + STAT(INVINCIBLE_FINISHED, this) = 0; + STAT(SUPERWEAPONS_FINISHED, this) = 0; + STAT(AIR_FINISHED, this) = 0; //this.dphitcontentsmask = 0; this.dphitcontentsmask = DPCONTENTS_SOLID; if (autocvar_g_playerclip_collisions) @@ -346,7 +355,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; @@ -588,7 +597,7 @@ void PutPlayerInServer(entity this) PS(this).dual_weapons = '0 0 0'; - this.superweapons_finished = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0; + STAT(SUPERWEAPONS_FINISHED, this) = (STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) ? time + autocvar_g_balance_superweapons_time : 0; this.items = start_items; @@ -614,7 +623,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 @@ -637,8 +645,8 @@ void PutPlayerInServer(entity this) this.punchangle = '0 0 0'; this.punchvector = '0 0 0'; - this.strength_finished = 0; - this.invincible_finished = 0; + STAT(STRENGTH_FINISHED, this) = 0; + STAT(INVINCIBLE_FINISHED, this) = 0; this.fire_endtime = -1; STAT(REVIVE_PROGRESS, this) = 0; this.revival_time = 0; @@ -647,7 +655,7 @@ void PutPlayerInServer(entity this) STAT(BUFFS, this) = 0; STAT(BUFF_TIME, this) = 0; - this.air_finished = time + autocvar_g_balance_contents_drowndelay; + STAT(AIR_FINISHED, this) = 0; this.waterlevel = WATERLEVEL_NONE; this.watertype = CONTENT_EMPTY; @@ -675,7 +683,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; @@ -686,8 +694,16 @@ void PutPlayerInServer(entity this) IL_REMOVE(g_conveyed, this); this.conveyor = NULL; // prevent conveyors at the previous location from moving a freshly spawned player if(this.swampslug) - delete(this.swampslug); - this.in_swamp = false; + 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); + }); STAT(HUD, this) = HUD_NORMAL; this.event_damage = PlayerDamage; @@ -846,7 +862,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, ""); @@ -919,7 +935,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); } @@ -1032,7 +1048,7 @@ 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"); @@ -1041,7 +1057,9 @@ string getwelcomemessage(entity this) modifications = substring(modifications, 2, strlen(modifications) - 2); string versionmessage = GetClientVersionMessage(this); - string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n"); + string s = strcat(versionmessage, "^8\n^8\nhost is ^9", autocvar_hostname, "^8\n"); + + s = strcat(s, "^8\nmatch type is ^1", gamemode_name, "^8\n"); if(modifications != "") s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n"); @@ -1149,7 +1167,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)) @@ -1210,7 +1228,7 @@ 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); @@ -1229,6 +1247,11 @@ void ClientDisconnect(entity this) if (this.chatbubbleentity) delete(this.chatbubbleentity); if (this.killindicator) delete(this.killindicator); + IL_EACH(g_counters, it.realowner == this, + { + delete(it); + }); + WaypointSprite_PlayerGone(this); bot_relinkplayerlist(); @@ -1291,6 +1314,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 @@ -1310,20 +1418,29 @@ void UpdateChatBubble(entity this) void respawn(entity this) { - if(this.alpha >= 0 && autocvar_g_respawn_ghosts) + bool damagedbycontents_prev = this.damagedbycontents; + if(this.alpha >= 0) { - this.solid = SOLID_NOT; - this.takedamage = DAMAGE_NO; - set_movetype(this, MOVETYPE_FLY); - this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed; - this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3; - this.effects |= CSQCMODEL_EF_RESPAWNGHOST; - Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1); - if(autocvar_g_respawn_ghosts_maxtime) - SUB_SetFade (this, time + autocvar_g_respawn_ghosts_maxtime / 2 + random () * (autocvar_g_respawn_ghosts_maxtime - autocvar_g_respawn_ghosts_maxtime / 2), 1.5); + if(autocvar_g_respawn_ghosts) + { + this.solid = SOLID_NOT; + this.takedamage = DAMAGE_NO; + this.damagedbycontents = false; + set_movetype(this, MOVETYPE_FLY); + this.velocity = '0 0 1' * autocvar_g_respawn_ghosts_speed; + this.avelocity = randomvec() * autocvar_g_respawn_ghosts_speed * 3 - randomvec() * autocvar_g_respawn_ghosts_speed * 3; + this.effects |= CSQCMODEL_EF_RESPAWNGHOST; + this.alpha = min(this.alpha, autocvar_g_respawn_ghosts_alpha); + Send_Effect(EFFECT_RESPAWN_GHOST, this.origin, '0 0 0', 1); + if(autocvar_g_respawn_ghosts_time > 0) + SUB_SetFade(this, time + autocvar_g_respawn_ghosts_time, autocvar_g_respawn_ghosts_fadetime); + } + else + SUB_SetFade (this, time, 1); // fade out the corpse immediately } CopyBody(this, 1); + this.damagedbycontents = damagedbycontents_prev; this.effects |= EF_NODRAW; // prevent another CopyBody PutClientInServer(this); @@ -1394,9 +1511,6 @@ void play_countdown(entity this, float finished, Sound samp) 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; - if((this.items & IT_USING_JETPACK) && !IS_DEAD(this) && !game_stopped) this.modelflags |= MF_ROCKET; else @@ -1404,9 +1518,24 @@ void player_powerups(entity this) this.effects &= ~(EF_RED | EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT | EF_FLAME | 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); + } + } + if((this.alpha < 0 || IS_DEAD(this)) && !this.vehicle) // don't apply the flags if the player is gibbed return; + // 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); @@ -1414,9 +1543,9 @@ void player_powerups(entity this) { if (this.items & ITEM_Strength.m_itemid) { - play_countdown(this, this.strength_finished, SND_POWEROFF); + play_countdown(this, STAT(STRENGTH_FINISHED, this), SND_POWEROFF); this.effects = this.effects | (EF_BLUE | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > this.strength_finished) + 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); @@ -1425,7 +1554,7 @@ void player_powerups(entity this) } else { - if (time < this.strength_finished) + if (time < STAT(STRENGTH_FINISHED, this)) { this.items = this.items | ITEM_Strength.m_itemid; if(!g_cts) @@ -1435,9 +1564,9 @@ void player_powerups(entity this) } if (this.items & ITEM_Shield.m_itemid) { - play_countdown(this, this.invincible_finished, SND_POWEROFF); + play_countdown(this, STAT(INVINCIBLE_FINISHED, this), SND_POWEROFF); this.effects = this.effects | (EF_RED | EF_ADDITIVE | EF_FULLBRIGHT); - if (time > this.invincible_finished) + 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); @@ -1446,7 +1575,7 @@ void player_powerups(entity this) } else { - if (time < this.invincible_finished) + if (time < STAT(INVINCIBLE_FINISHED, this)) { this.items = this.items | ITEM_Shield.m_itemid; if(!g_cts) @@ -1458,7 +1587,7 @@ void player_powerups(entity this) { if (!(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS)) { - this.superweapons_finished = 0; + STAT(SUPERWEAPONS_FINISHED, this) = 0; 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); @@ -1469,8 +1598,8 @@ void player_powerups(entity this) } else { - play_countdown(this, this.superweapons_finished, SND_POWEROFF); - if (time > this.superweapons_finished) + play_countdown(this, STAT(SUPERWEAPONS_FINISHED, this), SND_POWEROFF); + if (time > STAT(SUPERWEAPONS_FINISHED, this)) { this.items = this.items - (this.items & IT_SUPERWEAPON); STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS; @@ -1481,7 +1610,7 @@ void player_powerups(entity this) } else if(STAT(WEAPONS, this) & WEPSET_SUPERWEAPONS) { - if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS)) + if (time < STAT(SUPERWEAPONS_FINISHED, this) || (this.items & IT_UNLIMITED_SUPERWEAPONS)) { this.items = this.items | IT_SUPERWEAPON; if(!(this.items & IT_UNLIMITED_SUPERWEAPONS)) @@ -1493,13 +1622,13 @@ void player_powerups(entity this) } else { - this.superweapons_finished = 0; + STAT(SUPERWEAPONS_FINISHED, this) = 0; STAT(WEAPONS, this) &= ~WEPSET_SUPERWEAPONS; } } else { - this.superweapons_finished = 0; + STAT(SUPERWEAPONS_FINISHED, this) = 0; } } @@ -1536,8 +1665,10 @@ float CalcRot(float current, float stable, float rotfactor, float rotframetime) return max(stable, current + (stable - current) * rotfactor * rotframetime); } -float CalcRotRegen(float current, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit) +void RotRegen(entity this, int res, float regenstable, float regenfactor, float regenlinear, float regenframetime, float rotstable, float rotfactor, float rotlinear, float rotframetime, float limit_mod) { + float old = GetResource(this, res); + float current = old; if(current > rotstable) { if(rotframetime > 0) @@ -1555,10 +1686,12 @@ float CalcRotRegen(float current, float regenstable, float regenfactor, float re } } + float limit = GetResourceLimit(this, res) * limit_mod; if(current > limit) current = limit; - return current; + if (current != old) + SetResource(this, res, current); } void player_regen(entity this) @@ -1588,23 +1721,16 @@ void player_regen(entity this) if(!mutator_returnvalue) if(!STAT(FROZEN, this)) { - float mina, maxa, limith, limita; - maxa = autocvar_g_balance_armor_rotstable; - mina = autocvar_g_balance_armor_regenstable; - limith = GetResourceLimit(this, RES_HEALTH); - limita = GetResourceLimit(this, RES_ARMOR); + float maxa = autocvar_g_balance_armor_rotstable; + float mina = autocvar_g_balance_armor_regenstable; - regen_health_rotstable = regen_health_rotstable * max_mod; - regen_health_stable = regen_health_stable * max_mod; - limith = limith * limit_mod; - limita = limita * limit_mod; + RotRegen(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), limit_mod); - 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)); - 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)); + RotRegen(this, RES_HEALTH, regen_health_stable * max_mod, regen_health, regen_health_linear, + regen_mod * frametime * (time > this.pauseregen_finished), regen_health_rotstable * max_mod, regen_health_rot, regen_health_rotlinear, + rot_mod * frametime * (time > this.pauserothealth_finished), limit_mod); } // if player rotted to death... die! @@ -1619,15 +1745,12 @@ void player_regen(entity this) if (!(this.items & IT_UNLIMITED_AMMO)) { - float minf, maxf, limitf; - - maxf = autocvar_g_balance_fuel_rotstable; - minf = autocvar_g_balance_fuel_regenstable; - limitf = GetResourceLimit(this, RES_FUEL); + float maxf = autocvar_g_balance_fuel_rotstable; + float minf = autocvar_g_balance_fuel_regenstable; - 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)); + RotRegen(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), 1); } } @@ -1687,9 +1810,10 @@ 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); - this.strength_finished = spectatee.strength_finished; - this.invincible_finished = spectatee.invincible_finished; - this.superweapons_finished = spectatee.superweapons_finished; + STAT(STRENGTH_FINISHED, this) = STAT(STRENGTH_FINISHED, spectatee); + STAT(INVINCIBLE_FINISHED, this) = STAT(INVINCIBLE_FINISHED, spectatee); + STAT(SUPERWEAPONS_FINISHED, this) = STAT(SUPERWEAPONS_FINISHED, spectatee); + 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; @@ -1868,10 +1992,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: { @@ -1947,6 +2071,8 @@ void Join(entity this) int GetPlayerLimit() { + if(g_duel) + return 2; // TODO: this workaround is needed since the mutator hook from duel can't be activated before the gametype is loaded (e.g. switching modes via gametype vote screen) int player_limit = autocvar_g_maxplayers; MUTATOR_CALLHOOK(GetPlayerLimit, player_limit); player_limit = M_ARGV(0, int); @@ -1987,17 +2113,17 @@ int nJoinAllowed(entity this, entity ignore) int player_limit = GetPlayerLimit(); - float free_slots = 0; + int free_slots = 0; if (!player_limit) free_slots = maxclients - totalClients; - else if(currentlyPlaying < player_limit) + else if(player_limit > 0 && 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) + static float msg_time = 0; + if(this && !this.caplayer && ignore && !free_slots && time > msg_time) { Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT); - join_prevent_msg_time = time + 3; + msg_time = time + 0.5; } return free_slots; @@ -2079,7 +2205,6 @@ void PrintWelcomeMessage(entity this) } } -const int MIN_SPEC_TIME = 1; bool joinAllowed(entity this) { if (CS(this).version_mismatch) return false; @@ -2091,7 +2216,6 @@ bool joinAllowed(entity this) return true; } -.int items_added; .string shootfromfixedorigin; .bool dualwielding_prev; bool PlayerThink(entity this) @@ -2104,7 +2228,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; @@ -2208,8 +2332,6 @@ bool PlayerThink(entity this) // LordHavoc: allow firing on move frames (sub-ticrate), this gives better timing on slow servers //if(frametime) { - this.items &= ~this.items_added; - for(int slot = 0; slot < MAX_WEAPONSLOTS; ++slot) { .entity weaponentity = weaponentities[slot]; @@ -2217,12 +2339,6 @@ bool PlayerThink(entity this) W_Vortex_Charge(this, weaponentity, frametime); W_WeaponFrame(this, weaponentity); } - - this.items_added = 0; - 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; } if (frametime) @@ -2241,55 +2357,22 @@ 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; -void ObserverThink(entity this) -{ - if ( CS(this).impulse ) - { - MinigameImpulse(this, CS(this).impulse); - CS(this).impulse = 0; - } - - if (this.flags & FL_JUMPRELEASED) { - if (PHYS_INPUT_BUTTON_JUMP(this) && joinAllowed(this)) { - this.flags &= ~FL_JUMPRELEASED; - this.flags |= FL_SPAWNING; - } else if(PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch || this.would_spectate) { - this.flags &= ~FL_JUMPRELEASED; - if(SpectateNext(this)) { - TRANSMUTE(Spectator, 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); - set_movetype(this, preferred_movetype); - } - } else { - if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_JUMP(this))) { - this.flags |= FL_JUMPRELEASED; - if(this.flags & FL_SPAWNING) - { - this.flags &= ~FL_SPAWNING; - Join(this); - return; - } - } - } -} - -void SpectatorThink(entity this) +void ObserverOrSpectatorThink(entity this) { + bool is_spec = IS_SPEC(this); if ( CS(this).impulse ) { - if(MinigameImpulse(this, CS(this).impulse)) + int r = MinigameImpulse(this, CS(this).impulse); + if (!is_spec || r) CS(this).impulse = 0; - if (CS(this).impulse == IMP_weapon_drop.impulse) + if (is_spec && CS(this).impulse == IMP_weapon_drop.impulse) { STAT(CAMERA_SPECTATOR, this) = (STAT(CAMERA_SPECTATOR, this) + 1) % 3; CS(this).impulse = 0; @@ -2298,57 +2381,63 @@ void SpectatorThink(entity this) } if (this.flags & FL_JUMPRELEASED) { - if (PHYS_INPUT_BUTTON_JUMP(this) && joinAllowed(this)) { + if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) { this.flags &= ~FL_JUMPRELEASED; this.flags |= FL_SPAWNING; - } else if(PHYS_INPUT_BUTTON_ATCK(this) || CS(this).impulse == 10 || CS(this).impulse == 15 || CS(this).impulse == 18 || (CS(this).impulse >= 200 && CS(this).impulse <= 209)) { + } else if((is_spec && (PHYS_INPUT_BUTTON_ATCK(this) || CS(this).impulse == 10 || CS(this).impulse == 15 || CS(this).impulse == 18 || (CS(this).impulse >= 200 && CS(this).impulse <= 209))) + || (!is_spec && ((PHYS_INPUT_BUTTON_ATCK(this) && !CS(this).version_mismatch) || this.would_spectate))) { this.flags &= ~FL_JUMPRELEASED; if(SpectateNext(this)) { TRANSMUTE(Spectator, this); - } else { + } else if (is_spec) { TRANSMUTE(Observer, this); PutClientInServer(this); } - CS(this).impulse = 0; - } else if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) { - this.flags &= ~FL_JUMPRELEASED; - if(SpectatePrev(this)) { - TRANSMUTE(Spectator, this); - } else { + if (is_spec) + CS(this).impulse = 0; + } else if (is_spec) { + if(CS(this).impulse == 12 || CS(this).impulse == 16 || CS(this).impulse == 19 || (CS(this).impulse >= 220 && CS(this).impulse <= 229)) { + this.flags &= ~FL_JUMPRELEASED; + if(SpectatePrev(this)) { + TRANSMUTE(Spectator, this); + } else { + TRANSMUTE(Observer, this); + PutClientInServer(this); + } + CS(this).impulse = 0; + } else if(PHYS_INPUT_BUTTON_ATCK2(this)) { + this.would_spectate = false; + this.flags &= ~FL_JUMPRELEASED; TRANSMUTE(Observer, this); PutClientInServer(this); - } - CS(this).impulse = 0; - } else if (PHYS_INPUT_BUTTON_ATCK2(this)) { - this.would_spectate = false; - this.flags &= ~FL_JUMPRELEASED; - TRANSMUTE(Observer, this); - PutClientInServer(this); - } else { - if(!SpectateUpdate(this)) - { - if(!SpectateNext(this)) - { - PutObserverInServer(this); - this.would_spectate = true; - } + } else if(!SpectateUpdate(this) && !SpectateNext(this)) { + PutObserverInServer(this); + this.would_spectate = true; } } + else { + int preferred_movetype = ((!PHYS_INPUT_BUTTON_USE(this) ? CS(this).cvar_cl_clippedspectating : !CS(this).cvar_cl_clippedspectating) ? MOVETYPE_FLY_WORLDONLY : MOVETYPE_NOCLIP); + set_movetype(this, preferred_movetype); + } } else { - if (!(PHYS_INPUT_BUTTON_ATCK(this) || PHYS_INPUT_BUTTON_ATCK2(this))) { + 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; if(this.flags & FL_SPAWNING) { this.flags &= ~FL_SPAWNING; - Join(this); + if(joinAllowed(this)) + Join(this); + else if(time < CS(this).jointime + MIN_SPEC_TIME) + CS(this).autojoin_checked = -1; return; } } - if(!SpectateUpdate(this)) + if(is_spec && !SpectateUpdate(this)) PutObserverInServer(this); } - - this.flags |= FL_CLIENT | FL_NOTARGET; + if (is_spec) + this.flags |= FL_CLIENT | FL_NOTARGET; } void PlayerUseKey(entity this) @@ -2479,7 +2568,8 @@ void PlayerPreThink (entity this) { STAT(REVIVE_PROGRESS, this) = bound(0, STAT(REVIVE_PROGRESS, this) + frametime * this.revive_speed, 1); 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 (this.iceblock) + this.iceblock.alpha = bound(0.2, 1 - STAT(REVIVE_PROGRESS, this), 1); if (STAT(REVIVE_PROGRESS, this) >= 1) Unfreeze(this, false); @@ -2545,25 +2635,24 @@ void PlayerPreThink (entity this) IntermissionThink(this); return; } - else if (IS_REAL_CLIENT(this) && !CS(this).autojoin_checked && time >= CS(this).jointime + MIN_SPEC_TIME) + else if (IS_REAL_CLIENT(this) && CS(this).autojoin_checked <= 0 && time >= CS(this).jointime + MIN_SPEC_TIME) { - CS(this).autojoin_checked = true; + bool early_join_requested = (CS(this).autojoin_checked < 0); + CS(this).autojoin_checked = 1; // 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) + if (early_join_requested || MUTATOR_CALLHOOK(AutoJoinOnConnection, this) || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR)) && (!teamplay || autocvar_g_balance_teams))) { campaign_bots_may_start = true; - Join(this); + if(joinAllowed(this)) + Join(this); return; } } - else if (IS_OBSERVER(this)) { - ObserverThink(this); - } - else if (IS_SPEC(this)) { - SpectatorThink(this); + else if (IS_OBSERVER(this) || IS_SPEC(this)) { + ObserverOrSpectatorThink(this); } // WEAPONTODO: Add weapon request for this @@ -2577,7 +2666,7 @@ void PlayerPreThink (entity this) wep_zoomed += thiswep.wr_zoom(thiswep, this); } SetZoomState(this, PHYS_INPUT_BUTTON_ZOOM(this) || PHYS_INPUT_BUTTON_ZOOMSCRIPT(this) || wep_zoomed); - } + } if (CS(this).teamkill_soundtime && time > CS(this).teamkill_soundtime) { @@ -2609,27 +2698,32 @@ void PlayerPreThink (entity this) void DrownPlayer(entity this) { - if(IS_DEAD(this) || game_stopped || time < game_starttime) + if(IS_DEAD(this) || game_stopped || time < game_starttime || this.vehicle + || STAT(FROZEN, this) || this.watertype != CONTENT_WATER) + { + STAT(AIR_FINISHED, this) = 0; return; + } - if (this.waterlevel != WATERLEVEL_SUBMERGED || this.vehicle) + if (this.waterlevel != WATERLEVEL_SUBMERGED) { - if(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 = time + autocvar_g_balance_contents_drowndelay; - this.air_finished_stat = 0; + STAT(AIR_FINISHED, this) = 0; } - else if (this.air_finished < time) - { // drown! - if (this.pain_finished < time) - { - Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, DMG_NOWEP, this.origin, '0 0 0'); - this.pain_finished = time + 0.5; + else + { + 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) + { + Damage (this, NULL, NULL, autocvar_g_balance_contents_playerdamage_drowning * autocvar_g_balance_contents_damagerate, DEATH_DROWN.m_id, DMG_NOWEP, this.origin, '0 0 0'); + this.pain_finished = time + 0.5; + } } - this.air_finished_stat = this.air_finished; } - else - this.air_finished_stat = this.air_finished; } .bool move_qcphysics; @@ -2660,21 +2754,21 @@ void PlayerPostThink (entity this) { Player_Physics(this); - if (sv_maxidle > 0) + if (autocvar_sv_maxidle > 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_spectatorsareidle) { int totalClients = 0; - if(sv_maxidle_slots > 0) + if(autocvar_sv_maxidle_slots > 0) { - FOREACH_CLIENT(IS_REAL_CLIENT(it) || sv_maxidle_slots_countbots, + FOREACH_CLIENT(IS_REAL_CLIENT(it) || autocvar_sv_maxidle_slots_countbots, { ++totalClients; }); } - if (sv_maxidle_slots > 0 && (maxclients - totalClients) > sv_maxidle_slots) + if (autocvar_sv_maxidle_slots > 0 && (maxclients - totalClients) > autocvar_sv_maxidle_slots) { /* do nothing */ } else if (time - CS(this).parm_idlesince < 1) // instead of (time == this.parm_idlesince) to support sv_maxidle <= 10 { @@ -2686,8 +2780,8 @@ 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 + float timeleft = ceil(autocvar_sv_maxidle - (time - CS(this).parm_idlesince)); + if (timeleft == min(10, autocvar_sv_maxidle - 1)) { // - 1 to support sv_maxidle <= 10 if (!CS(this).idlekick_lasttimeleft) Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_DISCONNECT_IDLING, timeleft); } @@ -2697,9 +2791,8 @@ void PlayerPostThink (entity this) return; } else if (timeleft <= 10) { - if (timeleft != CS(this).idlekick_lasttimeleft) { - Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft)); - } + if (timeleft != CS(this).idlekick_lasttimeleft) + Send_Notification(NOTIF_ONE, this, MSG_ANNCE, Announcer_PickNumber(CNT_IDLE, timeleft)); CS(this).idlekick_lasttimeleft = timeleft; } } @@ -2712,6 +2805,9 @@ void PlayerPostThink (entity this) this.solid = SOLID_NOT; this.takedamage = DAMAGE_NO; set_movetype(this, MOVETYPE_NONE); + CS(this).teamkill_complain = 0; + CS(this).teamkill_soundtime = 0; + CS(this).teamkill_soundsource = NULL; } if (IS_PLAYER(this)) { @@ -2868,10 +2964,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc var .float flood_field = floodcontrol_chat; if(floodcontrol && source) { - float flood_spl; - float flood_burst; - float flood_lmax; - float lines; + float flood_spl, flood_burst, flood_lmax; if(privatesay) { flood_spl = autocvar_g_chat_flood_spl_tell; @@ -2901,7 +2994,7 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc { getWrappedLine_remaining = msgstr; msgstr = ""; - lines = 0; + int lines = 0; while(getWrappedLine_remaining && (!flood_lmax || lines <= flood_lmax)) { msgstr = strcat(msgstr, " ", getWrappedLineLen(82.4289758859709, strlennocol)); // perl averagewidth.pl < gfx/vera-sans.width @@ -2958,11 +3051,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc sourcecmsgstr = cmsgstr; } - if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer)) + if (!privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped + && (teamsay || CHAT_NOSPECTATORS())) { - if (!game_stopped) - if (teamsay || (autocvar_g_chat_nospectators == 1) || (autocvar_g_chat_nospectators == 2 && !warmup_stage)) - teamsay = -1; // spectators + teamsay = -1; // spectators } if(flood) @@ -2993,11 +3085,10 @@ int Say(entity source, int teamsay, entity privatesay, string msgin, bool floodc ret = 1; } - if (privatesay && source && !(IS_PLAYER(source) || source.caplayer)) + if (privatesay && source && !(IS_PLAYER(source) || source.caplayer) && !game_stopped + && (IS_PLAYER(privatesay) || privatesay.caplayer) && CHAT_NOSPECTATORS()) { - 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 + ret = -1; // just hide the message completely } MUTATOR_CALLHOOK(ChatMessage, source, ret); @@ -3117,7 +3208,7 @@ void PM_UpdateButtons(entity this, entity store) store.ping_movementloss = this.ping_movementloss; store.v_angle = this.v_angle; - store.movement = (typing) ? '0 0 0' : this.movement; + store.movement = this.movement; } NET_HANDLE(fpsreport, bool)