From ccb892909acc889e0333db75deb7b49cc315b9c3 Mon Sep 17 00:00:00 2001 From: cypress Date: Mon, 24 Jun 2024 21:29:49 -0700 Subject: [PATCH] SERVER: Last Stand and Revive Overhaul --- progs/ssqc.src | 5 +- source/client/defs/custom.qc | 5 +- source/client/hud.qc | 56 +- source/client/main.qc | 26 +- source/server/clientfuncs.qc | 11 +- source/server/damage.qc | 219 +------- source/server/defs/custom.qc | 2 - source/server/player/last_stand.qc | 505 ++++++++++++++++++ .../{player.qc => player/player_core.qc} | 74 ++- .../{entities => player}/spawn_points.qc | 2 +- source/server/weapons/weapon_core.qc | 128 +---- 11 files changed, 620 insertions(+), 413 deletions(-) create mode 100644 source/server/player/last_stand.qc rename source/server/{player.qc => player/player_core.qc} (95%) rename source/server/{entities => player}/spawn_points.qc (99%) diff --git a/progs/ssqc.src b/progs/ssqc.src index 883660e..5952375 100644 --- a/progs/ssqc.src +++ b/progs/ssqc.src @@ -30,12 +30,13 @@ main.qc utilities/weapon_utilities.qc utilities/game_restart.qc utilities/command_parser.qc -player.qc +player/player_core.qc damage.qc +player/spawn_points.qc +player/last_stand.qc entities/sub_functions.qc entities/sounds.qc entities/triggers.qc -entities/spawn_points.qc entities/explosive_barrel.qc entities/teleporter.qc entities/map_entities.qc diff --git a/source/client/defs/custom.qc b/source/client/defs/custom.qc index cb99020..00fd036 100644 --- a/source/client/defs/custom.qc +++ b/source/client/defs/custom.qc @@ -101,10 +101,7 @@ var struct revive_s { float draw; float timer; float state; - float org[3]; -} revive_icons[4]; - -float active_revive_icons; +} revive_icons[4]; // MAX_CLIENTS float weaponframetime; float weapon2frametime; diff --git a/source/client/hud.qc b/source/client/hud.qc index 04c2836..eda9c1c 100644 --- a/source/client/hud.qc +++ b/source/client/hud.qc @@ -1724,23 +1724,47 @@ void(float width, float height) HUD_PlayerNames = void(float width, float height) HUD_ReviveIcons = { - for (float i = 0; i < active_revive_icons; i++) { - if (revive_icons[i].draw == true) { - revive_icons[i].timer += frametime; - vector revive_origin; - revive_origin_x = revive_icons[i].org[0]; - revive_origin_y = revive_icons[i].org[1]; - revive_origin_z = revive_icons[i].org[2]; - vector screen_position = project(revive_origin); - screen_position_x -= (32)/2; + for (float i = 3; i >= 0; i = i - 1) { + float player_index = i; - if (screen_position_z > 0) { - // being revived - if (revive_icons[i].state == 2) - drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1,1], 1); - else { - drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1 - (revive_icons[i].timer/30),0], 1); - } + // Don't render our own Icon. + if (player_index + 1 == getstatf(STAT_PLAYERNUM)) + continue; + + // Player is not in Last Stand. + if (revive_icons[player_index].state <= 0) + continue; + + // Player is not being actively revived, so the + // Revive Icon should grow more and more red over time. + if (revive_icons[player_index].state == 1) + revive_icons[player_index].timer += frametime; + + float player_number = getplayerkeyfloat(i, "viewentity"); + + //string text = getplayerkeyvalue(i, "name"); + entity client = findfloat(world, playernum, player_number); + + // Client does not exist/is spectating. + if (client == world || client.movetype == MOVETYPE_BOUNCE) + continue; + + entity plr = findfloat(world, playernum, player_number); + vector player_origin = plr.origin + '0 0 32'; + vector screen_position = project(player_origin); + screen_position_x -= (32)/2; + + if (player_origin == '0 0 32') + continue; + + if (screen_position_z > 0) { + // Client is actively being revived, render white. + if (revive_icons[i].state == 2) { + drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1,1], 1); + } + // Draw with a yellow->red hue. + else { + drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1 - (revive_icons[i].timer/30),0], 1); } } } diff --git a/source/client/main.qc b/source/client/main.qc index b8229a6..79bd7a1 100644 --- a/source/client/main.qc +++ b/source/client/main.qc @@ -1072,28 +1072,20 @@ noref void() CSQC_Parse_Event = } break; case EVENT_REVIVECHANGE: - float revivechange_id = readbyte(); + float revivechange_player_index = readbyte() - 1; // playernum starts at one. float state = readbyte(); - revive_icons[revivechange_id].state = state; + revive_icons[revivechange_player_index].state = state; break; case EVENT_REVIVEON: - float reviveon_id = readbyte(); - revive_icons[reviveon_id].org[0] = readcoord(); - revive_icons[reviveon_id].org[1] = readcoord(); - revive_icons[reviveon_id].org[2] = readcoord(); - revive_icons[reviveon_id].state = 1; - revive_icons[reviveon_id].draw = true; - active_revive_icons++; + float reviveon_player_index = readbyte() - 1; // playernum starts at one. + revive_icons[reviveon_player_index].state = 1; + revive_icons[reviveon_player_index].draw = true; break; case EVENT_REVIVEOFF: - float reviveoff_id = readbyte(); - revive_icons[reviveoff_id].org[0] = 0; - revive_icons[reviveoff_id].org[1] = 0; - revive_icons[reviveoff_id].org[2] = 0; - revive_icons[reviveoff_id].state = 0; - revive_icons[reviveoff_id].timer = 0; - revive_icons[reviveoff_id].draw = false; - active_revive_icons--; + float reviveoff_player_index = readbyte() - 1; // playernum starts at one. + revive_icons[reviveoff_player_index].state = 0; + revive_icons[reviveoff_player_index].timer = 0; + revive_icons[reviveoff_player_index].draw = false; break; case EVENT_MAPTYPE: map_compatibility_mode = readbyte(); diff --git a/source/server/clientfuncs.qc b/source/server/clientfuncs.qc index a7a4d88..09ad3a9 100644 --- a/source/server/clientfuncs.qc +++ b/source/server/clientfuncs.qc @@ -155,29 +155,26 @@ void(float round) FTE_IncrementRound = #endif // FTE -void(float index, float state) ChangeReviveIconState = +void(float player_index, float state) ChangeReviveIconState = { #ifdef FTE WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EVENT_REVIVECHANGE); - WriteByte(MSG_MULTICAST, index); + WriteByte(MSG_MULTICAST, player_index); WriteByte(MSG_MULTICAST, state); multicast('0 0 0', MULTICAST_ALL); #endif // FTE } -void(float index, vector org) EnableReviveIcon = +void(float player_index) EnableReviveIcon = { #ifdef FTE WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, EVENT_REVIVEON); - WriteByte(MSG_MULTICAST, index); - WriteCoord(MSG_MULTICAST, org_x); - WriteCoord(MSG_MULTICAST, org_y); - WriteCoord(MSG_MULTICAST, org_z); + WriteByte(MSG_MULTICAST, player_index); multicast('0 0 0', MULTICAST_ALL); #endif // FTE diff --git a/source/server/damage.qc b/source/server/damage.qc index 099a8d5..4525e00 100644 --- a/source/server/damage.qc +++ b/source/server/damage.qc @@ -26,6 +26,7 @@ */ void (float achievement_id, optional entity who) GiveAchievement; +void(entity client) LastStand_Begin; #define DMG_SCORE_HEADSHOT 100 // Death by Headshot #define DMG_SCORE_MELEE 130 // Death by Melee @@ -158,213 +159,7 @@ void() EndGameSetup = return; } -// rec_downed is used as a recursive loop where we consistently check to see if ALL players are downed -// if they aren't dead, we keep looping until our OWN death (45 seconds, so 300 loops) -void() rec_downed = -{ - if (!self.beingrevived) - self.downedloop += 1; - - if (self.downedloop >= 30) { - DisableReviveIcon(self.electro_targeted); - revive_index--; - startspectate(); - return; - } - - float gotalive = PollPlayersAlive(); - - if (!gotalive && !self.progress_bar) { - EndGameSetup(); - return; - } - - self.think = rec_downed; - self.nextthink = time + 1; -} - float() push_away_zombies; -void() GetDown = -{ - // 'Pro Gamer Move' achievement. - if (rounds <= 1 && self.weapons[0].weapon_magazine == 0 && - self.weapons[0].weapon_magazine_left == 0 && self.weapons[0].weapon_reserve == 0 && - self.weapons[1].weapon_magazine == 0 && self.weapons[1].weapon_magazine_left == 0 && - self.weapons[1].weapon_reserve == 0) { - GiveAchievement(9, self); - } - - // Aim out to reset zoom values - W_AimOut(); - -#ifdef FTE - - self.viewzoom = 1; - -#endif // FTE - - // Make any zombies inside of the player's bounding box leave - push_away_zombies(); - - // Force the player to prone. - Player_SetStance(self, PLAYER_STANCE_PRONE, false); - - // Get rid of Mule Kick Weapon - for(float i = 0; i < MAX_PLAYER_WEAPONS; i++) { - if (self.weapons[i].is_mulekick_weapon == true) { - Weapon_RemoveWeapon(i); - Weapon_SetActiveInSlot(0, false); - } - } - - // Calculate the loss in points, take away points from downed Player. - float point_difference; - point_difference = self.points; - point_difference -= 10*rint((self.points*0.95)/10); - Player_RemoveScore(self, point_difference); - - self.requirespower = point_difference; - -#ifdef FTE - - // FTE-Specific: Broadcast that the player has downed to everyone. - FTE_BroadcastMessage(world, CSQC_BROADCAST_PLAYERDOWNED, 3, self.playernum); - -#endif // FTE - - // Reset state - self.velocity = self.zoom = 0; - self.downed = true; - self.dive_delay = 0; - - float players_still_alive = PollPlayersAlive(); - - if ((player_count && !players_still_alive) || (!player_count && !(self.perks & P_REVIVE))) { - EndGameSetup(); - return; - } else { - self.health = 19; - } - - // Initiate Self-Revive on Solo - if ((self.perks & P_REVIVE) && !player_count) { - self.progress_bar = 10 + time; - self.progress_bar_time = 10; - self.progress_bar_percent = 1; - -#ifdef FTE - - // FTE-Specific: Broadcast to the player they're reviving themselves. - FTE_BroadcastMessage(self, CSQC_BROADCAST_REVIVINGPLAYER, 3, self.playernum); - -#endif // FTE - - - } - - // Take away weapons and Perks - self.perks = 0; - - self.weaponbk = self.weapon; - self.currentammobk = self.weapons[0].weapon_reserve; - self.currentmagbk = self.weapons[0].weapon_magazine; - self.currentmagbk2 = self.weapons[0].weapon_magazine_left; - - //Reset the tracker for the achievement "No Perks? No Problem" - self.ach_tracker_npnp = 0; - - // Reset Juggernog health - self.max_health = self.health = PLAYER_START_HEALTH; - - if(Weapon_PlayerHasWeapon(self, W_BIATCH, false) || - Weapon_PlayerHasWeapon(self, W_RAY, true) || - Weapon_PlayerHasWeapon(self, W_357, true)) { - float weapon_slot; - float total_ammo; - total_ammo = 0; - - weapon_slot = Weapon_PlayerHasWeapon(self, W_RAY, true); - if (weapon_slot == 0) weapon_slot = Weapon_PlayerHasWeapon(self, W_BIATCH, false); - if (weapon_slot == 0) weapon_slot = Weapon_PlayerHasWeapon(self, W_357, true); - - switch(weapon_slot) { - case 1: - total_ammo = self.weapons[0].weapon_magazine + self.weapons[0].weapon_magazine_left + self.weapons[0].weapon_reserve; - break; - case 2: - total_ammo = self.weapons[1].weapon_magazine + self.weapons[1].weapon_magazine_left + self.weapons[1].weapon_reserve; - Weapon_SwapWeapons(true); - break; - } - - self.weaponbk = self.weapon; - self.currentammobk = self.weapons[0].weapon_reserve; - self.currentmagbk = self.weapons[0].weapon_magazine; - self.currentmagbk2 = self.weapons[0].weapon_magazine_left; - - // If it's greater than the mag size, we can fill the magazine. - if (total_ammo > getWeaponMag(self.weapon) || total_ammo == 0) { - self.weapons[0].weapon_magazine = getWeaponMag(self.weapon); - - // subtract it from the total ammo - if (total_ammo != 0) - total_ammo -= self.weapons[0].weapon_magazine; - } else { - self.weapons[0].weapon_magazine = total_ammo; - total_ammo = 0; - } - - // Check for dual wield mag too - if (IsDualWeapon(self.weapon)) { - if (total_ammo > getWeaponMag(self.weapon) || total_ammo == 0) { - self.weapons[0].weapon_magazine_left = getWeaponMag(self.weapon); - - // subtract it from the total ammo - if (total_ammo != 0) - total_ammo -= self.weapons[0].weapon_magazine_left; - } else { - self.weapons[0].weapon_magazine_left = total_ammo; - total_ammo = 0; - } - } - - // Ray Gun has a special case where we DON'T fill its reserve - if (self.weapon != W_RAY && self.weapon != W_PORTER) { - // Now see if the reserve ammo is more than max downed capacity - if (total_ammo > getWeaponMag(self.weapon)*2) { - self.weapons[0].weapon_reserve = getWeaponMag(self.weapon)*2; - } else { - // It's not so just fill it - self.weapons[0].weapon_reserve = total_ammo; - } - } else { - self.weapons[0].weapon_reserve = 0; - } - } else { - if (!player_count) { - Weapon_AssignWeapon(0, W_BIATCH, 6, 12); - } else { - Weapon_AssignWeapon(0, W_COLT, 8, 16); - } - } - - // Play Switch Animation - self.weaponmodel = GetWeaponModel(self.weapon, 0); - - float startframe = GetFrame(self.weapon,TAKE_OUT_START); - float endframe = GetFrame(self.weapon,TAKE_OUT_END); - Set_W_Frame (startframe, endframe, 0, 0, 0, SUB_Null, self.weaponmodel, false, S_BOTH); - - // Spawn Revive Sprite in Co-Op - if (player_count) { - EnableReviveIcon(revive_index, self.origin + VIEW_OFS_HL); - self.electro_targeted = revive_index; - revive_index++; - } - - // Play Last Stand Animation - PAnim_GetDown(); -} void () GetUp = { @@ -405,10 +200,6 @@ void () GetUp = self.teslacount = 0; - if (!player_count) { - Player_AddScore(self, self.requirespower, false); - } - Weapon_AssignWeapon(0, self.weaponbk, self.currentmagbk, self.currentammobk); }; @@ -417,8 +208,7 @@ void(entity ent) CheckRevive = { if (self.invoke_revive) { GetUp(); - DisableReviveIcon(self.electro_targeted); - revive_index--; + DisableReviveIcon(self.playernum); self.invoke_revive = 0; self.firer = world; } @@ -561,10 +351,7 @@ void(entity victim, entity attacker, float damage, float d_style) DamageHandler // Was 20 for.. some reason. if (victim.health <= 1) { - old_self = self; - self = victim; - GetDown(); - self = old_self; + LastStand_Begin(victim); } } } diff --git a/source/server/defs/custom.qc b/source/server/defs/custom.qc index 3ff6b58..2e8321f 100644 --- a/source/server/defs/custom.qc +++ b/source/server/defs/custom.qc @@ -627,8 +627,6 @@ float G_PERKPOWER; .float renderamt; .vector rendercolor; -float revive_index; - #ifdef FTE .float last_solid; .float had_solid_modified; diff --git a/source/server/player/last_stand.qc b/source/server/player/last_stand.qc new file mode 100644 index 0000000..b0d85b8 --- /dev/null +++ b/source/server/player/last_stand.qc @@ -0,0 +1,505 @@ +/* + server/player/last_stand.qc + + Logic for Last Stand and Player Reviving. + + Copyright (C) 2021-2024 NZ:P Team + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to: + + Free Software Foundation, Inc. + 59 Temple Place - Suite 330 + Boston, MA 02111-1307, USA + +*/ + +#define LASTSTAND_DEATH_TIMER 30 + +void() W_TakeOut; + +// +// LastStand_SoloRevive() +// Gets the client out of last stand after solo revive +// timer has completed. +// +void() LastStand_SoloRevive = +{ + entity old_self = self; + self = self.owner; + GetUp(); + self = old_self; + + // Give the player their score back. + Player_AddScore(self.owner, self.owner.requirespower, false); + + // No need for us to exist anymore, goodbye! + remove(self); +}; + +// +// LastStand_InitiateSoloRevive(client) +// Kicks off the timer set when a player enters Last +// Stand with Quick Revive in solo. +// +void(entity client) LastStand_InitiateSoloRevive = +{ + +#ifdef FTE + + // FTE-Specific: Broadcast to the player they're reviving themselves. + FTE_BroadcastMessage(client, CSQC_BROADCAST_REVIVINGPLAYER, 3, client.playernum); + +#endif // FTE + + // Create a new entity to "watch" and be the revivee. + entity watcher = spawn(); + watcher.owner = client; + watcher.think = LastStand_SoloRevive; + watcher.nextthink = time + 10; +}; + +// +// LastStand_Penalize(client) +// Client entered Last Stand, remove their perks and +// other minor things. +// +void(entity client) LastStand_Penalize = +{ + // Reset bought Perk-A-Colas. + client.perks = 0; + + // Set Max Health to that of without Jugger-Nog. + client.max_health = client.health = PLAYER_START_HEALTH; + + // Get rid of Mule Kick Weapon + for(float i = 0; i < MAX_PLAYER_WEAPONS; i++) { + if (client.weapons[i].is_mulekick_weapon == true) { + entity old_self = self; + self = client; + Weapon_RemoveWeapon(i); + Weapon_SetActiveInSlot(0, false); + self = old_self; + } + } +}; + +// +// LastStand_AssignWeapon(client) +// Chooses what weapon to hand off to the player +// based on their loadout upon entering Last +// Stand. +// +void(entity client) LastStand_AssignWeapon = +{ + entity old_self = self; + self = client; + + client.weaponbk = self.weapon; + client.currentammobk = self.weapons[0].weapon_reserve; + client.currentmagbk = self.weapons[0].weapon_magazine; + client.currentmagbk2 = self.weapons[0].weapon_magazine_left; + + if(Weapon_PlayerHasWeapon(self, W_BIATCH, false) || + Weapon_PlayerHasWeapon(self, W_RAY, true) || + Weapon_PlayerHasWeapon(self, W_357, true)) { + float weapon_slot; + float total_ammo; + total_ammo = 0; + + weapon_slot = Weapon_PlayerHasWeapon(self, W_RAY, true); + if (weapon_slot == 0) weapon_slot = Weapon_PlayerHasWeapon(self, W_BIATCH, false); + if (weapon_slot == 0) weapon_slot = Weapon_PlayerHasWeapon(self, W_357, true); + + switch(weapon_slot) { + case 1: + total_ammo = self.weapons[0].weapon_magazine + self.weapons[0].weapon_magazine_left + self.weapons[0].weapon_reserve; + break; + case 2: + total_ammo = self.weapons[1].weapon_magazine + self.weapons[1].weapon_magazine_left + self.weapons[1].weapon_reserve; + Weapon_SwapWeapons(true); + break; + } + + self.weaponbk = self.weapon; + self.currentammobk = self.weapons[0].weapon_reserve; + self.currentmagbk = self.weapons[0].weapon_magazine; + self.currentmagbk2 = self.weapons[0].weapon_magazine_left; + + // If it's greater than the mag size, we can fill the magazine. + if (total_ammo > getWeaponMag(self.weapon) || total_ammo == 0) { + self.weapons[0].weapon_magazine = getWeaponMag(self.weapon); + + // subtract it from the total ammo + if (total_ammo != 0) + total_ammo -= self.weapons[0].weapon_magazine; + } else { + self.weapons[0].weapon_magazine = total_ammo; + total_ammo = 0; + } + + // Check for dual wield mag too + if (IsDualWeapon(self.weapon)) { + if (total_ammo > getWeaponMag(self.weapon) || total_ammo == 0) { + self.weapons[0].weapon_magazine_left = getWeaponMag(self.weapon); + + // subtract it from the total ammo + if (total_ammo != 0) + total_ammo -= self.weapons[0].weapon_magazine_left; + } else { + self.weapons[0].weapon_magazine_left = total_ammo; + total_ammo = 0; + } + } + + // Ray Gun has a special case where we DON'T fill its reserve + if (self.weapon != W_RAY && self.weapon != W_PORTER) { + // Now see if the reserve ammo is more than max downed capacity + if (total_ammo > getWeaponMag(self.weapon)*2) { + self.weapons[0].weapon_reserve = getWeaponMag(self.weapon)*2; + } else { + // It's not so just fill it + self.weapons[0].weapon_reserve = total_ammo; + } + } else { + self.weapons[0].weapon_reserve = 0; + } + } else { + if (!player_count) { + Weapon_AssignWeapon(0, W_BIATCH, 6, 12); + } else { + Weapon_AssignWeapon(0, W_COLT, 8, 16); + } + } + + // Play Switch Animation + self.weaponmodel = GetWeaponModel(self.weapon, 0); + + float startframe = GetFrame(self.weapon,TAKE_OUT_START); + float endframe = GetFrame(self.weapon,TAKE_OUT_END); + Set_W_Frame (startframe, endframe, 0, 0, 0, SUB_Null, self.weaponmodel, false, S_BOTH); + + self = old_self; +}; + +// +// LastStand_KillPlayer(client) +// Player has bled out, force them into +// a spectator position. +// +void(entity client) LastStand_KillPlayer = +{ + entity old_self = self; + self = client; + + DisableReviveIcon(self.playernum); + startspectate(); + + self = old_self; +}; + +// +// LastStand_ReviveTriggerFollow() +// Updates the position of the revive trigger +// to always track it's owner (downed client), +// also handles bleed-out checks. +// +void() LastStand_ReviveTriggerFollow = +{ + setorigin(self, self.owner.origin); + self.nextthink = time + 1; + + // Iterate our death timer if we're not being revived. + if (self.owner.beingrevived == false) + self.downed++; + + if (self.downed >= LASTSTAND_DEATH_TIMER) { + // Kill our owner. + LastStand_KillPlayer(self.owner); + + // Kill ourselves. + remove(self); + } +}; + +// +// LastStand_UnlinkRevivee(client) +// Reviving has stopped, revert the state of +// both clients where necessary. +// +void(entity client) LastStand_UnlinkRevivee = +{ + // We can move again. + client.speed_penalty = client.speed_penalty_time = 0; + + // We're no longer being revived. + client.beingrevived = false; + + // Remove our broadcast messages for visual feedback. +#ifdef FTE + + FTE_BroadcastMessage(client, CSQC_BROADCAST_NONE, 0, client.playernum); + + if (client.firer != world) + FTE_BroadcastMessage(client.firer, CSQC_BROADCAST_NONE, 0, client.playernum); + +#endif // FTE + + if (client.firer != world) { + entity old_self = self; + self = client.firer; + + // The previous revivee should deploy their active weapon. + W_TakeOut(); + + // We should also reset their progress bar state. + Player_RemoveProgressBar(self); + + self = old_self; + } + + // We no longer have an active revivee. + client.firer.firer = world; + client.firer = world; + + // Revert our revive indicator. + ChangeReviveIconState(client.playernum, 1); +}; + +// +// LastStand_LinkRevivee(downed_client, revivee) +// Reviving has been initiated, link the two clients to +// one another for state tracking. +// +void(entity downed_client, entity revivee) LastStand_LinkRevivee = +{ + // We are, in fact, being revived. + downed_client.beingrevived = true; + + // Link the downed team mate to the client who + // initiated the revive. + downed_client.firer = revivee; + revivee.firer = downed_client; + + // Downed player should no longer be able to crawl + downed_client.speed_penalty = 0.01; + downed_client.speed_penalty_time = time + 100; + + // Set our revive indicator to white. + ChangeReviveIconState(downed_client.playernum, 2); + +#ifdef FTE + + // FTE-Specific: Broadcast to the downed player they're being revived. + FTE_BroadcastMessage(downed_client, CSQC_BROADCAST_BEINGREVIVED, 2, revivee.playernum); + + // FTE-Specific: Broadcast to revivee that they are reviving the downed player. + FTE_BroadcastMessage(revivee, CSQC_BROADCAST_REVIVINGPLAYER, 2, downed_client.playernum); + +#endif // FTE + + // Initiate an animation for morphine. + entity old_self = self; + self = revivee; + Set_W_Frame (0, 21, 0, 0, SPRINT, SUB_Null, "models/weapons/morphine/v_morphine.mdl", false, S_RIGHT); + self = old_self; +}; + +// +// LastStand_TouchReviveTrigger() +// Contact made with a downed player's revive trigger, +// handles reviving logic. +// +void() LastStand_TouchReviveTrigger = +{ + // Only trigger for free players, and not ourself. + if (other.classname != "player" || other == self.owner) + return; + + // If the team mate is actively being revived, and we are not the one + // reviving them, break out. + if (self.owner.beingrevived == true && self.owner.firer != other) + return; + + // Is the client touching us down, or are they not facing us, or buying something? + if (other.downed || !PlayerIsLooking(other, self.owner) || other.isBuying) { + // Were they in the process of reviving us previously? + if (self.owner.firer == other) { + // Unlink us. + LastStand_UnlinkRevivee(self.owner); + } + return; + } + + // No one is actively reviving the downed team mate. + if (self.owner.beingrevived == false) { + // Hold F to revive... + useprint(other, 13, 0, 0); + + // Kick-off the revive sequence. + if (other.button7) { + // Revive time should be significantly shorter with + // Quick Revive. + float revive_time = 4; + if (other.perks & P_REVIVE) + revive_time = 2; + + // Link us together! + LastStand_LinkRevivee(self.owner, other); + other.fire_delay = other.fire_delay2 = other.reload_delay = other.reload_delay2 = other.knife_delay = time + revive_time; // FIXME + + // Use ltime on the revive trigger to know when the revive is finished + self.ltime = time + revive_time; + + // Display progress bar for the revivee. + Player_InitiateProgressBar(other, revive_time); + } + return; + } + + // Revivee has released the revive button. + if (!self.owner.firer.button7) { + LastStand_UnlinkRevivee(self.owner); + return; + } + + // Reviving has been executed for the full duration.. + if (self.ltime < time) { + // Reward the revivee with the points the downed client lost + Player_AddScore(other, self.owner.requirespower, false); + + entity old_self = self; + self = self.owner; + GetUp(); + self = old_self; + + // Unlink the revivee, no need to stay connected. + LastStand_UnlinkRevivee(self.owner); + + // Remove the revive icon from above the previously downed client + DisableReviveIcon(self.owner.playernum); + + // No reason for the trigger to exist anymore, goodbye! + remove(self); + } +}; + +// +// LastStand_SpawnReviveTrigger(client) +// Spawns a trigger to follow a client for reviving. +// +void(entity client) LastStand_SpawnReviveTrigger = +{ + entity revive_trigger = spawn(); + revive_trigger.owner = client; + revive_trigger.classname = "revive_trigger"; + revive_trigger.think = LastStand_ReviveTriggerFollow; + revive_trigger.nextthink = time + 1; + revive_trigger.touch = LastStand_TouchReviveTrigger; + revive_trigger.solid = SOLID_TRIGGER; + + setorigin(revive_trigger, client.origin); + setsize(revive_trigger, '-52 -52 -16', '52 52 -4'); +}; + +// +// LastStand_Begin(client) +// Player has gone down, oh noes! Kicks off their +// state changes and decides what the game should +// do next. +// +void(entity client) LastStand_Begin = +{ + entity old_self = self; + self = client; + + // 'Pro Gamer Move' achievement. + if (rounds <= 1 && self.weapons[0].weapon_magazine == 0 && + self.weapons[0].weapon_magazine_left == 0 && self.weapons[0].weapon_reserve == 0 && + self.weapons[1].weapon_magazine == 0 && self.weapons[1].weapon_magazine_left == 0 && + self.weapons[1].weapon_reserve == 0) { + GiveAchievement(9, self); + } + + // Aim out to reset zoom values + W_AimOut(); + +#ifdef FTE + + self.viewzoom = 1; + +#endif // FTE + + // Make any zombies inside of the player's bounding box leave + push_away_zombies(); + + // Force the player to prone. + Player_SetStance(self, PLAYER_STANCE_PRONE, false); + + // Calculate the loss in points, take away points from downed Player. + float point_difference; + point_difference = self.points; + point_difference -= 10*rint((self.points*0.95)/10); + Player_RemoveScore(self, point_difference); + self.requirespower = point_difference; + + // Reset state + self.velocity = self.zoom = 0; + self.downed = true; + self.dive_delay = 0; + + // End the game if no one else is alive. + float players_still_alive = PollPlayersAlive(); + if ((player_count && !players_still_alive) || (!player_count && !(self.perks & P_REVIVE))) { + EndGameSetup(); + return; + } + +#ifdef FTE + + // FTE-Specific: Broadcast that the player has downed to everyone. + FTE_BroadcastMessage(world, CSQC_BROADCAST_PLAYERDOWNED, 3, self.playernum); + +#endif // FTE + + self.health = 19; + + // Initiate Self-Revive on Solo + if ((self.perks & P_REVIVE) && !player_count) { + LastStand_InitiateSoloRevive(self); + } + // Spawn the Revive Trigger + else { + LastStand_SpawnReviveTrigger(self); + } + + // Reset the tracker for the achievement "No Perks? No Problem" + self.ach_tracker_npnp = 0; + + // Take away Perks, Mule Kick weapon, etc. + LastStand_Penalize(self); + + // Assign the Player a temporary weapon to use while in Last Stand. + LastStand_AssignWeapon(self); + + // Spawn Revive Sprite in Co-Op + if (player_count) { + EnableReviveIcon(self.playernum); + } + + // Play Last Stand Animation + PAnim_GetDown(); + + self = old_self; +}; \ No newline at end of file diff --git a/source/server/player.qc b/source/server/player/player_core.qc similarity index 95% rename from source/server/player.qc rename to source/server/player/player_core.qc index 3987f92..b557e1d 100644 --- a/source/server/player.qc +++ b/source/server/player/player_core.qc @@ -1,5 +1,5 @@ /* - server/player.qc + server/player/player_core.qc Various stuff done for the player, including per-frame functions like PlayerPreThink and PlayerPostThink, also client specific @@ -30,7 +30,7 @@ void(entity e) Light_None; void() Spawns_Init; void() SUB_UseTargets; -void() rec_downed; +void(entity client) LastStand_UnlinkRevivee; #define PLAYER_START_HEALTH 100 @@ -208,7 +208,7 @@ void() PAnim_GetDown1 =[ 2, PAnim_GetDown2 ] {self.frame = 33;}; void() PAnim_GetDown2 =[ 3, PAnim_GetDown3 ] {self.frame = 34;}; void() PAnim_GetDown3 =[ 4, PAnim_GetDown4 ] {self.frame = 35;}; void() PAnim_GetDown4 =[ 5, PAnim_GetDown5 ] {self.frame = 36;}; -void() PAnim_GetDown5 =[ 6, SUB_Null ] {self.frame = 37; rec_downed();}; +void() PAnim_GetDown5 =[ 6, SUB_Null ] {self.frame = 37;}; // Firing, while in Last Stand void() PAnim_LastFire =[ 1, PAnim_LastFire1 ] {self.frame = 36; self.tp_anim_time = time + 0.25;} @@ -393,6 +393,41 @@ void(entity who, float preferred_stance, float play_animation) Player_SetStance } } +// +// Player_InitiateProgressBar(who, duration) +// Kicks-off progress bar for the server to interpret +// and render on the client side. +// +void(entity who, float duration) Player_InitiateProgressBar = +{ + who.progress_bar_time = duration; + who.progress_bar = who.progress_bar_time + time; +}; + +// +// Player_RemoveProgressBar(who) +// Resets progress bar status for client. +// +void(entity who) Player_RemoveProgressBar = +{ + who.progress_bar = who.progress_bar_time = who.progress_bar_percent = 0; +}; + +// +// Player_UpdateProgressBar() +// Increments progress bar percentage for client rendering updates. +// +void() Player_UpdateProgressBar = +{ + if (self.progress_bar_time) { + float progress_bar_remaining = self.progress_bar - time; + self.progress_bar_percent = invertfloat(progress_bar_remaining / self.progress_bar_time); + + if (self.progress_bar_percent >= 0.95) + Player_RemoveProgressBar(self); + } +}; + void() PlayerJump = { if (!(self.flags & FL_ONGROUND) @@ -672,26 +707,17 @@ void() PlayerPostThink = self.health_was_very_low = false; } } - - if (self.progress_bar) { - if (self.progress_bar < time) { - if (self.downed) - GetUp(); - - if (self.reviving) - self.revived = 1; - - self.progress_bar = 0; - self.progress_bar_time = 0; - self.progress_bar_percent = 0; - - - } else { - float remaining = self.progress_bar - time; - self.progress_bar_percent = invertfloat((remaining / self.progress_bar_time)); + + if (self.downed && self.firer != world) { + // Has our revivee left the vicinity of our revive trigger? + if (fabs(vlen(self.origin - self.firer.origin)) >= 72) { + LastStand_UnlinkRevivee(self); } } + // Keep track of any active progress bars. + Player_UpdateProgressBar(); + if (self.sprinting) { self.sprint_timer = self.sprint_duration + (time - self.sprint_start_time); @@ -1012,9 +1038,11 @@ void() ClientDisconnect = GameRestart_ResetPerkaColas(); - if (self.downed) { - DisableReviveIcon(self.electro_targeted); - revive_index--; + if (self.downed && self.firer != world) { + LastStand_UnlinkRevivee(self); + DisableReviveIcon(self.playernum); + } else if (self.firer != world) { + self.firer.beingrevived = false; } #ifdef FTE diff --git a/source/server/entities/spawn_points.qc b/source/server/player/spawn_points.qc similarity index 99% rename from source/server/entities/spawn_points.qc rename to source/server/player/spawn_points.qc index daeabf5..f1ca48c 100644 --- a/source/server/entities/spawn_points.qc +++ b/source/server/player/spawn_points.qc @@ -1,5 +1,5 @@ /* - server/entities/spawn_points.qc + server/player/spawn_points.qc Code for Player Spawn points. diff --git a/source/server/weapons/weapon_core.qc b/source/server/weapons/weapon_core.qc index 4e62eb3..e86a5cc 100644 --- a/source/server/weapons/weapon_core.qc +++ b/source/server/weapons/weapon_core.qc @@ -1340,8 +1340,9 @@ void() GrenadeExplode = } else { CallExplosion(self.origin); } - - SUB_Remove (); + + if (self.classname != "player") + SUB_Remove(); }; void() NadeStraighten = @@ -1768,129 +1769,6 @@ void() CheckPlayer = self.oldz = self.origin_z; } - - // - // Revive Sequence - // -#ifdef FTE - - entity ent; - ent = findradius(self.origin, 70); - - while(ent) - { - if(ent.classname == "player" && ent != self && ent.downed) - { - - // perform a trace to make sure they're always facing the revivee - vector source; - makevectors(self.angles); - source = self.origin - '0 0 12'; - traceline(source, source + v_forward*50, 0, self); - self.active_door = trace_ent; - - if (ent.beingrevived == false && self.active_door == ent && !self.downed) - useprint (self, 13, 0, 0); - - if (self.button7 && !ent.invoke_revive && self.active_door == ent && !self.downed) { - if (ent.beingrevived == true && ent.firer != self) - return; - - - #ifdef FTE - - // FTE-Specific: Broadcast to the player they're being revived. - FTE_BroadcastMessage(ent, CSQC_BROADCAST_BEINGREVIVED, 2, self.playernum); - - // FTE-Specific: Broadcast to client that they are reviving the downed player. - FTE_BroadcastMessage(self, CSQC_BROADCAST_REVIVINGPLAYER, 2, ent.playernum); - - #endif // FTE - - ent.speed_penalty = 0.01; - ent.speed_penalty_time = time + 100; - ent.beingrevived = true; - ent.firer = self; - - if (!self.reviving) { - ChangeReviveIconState(ent.electro_targeted, 2); - self.movetype = MOVETYPE_TOSS; - Set_W_Frame (0, 21, 0, 0, SPRINT, SUB_Null, "models/weapons/morphine/v_morphine.mdl", false, S_RIGHT); - - if !(self.perks & P_REVIVE) - self.progress_bar_time = 4; - else - self.progress_bar_time = 2; - - self.progress_bar = self.progress_bar_time + time; - self.progress_bar_percent = 1; - self.reviving = true; - } - - if (self.revived) { - self.movetype = MOVETYPE_WALK; - W_TakeOut(); - ent.invoke_revive = 1; - ent.beingrevived = false; - ent.speed_penalty_time = 0; - self.reviving = 0; - self.progress_bar = 0; - self.progress_bar_time = 0; - self.progress_bar_percent = 0; - self.revived = 0; - Player_AddScore(self, ent.requirespower, false); - - - #ifdef FTE - - // FTE-Specific: End Broadcast Messages on both ends - FTE_BroadcastMessage(ent, CSQC_BROADCAST_NONE, 0, self.playernum); - FTE_BroadcastMessage(self, CSQC_BROADCAST_NONE, 0, ent.playernum); - - #endif // FTE - - } - } - else if (self.downed || (!self.button7 && self.reviving) || (self.reviving && self.active_door != ent) || ent.classname == "disconnected") { - if (ent.classname != "disconnected") - ChangeReviveIconState(ent.electro_targeted, 1); - else - DisableReviveIcon(ent.electro_targeted); - self.movetype = MOVETYPE_WALK; - ent.beingrevived = false; - ent.speed_penalty_time = 0; - ent.firer = world; - W_TakeOut(); - self.progress_bar = 0; - self.progress_bar_time = 0; - self.progress_bar_percent = 0; - self.revived = 0; - self.reviving = 0; - - #ifdef FTE - - // FTE-Specific: End Broadcast Messages on both ends - FTE_BroadcastMessage(ent, CSQC_BROADCAST_NONE, 0, self.playernum); - FTE_BroadcastMessage(self, CSQC_BROADCAST_NONE, 0, ent.playernum); - - #endif // FTE - - } - } - else if(ent.classname == "player" && ent != self && !ent.downed) // small player push - { - vector push; - push = ent.origin - self.origin; - push_z = 0; - push = normalize(push) * 0.5; - - ent.velocity += push; - } - ent = ent.chain; - } - -#endif // FTE - } //