mirror of
https://github.com/nzp-team/quakec.git
synced 2024-12-13 06:00:54 +00:00
505 lines
No EOL
14 KiB
C++
505 lines
No EOL
14 KiB
C++
/*
|
|
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;
|
|
}; |