Merge pull request #81 from nzp-team/cypress_laststand

SERVER: Last Stand and Revive Overhaul
This commit is contained in:
cypress 2024-06-24 21:30:26 -07:00 committed by GitHub
commit 9fc6f93e77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 620 additions and 413 deletions

View file

@ -30,12 +30,13 @@ main.qc
utilities/weapon_utilities.qc utilities/weapon_utilities.qc
utilities/game_restart.qc utilities/game_restart.qc
utilities/command_parser.qc utilities/command_parser.qc
player.qc player/player_core.qc
damage.qc damage.qc
player/spawn_points.qc
player/last_stand.qc
entities/sub_functions.qc entities/sub_functions.qc
entities/sounds.qc entities/sounds.qc
entities/triggers.qc entities/triggers.qc
entities/spawn_points.qc
entities/explosive_barrel.qc entities/explosive_barrel.qc
entities/teleporter.qc entities/teleporter.qc
entities/map_entities.qc entities/map_entities.qc

View file

@ -101,10 +101,7 @@ var struct revive_s {
float draw; float draw;
float timer; float timer;
float state; float state;
float org[3]; } revive_icons[4]; // MAX_CLIENTS
} revive_icons[4];
float active_revive_icons;
float weaponframetime; float weaponframetime;
float weapon2frametime; float weapon2frametime;

View file

@ -1724,27 +1724,51 @@ void(float width, float height) HUD_PlayerNames =
void(float width, float height) HUD_ReviveIcons = void(float width, float height) HUD_ReviveIcons =
{ {
for (float i = 0; i < active_revive_icons; i++) { for (float i = 3; i >= 0; i = i - 1) {
if (revive_icons[i].draw == true) { float player_index = i;
revive_icons[i].timer += frametime;
vector revive_origin; // Don't render our own Icon.
revive_origin_x = revive_icons[i].org[0]; if (player_index + 1 == getstatf(STAT_PLAYERNUM))
revive_origin_y = revive_icons[i].org[1]; continue;
revive_origin_z = revive_icons[i].org[2];
vector screen_position = project(revive_origin); // 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; screen_position_x -= (32)/2;
if (player_origin == '0 0 32')
continue;
if (screen_position_z > 0) { if (screen_position_z > 0) {
// being revived // Client is actively being revived, render white.
if (revive_icons[i].state == 2) if (revive_icons[i].state == 2) {
drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1,1], 1); drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1,1], 1);
}
// Draw with a yellow->red hue.
else { else {
drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1 - (revive_icons[i].timer/30),0], 1); drawpic(screen_position, "gfx/hud/revive_icon.tga", [32, 32, 1], [1,1 - (revive_icons[i].timer/30),0], 1);
} }
} }
} }
} }
}
void(float width, float height) HUD_StopWatch = void(float width, float height) HUD_StopWatch =
{ {

View file

@ -1076,28 +1076,20 @@ noref void() CSQC_Parse_Event =
} }
break; break;
case EVENT_REVIVECHANGE: case EVENT_REVIVECHANGE:
float revivechange_id = readbyte(); float revivechange_player_index = readbyte() - 1; // playernum starts at one.
float state = readbyte(); float state = readbyte();
revive_icons[revivechange_id].state = state; revive_icons[revivechange_player_index].state = state;
break; break;
case EVENT_REVIVEON: case EVENT_REVIVEON:
float reviveon_id = readbyte(); float reviveon_player_index = readbyte() - 1; // playernum starts at one.
revive_icons[reviveon_id].org[0] = readcoord(); revive_icons[reviveon_player_index].state = 1;
revive_icons[reviveon_id].org[1] = readcoord(); revive_icons[reviveon_player_index].draw = true;
revive_icons[reviveon_id].org[2] = readcoord();
revive_icons[reviveon_id].state = 1;
revive_icons[reviveon_id].draw = true;
active_revive_icons++;
break; break;
case EVENT_REVIVEOFF: case EVENT_REVIVEOFF:
float reviveoff_id = readbyte(); float reviveoff_player_index = readbyte() - 1; // playernum starts at one.
revive_icons[reviveoff_id].org[0] = 0; revive_icons[reviveoff_player_index].state = 0;
revive_icons[reviveoff_id].org[1] = 0; revive_icons[reviveoff_player_index].timer = 0;
revive_icons[reviveoff_id].org[2] = 0; revive_icons[reviveoff_player_index].draw = false;
revive_icons[reviveoff_id].state = 0;
revive_icons[reviveoff_id].timer = 0;
revive_icons[reviveoff_id].draw = false;
active_revive_icons--;
break; break;
case EVENT_MAPTYPE: case EVENT_MAPTYPE:
map_compatibility_mode = readbyte(); map_compatibility_mode = readbyte();

View file

