mirror of
https://git.code.sf.net/p/quake/prozac-qfcc
synced 2024-11-27 06:22:23 +00:00
1068 lines
32 KiB
C++
1068 lines
32 KiB
C++
/*======================================================
|
|
JOBS.QC Custom TeamFortress v3.2SB1
|
|
|
|
(c) William Kerney 2/9/00
|
|
(c) SB-1 Tech 25/10/00
|
|
========================================================
|
|
Functions for handling the custom class professions
|
|
======================================================*/
|
|
|
|
#include "defs.qh"
|
|
#include "menu.qh"
|
|
#include "jobs.qh"
|
|
|
|
//WK - all of this
|
|
|
|
void(float delay) Attack_Finished;
|
|
void() DropToCustomClassGen; //Called when starting class generation
|
|
void() DropFromCustomClassGen; //Called when finished class generation
|
|
void() PrintMoney;
|
|
void(float in) PrintRefund;
|
|
void(float in) PrintNotEnoughMoney;
|
|
void(float cost, float type) BuyItem;
|
|
void(float cost, float type) BuyJob;
|
|
/*void(float cost, float type) BuyGren1;
|
|
void(float cost, float type) BuyGren2;*/
|
|
void() FragGrenadeTouch;
|
|
void() FragGrenadeExplode;
|
|
void() KracGrenadeTouch;
|
|
void() KracGrenadeExplode;
|
|
void(entity bastard,float threshold) createBastard;
|
|
void() UseJobSkill; //Function for handling professions
|
|
void (entity targ,float pain) RevealThief;
|
|
|
|
|
|
//Extern
|
|
void (vector org, entity death_owner) spawn_tdeath;
|
|
void (string gib, float health) ThrowGib;
|
|
float() W_BestWeapon;
|
|
void() W_SetCurrentAmmo;
|
|
void(entity p) TeamFortress_SetSpeed;
|
|
void(entity p) TeamFortress_SetSkin;
|
|
void () BecomeExplosion;
|
|
void(entity bomb, entity attacker, float rad, entity ignore) T_RadiusDamage;
|
|
void() SUB_regen;
|
|
float modelindex_eyes, modelindex_player, modelindex_null;
|
|
void(float inAuto) W_FireMedikit;
|
|
void(float inAuto) TeamFortress_ID;
|
|
void(float range,float inAuto) TeamFortress_Scan;
|
|
void(float points) custom_demon_create;
|
|
void() kill_my_demon;
|
|
void() player_assaultcannondown1;
|
|
void (entity rhook) Reset_Grapple;
|
|
void (string temp) DebugSprint;
|
|
void (float temp) DebugSprintFloat;
|
|
void() TeamFortress_RegenerateCells; // for thief
|
|
|
|
//- OfN Cyber Interface is now a job itself
|
|
//---- OfN
|
|
void(entity mine_owner) DetonateMines;
|
|
float(entity thing) IsMonsterNonArmy;
|
|
float(entity thing) IsMonster; // for mines
|
|
void() JobArmy;
|
|
void() JobHacker;
|
|
void(entity e, boolean b, float f) find_melee;
|
|
|
|
//
|
|
// Functions for handling our "professions"
|
|
// which add some class-like behavior to this
|
|
// unclassy version of TF
|
|
//
|
|
|
|
/*
|
|
** Thief Profession -
|
|
** Hides in shadows, can become fully hidden, leaves shadows if attacks or attacked
|
|
*/
|
|
void() JobThief =
|
|
{
|
|
local entity te;
|
|
//local string st;
|
|
if (self.job & JOB_ACTIVE) { //We are hiding
|
|
//If we hit "skill" again and we are moving, we leave shadows
|
|
//vel = vlen(self.velocity);
|
|
/*if (vel > 100) { //Leave shadows
|
|
// sprint(self,PRINT_HIGH,"Leaving shadows...\n");
|
|
//}
|
|
else { //Become fully hidden
|
|
//RJM - Can't hide while carrying flags.
|
|
if (self.effects & EF_ANYGLOW) {
|
|
sprint(self,PRINT_HIGH,"Not while glowing, idiot.\n");
|
|
return;
|
|
}
|
|
sprint(self,PRINT_HIGH,"You are fully hidden\n");
|
|
self.frame = 0;
|
|
self.weaponframe = 0;
|
|
self.modelindex = modelindex_null;
|
|
self.job = self.job | JOB_FULL_HIDE;
|
|
} */
|
|
if (self.invisible_time)
|
|
sprint (self, PRINT_HIGH, "Leaving shadows... just as soon as this ring wears off...\n");
|
|
else {
|
|
sprint (self, PRINT_HIGH, "Leaving shadows...\n");
|
|
self.items = self.items & ~IT_INVISIBILITY;
|
|
self.modelindex = modelindex_player;
|
|
}
|
|
self.job = self.job - JOB_ACTIVE;
|
|
self.job = self.job - (self.job & JOB_FULL_HIDE);
|
|
self.job_finished = time + 2;
|
|
TeamFortress_SetSpeed(self);
|
|
}
|
|
/*else if (self.job & JOB_FULL_HIDE) {
|
|
RevealThief(self,FALSE);
|
|
} */
|
|
else { //Start hiding
|
|
//RJM - Can't hide while carrying flags.
|
|
//WK - Allow them to go eyes invisible but not full hide
|
|
/*
|
|
if (self.effects & EF_ANYGLOW) {
|
|
sprint(self, PRINT_HIGH, "Not while glowing, gimp.\n");
|
|
return;
|
|
}
|
|
*/
|
|
if (self.invisible_time)
|
|
sprint (self, PRINT_HIGH, "Entering shadows... well not really...\n");
|
|
else
|
|
sprint (self, PRINT_HIGH, "Entering shadows...\n");
|
|
self.frame = 0;
|
|
self.weaponframe = 0;
|
|
self.modelindex = modelindex_eyes;
|
|
self.job = self.job | JOB_ACTIVE;
|
|
self.job_finished = time + 2;
|
|
TeamFortress_SetSpeed(self);
|
|
self.items = self.items | IT_INVISIBILITY;
|
|
|
|
te = spawn();
|
|
te.nextthink = time + PC_SPY_CELL_REGEN_TIME;
|
|
te.think = TeamFortress_RegenerateCells;
|
|
te.owner = self;
|
|
te.classname = "timer";
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
** Runner Profession -
|
|
** Sprints at +200 speed for a while, then has to rest (half speed)
|
|
*/
|
|
#define PHASE1 5
|
|
#define PHASE2 5
|
|
#define PHASE3 5
|
|
void() RunnerThink =
|
|
{
|
|
self.heat = self.heat + 1;
|
|
if (self.heat == 1) { //Initial Phase
|
|
sprint(self.owner,PRINT_HIGH,"Sprinting...\n");
|
|
TeamFortress_SetSpeed(self.owner);
|
|
self.nextthink = time + PHASE1;
|
|
}
|
|
else if (self.heat == 2) {
|
|
sprint(self.owner,PRINT_HIGH,"Recovering...\n");
|
|
self.owner.job = self.owner.job | JOB_TIRED;
|
|
TeamFortress_SetSpeed(self.owner);
|
|
self.nextthink = time + PHASE2;
|
|
}
|
|
else if (self.heat == 3) {
|
|
self.owner.job = self.owner.job - (self.owner.job & JOB_ACTIVE);
|
|
self.owner.job = self.owner.job - (self.owner.job & JOB_TIRED);
|
|
TeamFortress_SetSpeed(self.owner);
|
|
self.nextthink = time + PHASE3;
|
|
}
|
|
else {
|
|
dremove(self);
|
|
}
|
|
};
|
|
void() JobRunner =
|
|
{
|
|
local entity RunnerTimer;
|
|
|
|
self.job = self.job | JOB_ACTIVE; //Timer will remove this
|
|
|
|
RunnerTimer = spawn ();
|
|
RunnerTimer.classname = "timer";
|
|
RunnerTimer.owner = self;
|
|
RunnerTimer.nextthink = time + 0.5; //Small delays are cool
|
|
RunnerTimer.think = RunnerThink;
|
|
RunnerTimer.heat = 0;
|
|
self.job_finished = time + PHASE1 + PHASE2 + PHASE3 + 0.6;
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
void() JobWarlock =
|
|
{
|
|
//local float r;
|
|
//local entity SummonTimer;
|
|
if (self.attack_finished > time)
|
|
{
|
|
sprint(self,PRINT_HIGH,"You can't shoot and summon at the same time\n");
|
|
self.job_finished = time + 2;
|
|
return;
|
|
}
|
|
|
|
self.current_menu = MENU_DEMON;
|
|
self.menu_count = MENU_REFRESH_RATE;
|
|
};
|
|
|
|
/*
|
|
** Chaplan Profession -
|
|
** Dispels demons, inspires teammates to do x2 damage, but can't attack himself
|
|
** Timer triggers every so often, checking to see if you want to resume inspire
|
|
*/
|
|
#define GUIDE_TIME 1 //Period of how often lightning guides are shown. Must be less than...
|
|
#define CHAPLAN_TIME 1 //Period of seconds how often it fires
|
|
#define INSPIRE_TIME 6 //How long someone stays inspired
|
|
#define CHAPLAN_RADIUS 320 //About the radius of brightglow
|
|
#define CHAPLAN_HEAL 50 //If you have a medikit you'll heal friends this much
|
|
#define CHAPLAN_HEAL_DELAY 3 //You can't have been shot in last three seconds to be healed
|
|
|
|
//Hunt for all friendlies and power them up
|
|
//Take special care to coexist with Quad damage
|
|
void() ChaplanInspire = {
|
|
local entity head;
|
|
local float take;
|
|
head = findradius(self.origin, CHAPLAN_RADIUS);
|
|
|
|
while (head)
|
|
{
|
|
//Dispel enemy demons
|
|
//- OfN - if (head.classname == "monster_demon1" || head.classname == "monster_shambler")
|
|
if (IsMonsterNonArmy(head))
|
|
{ //No short circuit evaluation, so...
|
|
if (!Teammate(head.real_owner.team_no,self.team_no))
|
|
{ //...to avoid a crash from deref
|
|
if ((head.health <= 200 && head.classname == "monster_demon1") || head.health < 500 && head.classname == "monster_shambler") {
|
|
sprint(self,PRINT_HIGH,"You dispel a demon\n");
|
|
self.real_frags = self.real_frags + 1;
|
|
if (!(toggleflags & TFLAG_TEAMFRAGS))
|
|
self.frags = self.real_frags;
|
|
}
|
|
if (head.classname == "monster_shambler")
|
|
TF_T_Damage(head, self, self, 500, 0, 0);
|
|
else if (head.classname == "monster_demon1")
|
|
TF_T_Damage(head, self, self, 200, 0, 0);
|
|
}
|
|
}
|
|
else if (head.classname == "player" && //Person who is...
|
|
Teammate(head.team_no,self.team_no) && //Same Team
|
|
head != self && //Not me
|
|
!(head.is_undercover) && //Not a spy
|
|
!(head.job & JOB_CHAPLAN) && //Not a chaplan
|
|
head.playerclass != PC_CIVILIAN && //KK
|
|
//... and not Quadded
|
|
!(head.items & IT_QUAD && !(head.tfstate & TFSTATE_INSPIRED))
|
|
)
|
|
{
|
|
head.items = head.items | IT_QUAD;
|
|
head.inspirator = self; //We are their designated preacherman
|
|
head.super_time = 1;
|
|
if (head.super_damage_finished < time + INSPIRE_TIME)
|
|
head.super_damage_finished = time + INSPIRE_TIME;
|
|
head.tfstate = head.tfstate | TFSTATE_INSPIRED;
|
|
//Heal them if we have automedic too
|
|
//SB amended to medikit since there is no automedic now
|
|
if ((self.weapons_carried & WEAP_MEDIKIT) && (self.last_attacked_time < time + CHAPLAN_HEAL_DELAY)) {
|
|
take = head.max_health - head.health;
|
|
if (take > CHAPLAN_HEAL) take = CHAPLAN_HEAL;
|
|
if (take < 0) take = 0;
|
|
head.health = head.health + take;
|
|
}
|
|
}
|
|
head = head.chain;
|
|
}
|
|
|
|
};
|
|
|
|
|
|
//Draws lightning bolts towards all the friendlies we're inspiring
|
|
//entity(entity start, .string fld, string match) find = #18;
|
|
void() ChaplanGuides = {
|
|
local entity head;
|
|
head = find(NIL,classname,"player");
|
|
while (head) {
|
|
if (head.inspirator == self) {
|
|
// Create the Lightning
|
|
msg_entity = self;
|
|
WriteByte (MSG_ONE, SVC_TEMPENTITY);
|
|
WriteByte (MSG_ONE, TE_LIGHTNING1);
|
|
WriteEntity (MSG_ONE, self);
|
|
WriteCoord (MSG_ONE, self.origin_x);
|
|
WriteCoord (MSG_ONE, self.origin_y);
|
|
WriteCoord (MSG_ONE, self.origin_z + 8);
|
|
WriteCoord (MSG_ONE, head.origin_x);
|
|
WriteCoord (MSG_ONE, head.origin_y);
|
|
WriteCoord (MSG_ONE, head.origin_z + 8);
|
|
head = NIL;
|
|
}
|
|
else //We can only draw one lightning. :p
|
|
head = find(head,classname,"player");
|
|
}
|
|
};
|
|
|
|
void() ChaplanThink = {
|
|
local entity oself;
|
|
oself = self;
|
|
self = self.owner;
|
|
|
|
ChaplanGuides();
|
|
oself.nextthink = time + GUIDE_TIME;
|
|
oself.frags = oself.frags + GUIDE_TIME;
|
|
if (oself.frags >= CHAPLAN_TIME) { //Do the full thing every second
|
|
oself.frags = 0;
|
|
if (self.heat == TRUE) { //Inspire everyone again
|
|
//sprint(self,PRINT_HIGH,"Chaplan: Still Preaching\n");
|
|
ChaplanInspire();
|
|
}
|
|
else { //We stopped preaching
|
|
sprint(self,PRINT_HIGH,"You finish your sermon\n");
|
|
//Sync CHAN_MUSIC with disconnect and sound below
|
|
self.job = self.job - (self.job & JOB_ACTIVE);
|
|
sound (self, CHAN_MUSIC, "items/r_item1.wav", 0.1, ATTN_NORM);
|
|
self.effects &= ~EF_ANYGLOW;
|
|
//self.tfstate = self.tfstate - (self.tfstate & TFSTATE_RELOADING);
|
|
self.current_weapon = self.weapon;
|
|
W_SetCurrentAmmo();
|
|
oself.nextthink = time + 0.1;
|
|
oself.think = SUB_Remove;
|
|
}
|
|
}
|
|
self = oself;
|
|
};
|
|
|
|
void() JobChaplan =
|
|
{
|
|
local entity tWeapon;
|
|
if (self.job & JOB_ACTIVE) {
|
|
//self.job = self.job - JOB_ACTIVE;
|
|
if (self.heat == TRUE) //Only print this once
|
|
sprint(self,PRINT_HIGH,"You gradually stop preaching...\n");
|
|
self.heat = FALSE; //Bad to turn off active, since technically job is still on.
|
|
self.job_finished = time + 0.7; //Don't allow them to trigger too often
|
|
return;
|
|
}
|
|
if (self.tfstate & TFSTATE_RELOADING || self.is_feigning || self.heat) {
|
|
sprint(self,PRINT_HIGH,"You can't preach while doing other stuff\n");
|
|
self.job_finished = time + 0.5; //Don't allow them to trigger too often
|
|
return;
|
|
}
|
|
|
|
sprint(self,PRINT_HIGH,"You begin preaching. Hit skill again to stop.\n");
|
|
tWeapon = spawn();
|
|
tWeapon.frags = 0; //Clear guides counter
|
|
tWeapon.owner = self;
|
|
tWeapon.classname = "timer";
|
|
tWeapon.nextthink = time + GUIDE_TIME;
|
|
tWeapon.think = ChaplanThink;
|
|
self.job = self.job | JOB_ACTIVE;
|
|
self.job_finished = time + 0.3; //Don't allow them to trigger too often
|
|
|
|
//Hide our weapon. Can't shoot while preaching.
|
|
self.weapon = self.current_weapon;
|
|
self.current_weapon = 0;
|
|
self.weaponmodel = "";
|
|
self.weaponframe = 0;
|
|
self.heat = TRUE; //We're actively preaching.
|
|
//self.tfstate = self.tfstate | TFSTATE_RELOADING;
|
|
|
|
//Start playing preacher music, glow and inspire!
|
|
//sound (self, CHAN_VOICE, "ambience/orff.wav", 0.75, ATTN_NORM);
|
|
sound (self, CHAN_MUSIC, "ambience/orff.wav", 0.75, ATTN_NORM);
|
|
self.effects |= EF_GlowColor(self);
|
|
ChaplanInspire();
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
** Martyr Proficiency -
|
|
** Becomes invincible, but dies after a few seconds
|
|
*/
|
|
#define MARTYR_TIME 3.5
|
|
void() MartyrThink =
|
|
{
|
|
//Self.owner is the guy who became a martyr
|
|
|
|
//Clean these up so we can kill him
|
|
self.job = self.job - (self.job & JOB_ACTIVE); //why not?
|
|
self.owner.items = self.items & ~IT_INVULNERABILITY;
|
|
self.owner.invincible_time = 0;
|
|
self.owner.invincible_finished = 0;
|
|
self.owner.effects = self.owner.effects - (self.owner.effects & EF_DIMLIGHT);
|
|
|
|
//if (self.owner.martyr_enemy == self.owner)
|
|
deathmsg = DMSG_MARTYR;
|
|
//else
|
|
// deathmsg = self.owner.stored_deathmsg;
|
|
//- OfN was 20
|
|
TF_T_Damage(self.owner, self.owner.martyr_enemy, self.owner.martyr_enemy, self.owner.health + 20, TF_TD_IGNOREARMOUR, TF_TD_OTHER);
|
|
|
|
self.think = SUB_Remove;
|
|
self.nextthink = time + 0.1;
|
|
};
|
|
|
|
// SB I'll leave this here since it's still close to a job
|
|
void() JobMartyr =
|
|
{
|
|
local entity tWeapon;
|
|
local float martyr_time;
|
|
|
|
if (self.is_abouttodie)
|
|
return;
|
|
|
|
martyr_time = MARTYR_TIME; // don't need FORCED stuff since it's always automatic
|
|
|
|
sprint(self,PRINT_HIGH,"Beginning your suicide run...\n");
|
|
self.items = self.items | IT_INVULNERABILITY;
|
|
self.invincible_time = 1;
|
|
self.invincible_finished = time + martyr_time + 1; //Overlap so we can't die till end
|
|
self.job = self.job | JOB_ACTIVE; //why not?
|
|
|
|
tWeapon = spawn();
|
|
tWeapon.owner = self;
|
|
tWeapon.classname = "timer";
|
|
tWeapon.nextthink = time + martyr_time;
|
|
tWeapon.think = MartyrThink;
|
|
};
|
|
|
|
/* Berserker Profession -
|
|
** A simple soul that just likes killing things
|
|
** Takes 50 self inflicted damage and gets Quad for 5 seconds
|
|
** If he can't pay up with the 50 health he gets reduced to 1 and dies after his 5 seconds
|
|
*/
|
|
|
|
// now takes BERSERKER_HP_COST hp
|
|
|
|
#define BERSERK_TIME 4
|
|
|
|
void() BerserkerKillTimer =
|
|
{
|
|
if (!(self.tfstate & TFSTATE_CONCUSSIONED))
|
|
stuffcmd(self.owner, "v_idlescale 0\n");
|
|
|
|
if (self.has_sensor)
|
|
{
|
|
deathmsg = DMSG_BERSERK;
|
|
TF_T_Damage(self.owner, self.owner, self.owner, self.owner.health, TF_TD_IGNOREARMOUR, TF_TD_OTHER);
|
|
}
|
|
|
|
self.owner.job &= ~JOB_ACTIVE; //- OfN is a tard, should be self.owner idiot -Griev
|
|
dremove(self);
|
|
};
|
|
|
|
void() JobBerserker =
|
|
{
|
|
if (self.super_damage_finished > 0 || self.tfstate & TFSTATE_CONCUSSIONED)
|
|
return;
|
|
|
|
newmis = spawn();
|
|
newmis.classname = "berserker_timer";
|
|
newmis.nextthink = time + BERSERK_TIME + 1;
|
|
newmis.think = BerserkerKillTimer;
|
|
newmis.owner = self;
|
|
|
|
if (self.health > BERSERKER_HP_COST)
|
|
self.health -= BERSERKER_HP_COST;
|
|
else
|
|
{
|
|
self.health = 1;
|
|
newmis.has_sensor = 1;
|
|
}
|
|
self.super_time = 1;
|
|
self.super_damage_finished = time + BERSERK_TIME + 1;
|
|
self.items = self.items | IT_QUAD;
|
|
self.job = self.job | JOB_ACTIVE; //- OfN
|
|
self.job_finished = time + BERSERK_TIME * 2; // GR was * 3
|
|
|
|
stuffcmd(self, "v_idlescale 30\n");
|
|
};
|
|
|
|
|
|
/*
|
|
** Judoka Profession -
|
|
** Disarms opponents so they cannot attack
|
|
*/
|
|
//This is guaranteed to be removed if either target or owner dies or disconnects
|
|
//So we don't have to do error checking on those situations
|
|
//Four special cases, Rifle, Medikit, AC and Grapple, have side effects
|
|
// when you remove them. Need special cases to handle their theft
|
|
void() JudokaRearm =
|
|
{
|
|
//Self.owner is the guy who had his weapon taken away
|
|
//Self.enemy is the guy who took it away
|
|
local entity oself,te;
|
|
|
|
self.enemy.job = self.enemy.job - (self.enemy.job & JOB_ACTIVE);
|
|
if (self.heat == 1) { //We have their weapon
|
|
////Fix feign while stolen
|
|
self.enemy.weapon = 0;
|
|
////Fix reloading
|
|
te = find(NIL, netname, "reloadtimer");
|
|
while (te)
|
|
{
|
|
if (te.classname == "timer" && te.owner == self.enemy) {
|
|
oself = self;
|
|
self = te;
|
|
self.think();
|
|
self = oself;
|
|
te.think = SUB_Remove;
|
|
te.nextthink = time + 0.1;
|
|
}
|
|
te = find(te, netname, "reloadtimer");
|
|
}
|
|
////Fix double weapons
|
|
#ifndef NO_AUTORIFLE
|
|
if (self.current_weapon == WEAP_SNIPER_RIFLE) {
|
|
self.owner.weapons_carried = self.owner.weapons_carried | WEAP_AUTO_RIFLE;
|
|
self.enemy.weapons_carried = self.enemy.weapons_carried - (self.enemy.weapons_carried & WEAP_AUTO_RIFLE);
|
|
}
|
|
#endif
|
|
////Fix weird weapons
|
|
if (self.current_weapon == WEAP_ASSAULT_CANNON && self.enemy.current_weapon == WEAP_ASSAULT_CANNON) {
|
|
oself = self;
|
|
self = self.enemy;
|
|
stuffcmd(self, "v_idlescale 0\n");
|
|
self.tfstate &= ~TFSTATE_ASSAULTCANNON;
|
|
TeamFortress_SetSpeed(self);
|
|
self.weaponframe = 0;
|
|
self.count = 1;
|
|
self.heat = 0;
|
|
self.button0 = 0;
|
|
self.fire_held_down = FALSE;
|
|
player_assaultcannondown1();
|
|
self = oself;
|
|
}
|
|
if (self.current_weapon == WEAP_HOOK && self.enemy.hook_out) {
|
|
oself = self;
|
|
self = self.enemy;
|
|
Reset_Grapple (self.hook);
|
|
self.weaponframe = 0;
|
|
self = oself;
|
|
}
|
|
|
|
sprint(self.owner,PRINT_HIGH,"You get your weapon back\n");
|
|
self.owner.weapons_carried = self.owner.weapons_carried | self.current_weapon;
|
|
|
|
sprint(self.enemy,PRINT_HIGH,"You lose your stolen weapon\n");
|
|
self.enemy.weapons_carried = self.enemy.weapons_carried - (self.enemy.weapons_carried & self.current_weapon);
|
|
//Fix for a bug that would let someone keep their weapon if they switched to autorifle before
|
|
//the weapon returned.
|
|
if (self.enemy.current_weapon == self.current_weapon || self.current_weapon == WEAP_SNIPER_RIFLE || self.current_weapon == WEAP_MEDIKIT) {
|
|
oself = self;
|
|
self = self.enemy;
|
|
self.weaponframe = 0;
|
|
self.current_weapon = W_BestWeapon ();
|
|
W_SetCurrentAmmo();
|
|
self = oself;
|
|
}
|
|
//TODO: Is this really a Fix for diving?
|
|
//Should we even do this? Might cause firing skip. Evaluate
|
|
self.enemy.weaponframe = 0;
|
|
}
|
|
|
|
self.owner.tfstate &= ~TFSTATE_DISARMED;
|
|
|
|
self.think = SUB_Remove;
|
|
self.nextthink = time + 0.1;
|
|
};
|
|
void() JobJudoka =
|
|
{
|
|
//Take the weapon of any person in front of you and force a reload
|
|
self.job_finished = time + MISS_DELAY; //Delay if we don't hit
|
|
local vector dir;
|
|
local float chance;
|
|
local entity tWeapon,oself;
|
|
local entity te;
|
|
|
|
if (self.attack_finished > time)
|
|
return;
|
|
|
|
find_melee (self, TRUE, 48);
|
|
|
|
if (trace_fraction != 1.0 && trace_ent.classname == "player" && !Teammate(trace_ent.team_no, self.team_no) && trace_ent.playerclass != PC_UNDEFINED)
|
|
{
|
|
if (self.is_undercover) //Taking someone's weapon should give you away
|
|
Spy_RemoveDisguise(self);
|
|
|
|
// Let's not make it work all the time
|
|
dir = normalize (trace_ent.origin - self.origin);
|
|
makevectors(trace_ent.v_angle);
|
|
|
|
chance = dir * normalize(v_forward);
|
|
chance *= 0.35;
|
|
if (chance > 0.25)
|
|
chance = 0.25;
|
|
if (trace_ent.job & JOB_JUDOKA)
|
|
chance += 0.2;
|
|
if (trace_ent.cutf_items & CUTF_CLOSECOMBAT)
|
|
chance += 0.25;
|
|
if (trace_ent.current_weapon == WEAP_AXE && trace_ent.cutf_items & CUTF_KNIFE)
|
|
chance += 0.1;
|
|
if (self.cutf_items & CUTF_CLOSECOMBAT)
|
|
chance -= 0.25;
|
|
|
|
if (random() > chance || trace_ent.invincible_finished > time)
|
|
{
|
|
sprint (self, PRINT_HIGH, "Your strike is parried!\n");
|
|
Attack_Finished(3);
|
|
return;
|
|
}
|
|
|
|
sprint (trace_ent, PRINT_HIGH, "You have been disarmed by ");
|
|
sprint (trace_ent, PRINT_HIGH, self.netname);
|
|
sprint (trace_ent, PRINT_HIGH, "\n");
|
|
|
|
self.job = self.job | JOB_ACTIVE;
|
|
//Simplify the dual-weapon problem
|
|
#ifndef NO_AUTORIFLE
|
|
if (trace_ent.current_weapon == WEAP_AUTO_RIFLE)
|
|
trace_ent.current_weapon = WEAP_SNIPER_RIFLE;
|
|
#endif
|
|
|
|
trace_ent.tfstate &= ~(TFSTATE_AIMING | TFSTATE_RL_LASER);
|
|
|
|
trace_ent.tfstate |= TFSTATE_DISARMED;
|
|
|
|
//If already reloading, remove that timer
|
|
te = find(NIL, netname, "reloadtimer");
|
|
while (te)
|
|
{
|
|
if (te.classname == "timer" && te.owner == trace_ent) {
|
|
oself = self;
|
|
self = te;
|
|
self.think();
|
|
self = oself;
|
|
te.think = SUB_Remove;
|
|
te.nextthink = time + 0.1;
|
|
}
|
|
te = find(te, netname, "reloadtimer");
|
|
}
|
|
|
|
tWeapon = spawn();
|
|
tWeapon.owner = trace_ent;
|
|
tWeapon.enemy = self;
|
|
tWeapon.current_weapon = trace_ent.current_weapon;
|
|
tWeapon.classname = "timer";
|
|
tWeapon.netname = "judokatimer";
|
|
tWeapon.nextthink = time + DISARM_TIME;
|
|
tWeapon.think = JudokaRearm;
|
|
|
|
//Remove the weapon
|
|
trace_ent.attack_finished = time + CANT_ATTACK_TIME;
|
|
trace_ent.weaponmodel = "";
|
|
trace_ent.weaponframe = 0;
|
|
trace_ent.currentammo = 0;
|
|
|
|
|
|
if ((trace_ent.job & JOB_JUDOKA && trace_ent.job_finished > time) || trace_ent.current_weapon == 0) {
|
|
//Hit fellow judoka or chaplan
|
|
sprint (self, PRINT_HIGH, "You throw him with a mighty Seoi Otoshi\n");
|
|
deathmsg = DMSG_JUDOKA;
|
|
TF_T_Damage (trace_ent, self, self, 150, TF_TD_NOTTEAM, TF_TD_MELEE);
|
|
}
|
|
else if (self.weapons_carried & trace_ent.current_weapon) {
|
|
sprint (self, PRINT_HIGH, "You knock his weapon out of his hands\n");
|
|
tWeapon.heat = 0; //I.e., we didn't take a weapon
|
|
trace_ent.attack_finished = time + DISARM_TIME;
|
|
deathmsg = DMSG_JUDOKA;
|
|
TF_T_Damage (trace_ent, self, self, 100, TF_TD_NOTTEAM, TF_TD_MELEE);
|
|
}
|
|
else if (trace_ent.current_weapon != 0 && trace_ent.current_weapon != WEAP_AXE){
|
|
//Steal their weapon if they have one
|
|
sprint (self, PRINT_HIGH, "You rip his weapon from his hands!\n");
|
|
tWeapon.heat = 1;
|
|
|
|
//Fix double weapons
|
|
#ifndef NO_AUTORIFLE
|
|
if (trace_ent.current_weapon == WEAP_SNIPER_RIFLE) {
|
|
self.weapons_carried = self.weapons_carried | WEAP_AUTO_RIFLE;
|
|
trace_ent.weapons_carried = trace_ent.weapons_carried - (trace_ent.weapons_carried & WEAP_AUTO_RIFLE);
|
|
}
|
|
#endif
|
|
if (trace_ent.current_weapon == WEAP_MEDIKIT) {
|
|
self.health = self.max_health; //You heal yourself. :)
|
|
}
|
|
////Fix weird weapons
|
|
if (trace_ent.current_weapon == WEAP_ASSAULT_CANNON) {
|
|
oself = self;
|
|
self = trace_ent;
|
|
stuffcmd(self, "-attack;v_idlescale 0\n");
|
|
self.tfstate &= ~TFSTATE_ASSAULTCANNON;
|
|
TeamFortress_SetSpeed(self);
|
|
self.weaponframe = 0;
|
|
self.count = 1;
|
|
self.heat = 0;
|
|
self.button0 = 0;
|
|
self.fire_held_down = FALSE;
|
|
player_assaultcannondown1();
|
|
self = oself;
|
|
}
|
|
if (trace_ent.current_weapon == WEAP_HOOK && trace_ent.hook_out) {
|
|
oself = self;
|
|
self = trace_ent;
|
|
Reset_Grapple (self.hook);
|
|
self = oself;
|
|
}
|
|
|
|
self.weapons_carried = self.weapons_carried | trace_ent.current_weapon;
|
|
self.current_weapon = trace_ent.current_weapon;
|
|
W_SetCurrentAmmo();
|
|
|
|
trace_ent.weapons_carried = trace_ent.weapons_carried - (trace_ent.weapons_carried & trace_ent.current_weapon);
|
|
trace_ent.current_weapon = 0;
|
|
|
|
deathmsg = DMSG_JUDOKA;
|
|
TF_T_Damage (trace_ent, self, self, 65, TF_TD_NOTTEAM, TF_TD_MELEE);
|
|
}
|
|
|
|
self.job_finished = time + HIT_DELAY;
|
|
Attack_Finished(0.5);
|
|
}
|
|
else
|
|
{
|
|
sprint (self, PRINT_HIGH, "You miss.\n");
|
|
Attack_Finished(1.5);
|
|
}
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
** Guerilla Profession -
|
|
** Can set self-detonating land mines
|
|
*/
|
|
#define ACTIVATE_TIME 8 //Time until it turns on //- it was 3? prolly 10
|
|
#define BEEP_RATE 4 //Delay between beeps //- it was 3
|
|
#define MINE_DURATION 360 //Time it lasts after being activated //- it was 60 ofn
|
|
#define JOB_DELAY 4 //Time between mine placements //- it was 10
|
|
#define GUERILLA_RADIUS 135 //-it was 150 without define
|
|
#define MINE_COST 4 // OfN number of rockets a mine needs
|
|
//#define MAX_MINES 4 // OfN Maximum number of mines for player NOW A LOCALINFO
|
|
//Blow up a mine
|
|
void() GuerillaExplode =
|
|
{
|
|
self.health = 0; //CH would cause tesla to act weird without
|
|
deathmsg = DMSG_LAND_MINE;
|
|
|
|
if (self.has_tesla == 2) // set by GuerillaSwep for trace weapons
|
|
sprint(self.owner,PRINT_HIGH,"your mine is destroyed\n");
|
|
|
|
if (self.has_tesla == 0) // default
|
|
sprint(self.owner,PRINT_HIGH,"your mine explodes\n");
|
|
|
|
// if has_tesla is 1 print nothing, as this is set by DetonateMines() and GuerillaThink
|
|
|
|
if (time < self.heat + ACTIVATE_TIME) //If not charged, do less damage when blowing up
|
|
T_RadiusDamage (self, self.owner, MINE_DMG * 0.5, NIL); //- damage was 80
|
|
else
|
|
T_RadiusDamage (self, self.owner, MINE_DMG, NIL); //- damage was 160
|
|
|
|
// num_mines is the number of mines the player has
|
|
self.owner.num_mines = self.owner.num_mines - 1;
|
|
if (self.owner.num_mines < 0)
|
|
self.owner.num_mines = 0;
|
|
|
|
WriteByte (MSG_MULTICAST, SVC_TEMPENTITY);
|
|
WriteByte (MSG_MULTICAST, TE_EXPLOSION);
|
|
WriteCoord (MSG_MULTICAST, self.origin_x);
|
|
WriteCoord (MSG_MULTICAST, self.origin_y);
|
|
WriteCoord (MSG_MULTICAST, self.origin_z);
|
|
multicast (self.origin, MULTICAST_PHS);
|
|
dremove(self);
|
|
|
|
};
|
|
|
|
//=========================================================================
|
|
// Detonate all mines the mine_owner player entity has
|
|
void(entity mine_owner) DetonateMines =
|
|
{
|
|
local entity e;
|
|
|
|
// Find any mine
|
|
e = find(NIL, netname, "land_mine");
|
|
|
|
while (e)
|
|
{
|
|
if(e.owner == mine_owner) {
|
|
e.heat = time;
|
|
e.has_tesla = 1; //- display no message on GuerillaExplode
|
|
e.think = GuerillaExplode;
|
|
e.nextthink = time;
|
|
}
|
|
e = find(e, netname, "land_mine");
|
|
}
|
|
mine_owner.num_mines = 0;
|
|
};
|
|
//====================================================
|
|
//Code to detonate the guerilla mines on traceattack weapons.
|
|
//Has small radius, just large enough to hit what was aimed at
|
|
void(vector startpos) GuerillaMineSweep =
|
|
{
|
|
local entity head;
|
|
local entity inf = self;
|
|
local float loopc = 0;
|
|
head = findradius(startpos, 30);
|
|
|
|
while (head)
|
|
{
|
|
if (head.classname == "grenade" && head.netname == "land_mine") {
|
|
|
|
head.has_tesla = 2; // "mine is destroyed" message on guerillaexplode()
|
|
head.think = GuerillaExplode;
|
|
head.nextthink = time + 0.1;
|
|
head.martyr_enemy = inf;
|
|
}
|
|
head = head.chain;
|
|
}
|
|
};
|
|
|
|
void() GuerillaThink = //Every second see if we have enemy nearby
|
|
{
|
|
local entity head;
|
|
local float finished; //Only blow up once
|
|
finished = 0;
|
|
self.nextthink = time + MINE_SCANRATE;
|
|
self.martyr_enemy = NIL;
|
|
if (time < self.heat + ACTIVATE_TIME)
|
|
return;
|
|
if (time > self.last_attacked_time) {
|
|
sound (self, CHAN_WEAPON, "weapons/guerilla_blip.wav", 1, ATTN_IDLE);
|
|
self.last_attacked_time = time + BEEP_RATE;
|
|
}
|
|
self.health = self.health - 1;
|
|
|
|
if (self.health <= 0) { //Detonate mine cause we ran out of time
|
|
|
|
self.martyr_enemy = self.owner;
|
|
self.heat = time; //Make it a smaller explosion
|
|
sprint(self.owner,PRINT_HIGH,"Mine runs out of energy, ");
|
|
|
|
if (self.owner.num_mines > 1) {
|
|
local string st;
|
|
st = ftos (self.owner.num_mines - 1);
|
|
sprint(self.owner,PRINT_HIGH,"you still have ");
|
|
sprint(self.owner,PRINT_HIGH,st);
|
|
sprint(self.owner,PRINT_HIGH,"/");
|
|
st = ftos (max_mines);
|
|
sprint(self.owner,PRINT_HIGH,st);
|
|
sprint(self.owner,PRINT_HIGH," mines up\n");
|
|
}
|
|
else
|
|
sprint(self.owner,PRINT_HIGH,"you currently have no mines active\n");
|
|
self.has_tesla = 1; //- display no message on GuerillaExplode
|
|
GuerillaExplode();
|
|
return;
|
|
}
|
|
head = findradius(self.origin,GUERILLA_RADIUS);//OfN it was 150
|
|
while (head && !finished) {
|
|
if (head.classname == "player") {
|
|
//Mines detonate on either enemies or yourself
|
|
if (CanDamage(head,self)) {
|
|
if (!Teammate(head.team_no,self.owner.team_no) && head.undercover_team == 0)
|
|
{ //Uncouvered ENEMY
|
|
sprint(self.owner,PRINT_HIGH,"your mine explodes on ");
|
|
sprint(self.owner,PRINT_HIGH,head.netname);
|
|
sprint(self.owner,PRINT_HIGH,"'s face!\n");
|
|
self.has_tesla = 1; //- display no message on GuerillaExplode
|
|
self.martyr_enemy = head;
|
|
GuerillaExplode();
|
|
return;
|
|
} else if (head == self.owner) { // ourselves
|
|
self.martyr_enemy = self.owner;
|
|
self.has_tesla = 1; //- display no message on GuerillaExplode
|
|
GuerillaExplode();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
else if (IsMonster(head) && !Teammate(head.real_owner.team_no,self.owner.team_no)) { //- OfN - monsters and grunt are affected
|
|
T_Damage(head,self,self.owner,MINE_DMG); //Demons are vulnerable
|
|
self.has_tesla = 1; //- display no message on GuerillaExplode
|
|
GuerillaExplode();
|
|
return;
|
|
}
|
|
head = head.chain;
|
|
}
|
|
};
|
|
|
|
void(entity inflictor, float amt) GuerillaPain = //What happens when someone hurts it
|
|
{
|
|
if (time < self.heat + ACTIVATE_TIME)
|
|
return;
|
|
|
|
self.martyr_enemy = inflictor;
|
|
GuerillaExplode();
|
|
};
|
|
|
|
void() GuerillaTouch = // what happens when someone runs it over
|
|
{
|
|
GuerillaPain(other, 100);
|
|
};
|
|
|
|
void() GuerillaTossTouch =
|
|
{
|
|
self.solid = SOLID_BBOX;
|
|
setsize(self, self.mins, self.maxs);
|
|
|
|
if (other && other != self.owner) {
|
|
GuerillaPain(other, 100);
|
|
return;
|
|
}
|
|
|
|
if (entpointcontents(self) == CONTENTS_SKY || entpointcontents(self) == CONTENTS_SOLID) {
|
|
self.health = 0; //CH needed for tesla
|
|
// OfN CAUSES ANY PROBLEM? Is this needed?
|
|
|
|
self.owner.num_mines = self.owner.num_mines - 1;
|
|
if (self.owner.num_mines < 0)
|
|
self.owner.num_mines = 0;
|
|
|
|
dremove(self);
|
|
return;
|
|
}
|
|
|
|
self.angles = self.velocity = self.avelocity = '0 0 0';
|
|
self.movetype = MOVETYPE_NONE;
|
|
sound (self, CHAN_WEAPON, "weapons/guerilla_set.wav", 1, ATTN_NORM);
|
|
self.touch = GuerillaTouch;
|
|
self.think = GuerillaThink;
|
|
self.takedamage = DAMAGE_AIM;
|
|
self.th_pain = GuerillaPain;
|
|
self.nextthink = time + 1;
|
|
};
|
|
|
|
void() JobGuerilla =
|
|
{
|
|
if (self.ammo_rockets < MINE_COST) {
|
|
sprint(self, PRINT_HIGH, "every mine needs 4 rockets to work!\n");
|
|
self.job_finished = time + 1;
|
|
return;
|
|
}
|
|
|
|
if (self.num_mines >= max_mines) {
|
|
local string st;
|
|
st = ftos(max_mines);
|
|
sprint(self, PRINT_HIGH, "you can set upto ");
|
|
sprint(self, PRINT_HIGH, st);
|
|
sprint(self, PRINT_HIGH, " mines!\n");
|
|
self.job_finished = time + 1;
|
|
return;
|
|
}
|
|
|
|
self.ammo_rockets = self.ammo_rockets - MINE_COST;
|
|
self.job_finished = time + JOB_DELAY;
|
|
|
|
// OfN - I created the field num_mines to store the number of mines the player has
|
|
self.num_mines = self.num_mines + 1;
|
|
|
|
sprint(self,PRINT_HIGH,"you place a mine...\n");
|
|
|
|
teamprefixsprint(self.team_no,self);
|
|
teamsprint(self.team_no,self,self.netname);
|
|
teamsprint(self.team_no,self," places a mine\n");
|
|
|
|
//Set all the critical crap on the mine
|
|
newmis = spawn();
|
|
newmis.movetype = MOVETYPE_BOUNCE;
|
|
newmis.solid = SOLID_TRIGGER;
|
|
newmis.takedamage = DAMAGE_AIM;
|
|
newmis.classname = "grenade";
|
|
newmis.netname = "land_mine";
|
|
setsize (newmis, '-0.5 -0.5 -0.5', '1 1 1');
|
|
newmis.owner = self;
|
|
makevectors (self.v_angle);
|
|
newmis.avelocity = '300 300 300';
|
|
newmis.velocity = v_forward*600 + v_up * 200 + v_right*10 + v_up*10;
|
|
setorigin (newmis, self.origin + (normalize(newmis.velocity) * 5));
|
|
newmis.angles = vectoangles(newmis.velocity);
|
|
// newmis.skin = self.team_no - 1;
|
|
newmis.skin = 1;
|
|
// setmodel (newmis, "progs/lndmine.mdl");
|
|
setmodel (newmis, "progs/biggren.mdl");
|
|
|
|
newmis.heat = time; //Controls when mine can first go off
|
|
newmis.has_tesla = 0; // OfN flag to Control what to sprint to owner of the mine when it exlodes
|
|
newmis.last_attacked_time = time; //Time that you were last shot at
|
|
newmis.health = MINE_DURATION; //Max time for mine to live
|
|
newmis.touch = GuerillaTossTouch;
|
|
newmis.think = GuerillaThink;
|
|
newmis.nextthink = time + ACTIVATE_TIME;
|
|
};
|
|
|
|
void() JobCrusader;
|
|
|
|
void() UseJobSkill =
|
|
{
|
|
local float myjob;
|
|
|
|
//Make sure they can do it
|
|
if (self.done_custom & CUSTOM_BUILDING) return;
|
|
if (self.job_finished > time) return;
|
|
|
|
myjob = self.job;
|
|
if (myjob & JOB_THIEF)
|
|
JobThief();
|
|
else if (myjob & JOB_RUNNER)
|
|
JobRunner();
|
|
else if (myjob & JOB_WARLOCK) {
|
|
if (HasFlag(self.team_no) == FALSE) {
|
|
CenterPrint(self, "No demons until your team has the flag!\n");
|
|
return;
|
|
}
|
|
JobWarlock();
|
|
} else if (myjob & JOB_CHAPLAN)
|
|
JobChaplan();
|
|
else if (myjob & JOB_BERSERKER)
|
|
JobBerserker();
|
|
else if (myjob & JOB_GUERILLA)
|
|
JobGuerilla();
|
|
else if (myjob & JOB_JUDOKA)
|
|
JobJudoka();
|
|
else if (myjob & JOB_HACKER)
|
|
JobHacker();
|
|
else if (myjob & JOB_MARTYR)
|
|
JobMartyr();
|
|
else if (myjob & JOB_ARMY)
|
|
JobArmy();
|
|
else if (myjob & JOB_CRUSADER)
|
|
JobCrusader();
|
|
else {
|
|
sprint(self,PRINT_HIGH,"You don't have a job. Go get employed.\n");
|
|
self.job_finished = time + 5; //Don't let them print this message that often
|
|
}
|
|
};
|
|
|
|
#define NOHIDE_TIME 8
|
|
#define EXPOSURE_NOHIDE_TIME 20
|
|
#define EXPOSURE_NORELOAD_TIME 7
|
|
#define FULLH_NORELOAD_TIME 1.5
|
|
void (entity targ, float pain) RevealThief =
|
|
{
|
|
if (targ.classname != "player")
|
|
return;
|
|
|
|
if (!targ.invisible_time) {
|
|
targ.modelindex = modelindex_player;
|
|
targ.items = targ.items & ~IT_INVISIBILITY;
|
|
}
|
|
if (pain) {
|
|
if (targ.invisible_time)
|
|
sprint (targ, PRINT_HIGH, "You have been uncovered, or would have if you didn't have that ring.\n");
|
|
else
|
|
sprint(targ,PRINT_HIGH,"You have been uncovered!\n");
|
|
targ.attack_finished = time + EXPOSURE_NORELOAD_TIME;
|
|
targ.job_finished = time + EXPOSURE_NOHIDE_TIME;
|
|
} else {
|
|
if (targ.invisible_time)
|
|
sprint (targ, PRINT_HIGH, "Leaving shadows... just as soon as this ring wears off...\n");
|
|
else
|
|
sprint (targ, PRINT_HIGH, "Leaving shadows...\n");
|
|
targ.job_finished = time + NOHIDE_TIME;
|
|
if (targ.job & JOB_FULL_HIDE)
|
|
targ.attack_finished = time + FULLH_NORELOAD_TIME;
|
|
}
|
|
targ.job = targ.job - (targ.job & JOB_FULL_HIDE);
|
|
targ.job = targ.job - (targ.job & JOB_ACTIVE);
|
|
TeamFortress_SetSpeed(targ);
|
|
};
|