/* server/clientfuncs.qc used for any sort of down, hit, etc that the player or entity experiences Copyright (C) 2021 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 */ #ifndef NX void (float achievement_id, optional entity who) GiveAchievement; #endif // NX void() Barrel_Hit; void() EndGame_Restart = { localcmd("restart"); } // Fade to black function, creates another think for restart void() EndGame_FadePrompt = { PromptLevelChange(time + 6, 2, self); #ifdef PC self.think = EndGame_Restart; #else self.think = Soft_Restart; #endif self.nextthink = time + 5; } //Actual endgame function, all zombies die, music plays void() EndGame = { local entity oldself; local entity who; self.health = 0; self.origin = '0 0 0'; setorigin (self, self.origin); self.velocity = '0 0 0'; sound (self, CHAN_AUTO, "sounds/music/end.wav", 1, ATTN_NORM); oldself = self; who = find(world,classname,"ai_zombie"); while(who != world) { if(who.health) { self = who; self.th_die(); self = oldself; } who = find(who,classname,"ai_zombie"); } self.think = EndGame_FadePrompt; self.nextthink = time + 33; } // removes revive icon from downed player heads, used as a recursive think function void() remove_revive = { if (self.owner.beingrevived) setmodel (self, "models/sprites/revive_white.spr"); else setmodel (self, "models/sprites/revive.spr"); if (!self.owner.downed || self.owner.isspec) SUB_Remove (); else { self.think = remove_revive; self.nextthink = time + 0.1; } } // when dead and other players exist and are alive, throw user into spectate mode void() startspectate = { if (!self.downed) return; if (self.beingrevived) { self.think = startspectate; self.nextthink = time + 0.1; return; } self.downedloop = 0; self.beingrevived = false; self.model = ""; setmodel(self, self.model); self.health = 100; self.weaponmodel = ""; self.weapon2model = ""; self.downed = 0; self.frame = 0; UpdateVmodel(self.weaponmodel, GetWepSkin(self.weapon)); UpdateV2model(self.weapon2model, GetWepSkin(self.weapon)); SpectatorSpawn(); } // searches for players that are alive given which clients have which playernumbers // Returns 1 if there IS someone in the world that's not downed float() PollPlayersAlive = { float i, gotalive; entity playerent; gotalive = 0; for (i = 1; i <= 4; i++) { playerent = findfloat(world, playernum, i); if (playerent) { if (!playerent.downed && !playerent.isspec) { gotalive = 1; break; } } } return gotalive; } // Endgamesetup -- think function for setting up the death of everyone void() EndGameSetup = { game_over = true; self.health = 10; self.think = EndGame; self.nextthink = time + 5; self.weapon = 0; self.currentammo = 0; self.currentmag = 0; self.weaponmodel = ""; self.weapon2model = ""; self.animend = SUB_Null; self.perks = 0; self.isspec = true; SetPerk(self, self.perks); SwitchWeapon(0); addmoney(self, -self.points, 0); addmoney(self, self.score, 0); 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 = { self.downedloop++; if (self.downedloop == 300) { startspectate(); return; } float gotalive = PollPlayersAlive(); if (!gotalive && !self.progress_bar) { EndGameSetup(); return; } self.think = rec_downed; self.nextthink = time + 0.1; } void() GetDown = { float startframe; float endframe; local string modelname; if (rounds <= 1 && self.currentmag == 0 && self.currentmag2 == 0 && self.currentammo == 0 && self.secondarymag == 0 && self.secondarymag2 == 0 && self.secondaryammo == 0) { GiveAchievement(9, self); } playdown(); switch(self.stance) { case 2: self.new_ofs_z = self.view_ofs_z - 42; self.stance = 0; break; case 1: self.new_ofs_z = self.view_ofs_z - 24; self.stance = 0; break; default: break; } // remove third weapon self.thirdweapon = 0; self.velocity = '-80 0 -80'; // Stop any old movement self.zoom = 0; self.downed = true; self.dive_delay = 0; self.movetype = MOVETYPE_NONE; float gotalive = PollPlayersAlive(); if ((coop && !gotalive) || (!coop && !(self.perks & P_REVIVE))) { EndGameSetup(); return; } else { self.health = 19; } if ((self.perks & P_REVIVE) && !coop) { self.progress_bar = 10 + time; self.progress_bar_time = 10; self.progress_bar_percent = 1; self.downed = true; } self.points = 10*rint((self.points*0.95)/10); addmoney(self, 0, true); // used to call a broadcast BroadcastMessage(time + 3, 2); self.perks = 0; self.weaponbk = self.weapon; self.currentammobk = self.currentammo; self.currentmagbk = self.currentmag; self.currentmagbk2 = self.currentmag2; if (self.weapon == W_BIATCH || self.secondaryweapon == W_BIATCH || self.progress_bar_percent > 0) { self.weapon = W_BIATCH; self.currentammo = 12; self.currentmag = 6; self.currentmag2 = 6; } else { self.weapon = W_COLT; self.currentammo = 16; self.currentmag = 8; } modelname = GetWeaponModel(self.weapon, 0); self.weaponmodel = modelname; SwitchWeapon(self.weapon); startframe = GetFrame(self.weapon,TAKE_OUT_START); endframe = GetFrame(self.weapon,TAKE_OUT_END); Set_W_Frame (startframe, endframe, 0, 0, 0, SUB_Null, modelname, false, S_BOTH); local entity revive; revive = spawn (); revive.owner = self; revive.movetype = MOVETYPE_NONE; revive.solid = SOLID_NOT; revive.think = remove_revive; revive.nextthink = time + 0.1; setmodel (revive, "models/sprites/revive.spr"); revive.origin = self.origin + VEC_VIEW_OFS; setorigin (revive, revive.origin); SetPerk(self, 0); self.think = rec_downed; self.nextthink = time + 0.1; } void () GetUp = { local string modelname; float startframe; float endframe; playgetup(); // animation self.new_ofs_z = self.view_ofs_z + 42; self.stance = 2; self.health = 100; self.downedloop = 0; // used for death timing vs endgame self.downed = 0; self.classname = "player"; if (self.weaponbk) { self.weapon = self.weaponbk; self.currentammo = self.currentammobk; self.currentmag = self.currentmagbk; self.currentmag2 = self.currentmagbk2; } modelname = GetWeaponModel(self.weapon, 0); self.weaponmodel = modelname; SwitchWeapon(self.weapon); self.weapon2model = GetWeapon2Model(self.weapon); self.movetype = MOVETYPE_WALK; startframe = GetFrame(self.weapon,TAKE_OUT_START); endframe = GetFrame(self.weapon,TAKE_OUT_END); Set_W_Frame (startframe, endframe, 0, 0, 0, SUB_Null, modelname, false, S_BOTH); }; // poll checking whether to see if our revive invoke is active void(entity ent) CheckRevive = { if (self.invoke_revive) { GetUp(); self.invoke_revive = 0; } } void(entity attacker, float d_style) DieHandler = { float t; t = random(); if (self.classname == "ai_zombie" || self.classname == "ai_dog") { self.th_die(); } if (attacker.classname == "player") { attacker.kills++; if (d_style == S_HEADSHOT) { addmoney(attacker, 100, true); attacker.headshots++; } else if (d_style == S_NORMAL) { addmoney(attacker, 60, true); } else if (d_style == S_KNIFE){ addmoney(attacker, 130, true); } else if (d_style == S_TESLA) { addmoney(attacker, 50, true); } } } void(entity victim,entity attacker, float damage, float d_style) DamageHandler = { // don't do any attacking during nuke delay if (d_style == S_ZOMBIE && nuke_powerup_active > time) return; // Abstinence Program victim.ach_tracker_abst = 1; entity old_self; if (victim.classname == "ai_zombie" || victim.classname == "ai_dog") { if (attacker.classname == "player" && (victim.health - damage)> 0) { addmoney(attacker, 10, 1); } victim.health = victim.health - damage; if (d_style == S_EXPLOSIVE) { if (victim.health < z_health*0.5) { if (victim.crawling != TRUE && !(victim.health <= 0) && crawler_num < 5 && victim.classname != "ai_dog") { makeCrawler(victim); #ifndef NX GiveAchievement(3, attacker); #endif } else { if (attacker.classname == "player" && (victim.health - damage) > 0) addmoney(attacker, 10, 1); } } // MOTO - explosives seem to work fine without, but with will cause counter and QC errors.. //else // victim.th_die(); if (victim.health <= 0) addmoney(attacker, 60, 1); } if (victim.health <= 0 || instakill_finished) { old_self = self; self = victim; DieHandler(attacker, d_style); self = old_self; } } else if (victim.classname == "player" && !victim.downed) { if (victim.flags & FL_GODMODE) { return; } if (victim.perks & P_JUG) damage = ceil(damage*0.5); victim.health = victim.health - damage; victim.health_delay = time + 2; // shake the camera on impact local vector distance; distance = attacker.angles - victim.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 victim.punchangle_x = distance_x; victim.punchangle_y = distance_y; // Play pain noise if this isn't done by an electric barrier. if (d_style != S_ZAPPER) sound (self, CHAN_AUTO, "sounds/player/pain4.wav", 1, ATTN_NORM); else sound (self, CHAN_AUTO, "sounds/machines/elec_shock.wav", 1, ATTN_NORM); if (victim.health <= 20) { old_self = self; self = victim; GetDown(); self = old_self; } } } /* ============ CanDamage Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. ============ */ float(entity targ, entity inflictor) CanDamage = { if (targ.flags == FL_GODMODE) return FALSE; // bmodels need special checking because their origin is 0,0,0 if (targ.movetype == MOVETYPE_PUSH) { traceline(inflictor.origin, 0.5 * (targ.absmin + targ.absmax), TRUE, self); if (trace_fraction == 1) return TRUE; if (trace_ent == targ) return TRUE; return FALSE; } traceline(inflictor.origin, targ.origin, TRUE, self); if (trace_fraction == 1) return TRUE; traceline(inflictor.origin, targ.origin + '15 15 0', TRUE, self); if (trace_fraction == 1) return TRUE; traceline(inflictor.origin, targ.origin + '-15 -15 0', TRUE, self); if (trace_fraction == 1) return TRUE; traceline(inflictor.origin, targ.origin + '-15 15 0', TRUE, self); if (trace_fraction == 1) return TRUE; traceline(inflictor.origin, targ.origin + '15 -15 0', TRUE, self); if (trace_fraction == 1) return TRUE; return FALSE; }; void(entity inflictor, entity attacker, float damage2, float mindamage, float radius) DamgageExplode = { float final_damage; entity ent; float multi, r; ent = findradius(inflictor.origin, radius); while (ent != world) { if(ent.classname == "player") { if (ent.perks & P_FLOP) final_damage = 0; else { final_damage = radius - vlen(inflictor.origin - ent.origin); if(final_damage < 0) continue; if (final_damage > radius * 0.6) final_damage = 100; if (final_damage < other.health) { addmoney(self, 10, 0); } else if (final_damage > other.health) { addmoney(self, 60, 0); } else { final_damage /= radius; final_damage *= 60; } DamageHandler (attacker, attacker, final_damage, S_EXPLOSIVE); } } else if (ent.classname == "explosive_barrel") { final_damage = radius - vlen(inflictor.origin - ent.origin); final_damage *= 4; if (final_damage < 0) continue; ent.health -= final_damage; entity oldself; oldself = self; self = ent; Barrel_Hit(); self = oldself; } else if (ent.takedamage && ent.classname != "ai_zombie_head" && ent.classname != "ai_zombie_larm" && ent.classname != "ai_zombie_rarm") { // verify we aren't doin anything with a bmodel if (ent.solid == SOLID_BSP || ent.movetype == MOVETYPE_PUSH) return; if (mapname == "ndu" && ent.classname == "ai_zombie" && inflictor.classname == "explosive_barrel") { ach_tracker_barr++; if (ach_tracker_barr >= 15) { GiveAchievement(13); } } r = rounds; multi = 1.07; while(r > 0) { multi *= 1.05; r --; } if (mindamage == 75) final_damage = (200 * multi) + 185; else final_damage = (mindamage + damage2)/2; if (final_damage > 0) { /* ndaekill = true; */ if (CanDamage (ent, inflictor)) DamageHandler (ent, attacker, final_damage, S_EXPLOSIVE); /* kill = false; */ } } ent = ent.chain; } };