SERVER: Bouncing Betty Revamp

* Fix timing of priming sound.
* Sped up animation playback rate.
* Fix not playing "take out" animation after placing Betty.
* Decreases raise time from 0.35 seconds to 0.28 seconds.
* Betties rise with more force.
* Increase explosion effect dramatics.
* Betties no longer inflict self-damage.
* Betties can no longer be re-bought from wall chalk.
* Fixed betties using cost2 for their entity instead of cost.
* Move bouncing betty logic to separate source file.
This commit is contained in:
cypress 2023-09-10 10:39:38 -04:00
parent 973d60eb9b
commit bf4afd4ae3
8 changed files with 223 additions and 137 deletions

View file

@ -37,6 +37,7 @@
../source/server/weapons/tesla.qc ../source/server/weapons/tesla.qc
../source/server/weapons/flamethrower.qc ../source/server/weapons/flamethrower.qc
../source/server/weapons/grenade_launcher.qc ../source/server/weapons/grenade_launcher.qc
../source/server/weapons/bouncing_betty.qc
../source/server/weapons/weapon_core.qc ../source/server/weapons/weapon_core.qc
../source/server/entities/powerups.qc ../source/server/entities/powerups.qc

View file

@ -41,6 +41,7 @@
../source/server/weapons/tesla.qc ../source/server/weapons/tesla.qc
../source/server/weapons/flamethrower.qc ../source/server/weapons/flamethrower.qc
../source/server/weapons/grenade_launcher.qc ../source/server/weapons/grenade_launcher.qc
../source/server/weapons/bouncing_betty.qc
../source/server/weapons/weapon_core.qc ../source/server/weapons/weapon_core.qc
../source/server/entities/powerups.qc ../source/server/entities/powerups.qc

View file