@ -155,29 +155,26 @@ void(float round) FTE_IncrementRound =
#endif // FTE #endif // FTE
void(float index, float state) ChangeReviveIconState = void(float player_index, float state) ChangeReviveIconState =
{ {
#ifdef FTE #ifdef FTE
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EVENT_REVIVECHANGE); WriteByte(MSG_MULTICAST, EVENT_REVIVECHANGE);
WriteByte(MSG_MULTICAST, index); WriteByte(MSG_MULTICAST, player_index);
WriteByte(MSG_MULTICAST, state); WriteByte(MSG_MULTICAST, state);
multicast('0 0 0', MULTICAST_ALL); multicast('0 0 0', MULTICAST_ALL);
#endif // FTE #endif // FTE
} }
void(float index, vector org) EnableReviveIcon = void(float player_index) EnableReviveIcon =
{ {
#ifdef FTE #ifdef FTE
WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET); WriteByte(MSG_MULTICAST, SVC_CGAMEPACKET);
WriteByte(MSG_MULTICAST, EVENT_REVIVEON); WriteByte(MSG_MULTICAST, EVENT_REVIVEON);
WriteByte(MSG_MULTICAST, index); WriteByte(MSG_MULTICAST, player_index);
WriteCoord(MSG_MULTICAST, org_x);
WriteCoord(MSG_MULTICAST, org_y);
WriteCoord(MSG_MULTICAST, org_z);
multicast('0 0 0', MULTICAST_ALL); multicast('0 0 0', MULTICAST_ALL);
#endif // FTE #endif // FTE

View file

@ -26,6 +26,7 @@
*/ */
void (float achievement_id, optional entity who) GiveAchievement; void (float achievement_id, optional entity who) GiveAchievement;
void(entity client) LastStand_Begin;
#define DMG_SCORE_HEADSHOT 100 // Death by Headshot #define DMG_SCORE_HEADSHOT 100 // Death by Headshot
#define DMG_SCORE_MELEE 130 // Death by Melee #define DMG_SCORE_MELEE 130 // Death by Melee
@ -158,213 +159,7 @@ void() EndGameSetup =
return; 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; 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 = void () GetUp =
{ {
@ -405,10 +200,6 @@ void () GetUp =
self.teslacount = 0; self.teslacount = 0;
if (!player_count) {
Player_AddScore(self, self.requirespower, false);
}
Weapon_AssignWeapon(0, self.weaponbk, self.currentmagbk, self.currentammobk); Weapon_AssignWeapon(0, self.weaponbk, self.currentmagbk, self.currentammobk);
}; };
@ -417,8 +208,7 @@ void(entity ent) CheckRevive =
{ {
if (self.invoke_revive) { if (self.invoke_revive) {
GetUp(); GetUp();
DisableReviveIcon(self.electro_targeted); DisableReviveIcon(self.playernum);
revive_index--;
self.invoke_revive = 0; self.invoke_revive = 0;
self.firer = world; self.firer = world;
} }
@ -561,10 +351,7 @@ void(entity victim, entity attacker, float damage, float d_style) DamageHandler
// Was 20 for.. some reason. // Was 20 for.. some reason.
if (victim.health <= 1) if (victim.health <= 1)
{ {
old_self = self; LastStand_Begin(victim);
self = victim;
GetDown();
self = old_self;
} }
} }
} }

View file

@ -627,8 +627,6 @@ float G_PERKPOWER;
.float renderamt; .float renderamt;
.vector rendercolor; .vector rendercolor;
float revive_index;
#ifdef FTE #ifdef FTE
.float last_solid; .float last_solid;
.float had_solid_modified; .float had_solid_modified;

View file

@ -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;
};

View file

@ -1,5 +1,5 @@
/* /*
server/player.qc server/player/player_core.qc
Various stuff done for the player, including per-frame functions Various stuff done for the player, including per-frame functions
like PlayerPreThink and PlayerPostThink, also client specific like PlayerPreThink and PlayerPostThink, also client specific
@ -30,7 +30,7 @@
void(entity e) Light_None; void(entity e) Light_None;
void() Spawns_Init; void() Spawns_Init;
void() SUB_UseTargets; void() SUB_UseTargets;
void() rec_downed; void(entity client) LastStand_UnlinkRevivee;
#define PLAYER_START_HEALTH 100 #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_GetDown2 =[ 3, PAnim_GetDown3 ] {self.frame = 34;};
void() PAnim_GetDown3 =[ 4, PAnim_GetDown4 ] {self.frame = 35;}; void() PAnim_GetDown3 =[ 4, PAnim_GetDown4 ] {self.frame = 35;};
void() PAnim_GetDown4 =[ 5, PAnim_GetDown5 ] {self.frame = 36;}; 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 // Firing, while in Last Stand
void() PAnim_LastFire =[ 1, PAnim_LastFire1 ] {self.frame = 36; self.tp_anim_time = time + 0.25;} 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 = void() PlayerJump =
{ {
if (!(self.flags & FL_ONGROUND) if (!(self.flags & FL_ONGROUND)
@ -673,25 +708,16 @@ void() PlayerPostThink =
} }
} }
if (self.progress_bar) { if (self.downed && self.firer != world) {
if (self.progress_bar < time) { // Has our revivee left the vicinity of our revive trigger?
if (self.downed) if (fabs(vlen(self.origin - self.firer.origin)) >= 72) {
GetUp(); LastStand_UnlinkRevivee(self);
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));
} }
} }
// Keep track of any active progress bars.
Player_UpdateProgressBar();
if (self.sprinting) { if (self.sprinting) {
self.sprint_timer = self.sprint_duration + (time - self.sprint_start_time); self.sprint_timer = self.sprint_duration + (time - self.sprint_start_time);
@ -1012,9 +1038,11 @@ void() ClientDisconnect =
GameRestart_ResetPerkaColas(); GameRestart_ResetPerkaColas();
if (self.downed) { if (self.downed && self.firer != world) {
DisableReviveIcon(self.electro_targeted); LastStand_UnlinkRevivee(self);
revive_index--; DisableReviveIcon(self.playernum);
} else if (self.firer != world) {
self.firer.beingrevived = false;
} }
#ifdef FTE #ifdef FTE

View file

@ -1,5 +1,5 @@
/* /*
server/entities/spawn_points.qc server/player/spawn_points.qc
Code for Player Spawn points. Code for Player Spawn points.

View file

@ -1341,6 +1341,7 @@ void() GrenadeExplode =
CallExplosion(self.origin); CallExplosion(self.origin);
} }
if (self.classname != "player")
SUB_Remove(); SUB_Remove();
}; };
@ -1768,129 +1769,6 @@ void() CheckPlayer =
self.oldz = self.origin_z; 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
} }
// //