@ -594,7 +594,9 @@ void(entity inflictor, entity attacker, float damage2, float mindamage, float ra
{ {
if(ent.classname == "player" && ent == attacker) // we don't want OUR explosives to harm other players.. if(ent.classname == "player" && ent == attacker) // we don't want OUR explosives to harm other players..
{ {
if (ent.perks & P_FLOP) if (ent.perks & P_FLOP) // PhD Flopper makes us immune to any explosive damage
final_damage = 0;
else if (inflictor.classname == "betty") // Self-inflicted betties don't do damage either.
final_damage = 0; final_damage = 0;
else else
{ {
@ -623,23 +625,22 @@ void(entity inflictor, entity attacker, float damage2, float mindamage, float ra
final_damage *= 60; final_damage *= 60;
} }
DamageHandler (ent, ent, final_damage, S_EXPLOSIVE); DamageHandler (ent, ent, final_damage, S_EXPLOSIVE);
// shake the camera on impact
vector distance;
distance = inflictor.angles - ent.angles;
// just to prevent radical punchangles
while(distance_x > 10 || distance_x < -10) {
distance_x /= 2;
}
while(distance_y > 10 || distance_y < -10) {
distance_y /= 2;
}
// apply
ent.punchangle_x = distance_x;
ent.punchangle_y = distance_y;
} }
// shake the camera on impact
vector distance;
distance = inflictor.angles - ent.angles;
// just to prevent radical punchangles
while(distance_x > 10 || distance_x < -10) {
distance_x /= 2;
}
while(distance_y > 10 || distance_y < -10) {
distance_y /= 2;
}
// apply
ent.punchangle_x = distance_x;
ent.punchangle_y = distance_y;
} }
else if (ent.classname == "explosive_barrel") else if (ent.classname == "explosive_barrel")
{ {

View file

@ -108,6 +108,11 @@ void() weapon_wall =
makevectors(self.angles); makevectors(self.angles);
setorigin(self.enemy, self.origin + v_right*-4); setorigin(self.enemy, self.origin + v_right*-4);
// Backwards compatibility for betties brokenly using
// cost2.
if (self.cost == 0 && self.weapon == W_BETTY)
self.cost = self.cost2;
// Prep it for usage. // Prep it for usage.
self.skin = 0; self.skin = 0;
self.frame = self.sequence; self.frame = self.sequence;
@ -248,10 +253,12 @@ void () WallWeapon_TouchTrigger =
} }
if (self.weapon == W_BETTY) if (self.weapon == W_BETTY)
{ {
if (other.secondary_grenades < 2) // Betties are a one-time purchase.
useprint (other, 3, self.cost2, self.weapon); if (other.grenades & 2)
else
return; return;
useprint (other, 4, self.cost, self.weapon);
if (!other.button7 || other.semiuse) if (!other.button7 || other.semiuse)
return; return;
if (other.points < self.cost2) if (other.points < self.cost2)

View file

@ -0,0 +1,173 @@
/*
server/weapons/bouncing_betty.qc
Core logic for the "Bouncing Betty" grenade.
Copyright (C) 2021-2023 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
*/
void() W_PlayTakeOut;
//
// Betty_Rise()
// Rise from the ground and explode.
//
void() Betty_Rise =
{
if (self.ltime < time) {
self.velocity = '0 0 0';
self.movetype = MOVETYPE_NONE;
self.think = SUB_Null;
GrenadeExplode();
}
self.nextthink = time + 0.05;
makevectors(self.angles);
self.velocity = v_up*100;
}
//
// Betty_Touch()
// Check if contact is with an enemy,
// trigger if so.
//
void() Betty_Touch =
{
// Only trigger if an enemy is making contact.
if (other.classname != "ai_zombie" && other.classname != "ai_dog")
return;
// Additional sanity check -- don't activate because of us.
if (other == self.owner || other.solid == SOLID_TRIGGER)
return;
// Next step is to rise!
self.think = Betty_Rise;
self.touch = SUB_Null;
self.nextthink = time + 0.1;
// Set a timer for when to explode.
self.ltime = time + 0.28;
}
//
// Betty_Drop()
// Spawns the Betty grenade and puts it
// on the ground.
//
void() Betty_Drop =
{
// Spawn it and set its attributes.
entity betty;
betty = spawn ();
betty.owner = self;
betty.grenade_delay = betty.owner.grenade_delay;
betty.owner.grenade_delay = 0;
betty.movetype = MOVETYPE_NOCLIP;
betty.solid = SOLID_TRIGGER;
betty.classname = "betty";
betty.velocity = v_forward*0;
betty.touch = Betty_Touch;
setmodel (betty, "models/weapons/grenade/g_betty.mdl");
setsize (betty, '-64 -64 -4', '64 64 32');
betty.origin = betty.owner.origin - self.view_ofs + '0 0 1';
// If we're in the air, just drop the betty to the ground
// cypress -- This felt a little whack so I checked World at War..
// ..and it does this too, so..
if (!(self.flags & FL_ONGROUND)) {
entity oldself;
oldself = self;
self = betty;
#ifdef FTE
droptofloor();
#else
droptofloor(0, 0);
#endif // FTE
self = oldself;
}
// Sink into the ground a bit
betty.origin += v_forward * 0;
betty.origin += v_up * -1;
setorigin (betty, betty.origin);
// Return the player's weapon to them
self.animend = W_PlayTakeOut;
self.callfuncat = 0;
self.isBuying = false;
}
//
// Betty_CheckForRelease()
// Holds the Bouncing Betty until the
// action button is released, then kicks
// off spawning the entity.
//
void() Betty_CheckForRelease =
{
// Release the Betty
if (!self.button3) {
if(self.grenade_delay < time)
self.grenade_delay = time + 0.05;
self.isBuying = true;
Set_W_Frame (18, 23, 0.3, 5, GRENADE, Betty_Drop, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
sound (self, CHAN_WEAPON, "sounds/weapons/grenade/throw.wav", 1, ATTN_NORM);
self.reload_delay2 = self.fire_delay2 = self.reload_delay = self.fire_delay = time + 0.4;
self.throw_delay = time + 0.9;
}
// Keep holding it
else {
self.isBuying = true;
Set_W_Frame (18, 18, 0, 0, GRENADE, Betty_CheckForRelease, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
}
}
//
// W_PrimeBetty()
// Priming/Init state for the Bouncing Betty,
// called inside of weapon_core.
//
void() W_PrimeBetty =
{
if (self.throw_delay > time || self.zoom || self.downed || self.secondary_grenades < 1 || self.isBuying)
return;
// Prevent the Player from Sprinting and also avoid issues with
// the equipment being completely cancelled..
if (self.sprinting)
W_SprintStop();
// Play the "prime" animation for the betty viewmodel.
Set_W_Frame (0, 18, 1, 0, GRENADE, Betty_CheckForRelease, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
// Subtract a betty from our inventory, set up delays, prevent use spam.
self.secondary_grenades -= 1;
self.reload_delay2 = self.fire_delay2 = self.throw_delay = self.reload_delay = self.fire_delay = time + 6;
self.seminade = true;
}

View file

@ -149,6 +149,10 @@ void () W_Frame_Update =
} }
} }
PlayWeaponSound(self.weapon, self.weapon_anim_type, FALSE, self.weaponframe); PlayWeaponSound(self.weapon, self.weapon_anim_type, FALSE, self.weaponframe);
// FIXME: We need a way to play sounds at specific frames for any viewmodel, not just weapons
if (self.weaponmodel == "models/weapons/grenade/v_betty.mdl" && self.weaponframe == 12)
sound (self, CHAN_WEAPON, "sounds/weapons/grenade/prime.wav", 1, ATTN_NORM);
return; return;
} }
else else

View file

@ -3,7 +3,7 @@
Core logic for the Wunderwaffe special weapon. Core logic for the Wunderwaffe special weapon.
Copyright (C) 2021-2022 NZ:P Team Copyright (C) 2021-2023 NZ:P Team
This program is free software; you can redistribute it and/or This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License modify it under the terms of the GNU General Public License

View file

@ -1403,14 +1403,23 @@ void() GrenadeExplode =
DamgageExplode (self, self.owner, 12000, 11000, 128); DamgageExplode (self, self.owner, 12000, 11000, 128);
else else
DamgageExplode (self, self.owner, 1200, 1000, 75); DamgageExplode (self, self.owner, 1200, 1000, 75);
#ifdef FTE #ifdef FTE
te_customflash(self.origin, 200, 300, '1 1 1'); te_customflash(self.origin, 200, 300, '1 1 1');
#endif // FTE #endif // FTE
CallExplosion(self.origin); // Really stupid hack: For betties, let's dramatically increase
// the explosion effect.
if (self.classname == "betty") {
CallExplosion(self.origin);
CallExplosion(self.origin);
CallExplosion(self.origin);
CallExplosion(self.origin);
} else {
CallExplosion(self.origin);
}
SUB_Remove (); SUB_Remove ();
}; };
@ -1433,79 +1442,6 @@ void() Velocity_reduce =
NadeStraighten(); NadeStraighten();
}; };
void() betty_do_rise =
{
if (self.ltime < time) {
self.velocity = '0 0 0';
self.movetype = MOVETYPE_NONE;
self.think = SUB_Null;
GrenadeExplode();
}
self.nextthink = time + 0.05;
makevectors(self.angles);
self.velocity = v_up*70;
}
void() betty_touch =
{
// Only trigger if an enemy is making contact.
if (other.classname != "ai_zombie" && other.classname != "ai_dog")
return;
if (other == self.owner || other.solid == SOLID_TRIGGER)
return;
self.think = betty_do_rise;
self.touch = SUB_Null;
self.nextthink = time + 0.1;
self.ltime = time + 0.35;
}
void() W_ThrowBetty =
{
entity betty;
betty = spawn ();
betty.owner = self;
betty.grenade_delay = betty.owner.grenade_delay;
betty.owner.grenade_delay = 0;
betty.movetype = MOVETYPE_NOCLIP;
betty.solid = SOLID_TRIGGER;
betty.classname = "betty";
betty.velocity = v_forward*0;
betty.touch = betty_touch;
setmodel (betty, "models/weapons/grenade/g_betty.mdl");
setsize (betty, '-64 -64 -4', '64 64 32');
betty.origin = betty.owner.origin - self.view_ofs + '0 0 1';
//if player isn't on ground, drop the betty to the floor.
if (!(self.flags & FL_ONGROUND)) {
local entity oldself;
oldself = self;
self = betty;
#ifdef FTE
droptofloor();
#else
droptofloor(0, 0);
#endif // FTE
self = oldself;
}
betty.origin += v_forward * 0;
setorigin (betty, betty.origin);
self.animend = ReturnWeaponModel;
self.callfuncat = 0;
self.isBuying = false;
}
void() W_ThrowGrenade = void() W_ThrowGrenade =
{ {
string modelname; string modelname;
@ -1549,7 +1485,7 @@ void() W_ThrowGrenade =
self.callfuncat = 0; self.callfuncat = 0;
self.isBuying = false; self.isBuying = false;
} else if (self.pri_grenade_state == 1) { } else if (self.pri_grenade_state == 1) {
W_ThrowBetty(); Betty_Drop();
} else { } else {
centerprint (other, "No grenadetype defined...\n"); centerprint (other, "No grenadetype defined...\n");
} }
@ -1566,30 +1502,10 @@ void() W_ThrowGrenade =
SetUpdate(self, UT_HUD, 6, 0, 0); SetUpdate(self, UT_HUD, 6, 0, 0);
} }
void() checkHoldB =
{
if (!self.button3)
{
if(self.grenade_delay < time)
self.grenade_delay = time + 0.05;
self.isBuying = true;
Set_W_Frame (18, 23, 0, 5, GRENADE, W_ThrowBetty, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
sound (self, CHAN_WEAPON, "sounds/weapons/grenade/throw.wav", 1, ATTN_NORM);
self.reload_delay2 = self.fire_delay2 = self.reload_delay = self.fire_delay = time + 0.4;
self.throw_delay = time + 0.9;
}
else
{
self.isBuying = true;
Set_W_Frame (18, 18, 0, 0, GRENADE, checkHoldB, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
}
}
void() checkHold = void() checkHold =
{ {
if (self.pri_grenade_state == 1) { if (self.pri_grenade_state == 1) {
checkHoldB(); Betty_CheckForRelease();
return; return;
} }
@ -1611,23 +1527,6 @@ void() checkHold =
} }
} }
void() W_Betty = {
if (self.throw_delay > time || self.zoom || self.downed || self.secondary_grenades < 1 || self.isBuying)
return;
// Prevent the Player from Sprinting and also avoid issues with
// the equipment being completely cancelled..
if (self.sprinting)
W_SprintStop();
Set_W_Frame (0, 18, 0, 0, GRENADE, checkHoldB, "models/weapons/grenade/v_betty.mdl", true, S_RIGHT);
sound (self, CHAN_WEAPON, "sounds/weapons/grenade/prime.wav", 1, ATTN_NORM);
self.secondary_grenades -= 1;
self.reload_delay2 = self.fire_delay2 = self.throw_delay = self.reload_delay = self.fire_delay = time + 6;
self.seminade = true;
}
void() W_Grenade = void() W_Grenade =
{ {
if (self.throw_delay > time || self.zoom || self.downed || self.primary_grenades < 1 || self.isBuying) if (self.throw_delay > time || self.zoom || self.downed || self.primary_grenades < 1 || self.isBuying)
@ -1777,7 +1676,7 @@ void () Impulse_Functions =
switch_nade(); switch_nade();
break; break;
case 33: case 33:
W_Betty(); W_PrimeBetty();
break; break;
case 26: case 26:
case 30: case 30:
@ -2301,7 +2200,7 @@ void () Weapon_Logic =
if (self.pri_grenade_state == 0) if (self.pri_grenade_state == 0)
W_Grenade(); W_Grenade();
else else
W_Betty(); W_PrimeBetty();
self.seminade = true; self.seminade = true;
} else if (!self.button3 && self.seminade) { } else if (!self.button3 && self.seminade) {
self.seminade = false; self.seminade = false;