mirror of
https://git.code.sf.net/p/quake/prozac-qfcc
synced 2024-11-27 06:22:23 +00:00
db4e96e70a
1) added checkmove forward so it's easier to build against walls 2) Can't build sentries on top of forcefields anymore (they get bounced off) 3) fieldgens are now one unit taller than their fields, so you CAN build on the gen 4) forcefields bounce everything (including buildings) away now. 5) added #ifdef DISALLOW_BLOCKED_TELE around tele block checks. didn't get the point Debug tweaks: 1) added #ifdef DEBUG, which enables RPrint(), dremove(), and printtrace(), as well as the warlock cheat and origin reporting. 2) replaced EVERY dprint with RPrint. 3) changed makefile so that all = no DEBUG and no .sym
1167 lines
32 KiB
C++
1167 lines
32 KiB
C++
#include "defs.qh"
|
|
#include "menu.qh"
|
|
/*=======================================================//
|
|
// field.QC - CustomTF 3.2.OfN - 30/1/2001 - //
|
|
// by Sergio Fumaña Grunwaldt - OfteN aka superCOCK2000 //
|
|
=========================================================//
|
|
Field generator stuff - Written all by myself! :)
|
|
I took the model and some sounds from deadlode mod
|
|
One sound is from megaTF
|
|
=========================================================*/
|
|
|
|
// field generator flags, DO NOT MODIFY
|
|
|
|
#define FIELDGEN_ISOFF 0 // no field, not linked, and its not trying to link (several possible reasons)
|
|
#define FIELDGEN_ISIDLE 1 // no field, not linked, trying to link with other generator
|
|
#define FIELDGEN_ISDISABLED 2 // no field, linked, its disabled temporary (teammate passing thru field)
|
|
#define FIELDGEN_ISENABLED 3 // field, linked, and cells enough, waiting for shock, only hum sound
|
|
#define FIELDGEN_ISWORKING 4 // field, linked, glowing and all the field visual/sound effects are on
|
|
|
|
// field generator settings
|
|
|
|
#define FIELDGEN_SHOCKTIME 3 // seconds the generators remains glowing and doing lightning after a shock
|
|
#define FIELDGEN_LINKTIME 3.5 // seconds between tries to link with other generator
|
|
#define FIELDGEN_TIMEDISABLED 1.5 // was 3 // then 2
|
|
#define FIELDGEN_CELLSCOST 2 // cells cost for each "FIELDGEN_ISWORKING" pass
|
|
#define FIELDGEN_DMG 80 // normal damag when touching
|
|
#define FIELDGEN_DMGINSIDE 120 // damage when trapped inside field
|
|
#define FIELDGEN_MAXZ 10
|
|
|
|
/*===============================================================================================
|
|
|
|
EXPLANATION OF HOW THE ENTITY FIELDS ARE USED ( thnx? np.. =) )
|
|
---------------------------------------------
|
|
|
|
For field generator entity:
|
|
---------------------------
|
|
|
|
.fieldgen_status - Holds current status of every field generator, FIELDGEN_XXXX determines
|
|
.fieldgen_hasfield - Boolean value, determines if field generator is currently supporting a force field
|
|
.fieldgen_field - This points to the force field, if none its always 'NIL'
|
|
.no_grenades_1 - Controls delay between tries to link (only affects sound currently, it tries to link every frame)
|
|
.no_grenades_1 - Controls delay for field to go up again after beeing disabled
|
|
.tp_grenades_1 - Controls delay of the WORKING status
|
|
.has_teleporter - Used to flash generators when field is activated
|
|
.has_camera - Controls delay between cells take
|
|
.has_tesla - Boolean, forced status.
|
|
|
|
For force field entity:
|
|
-----------------------
|
|
|
|
.demon_one - Points to the first field generator
|
|
.demon_two - Points to the 2nd generator
|
|
.fieldgen_status - Hum sound running, boolean
|
|
.fieldgen_hasfield - Shield sound running, boolean
|
|
.has_tesla - Controls delay between hums
|
|
.has_sentry - Controls delay between shield sounds
|
|
.dmg - Next damage the field will do
|
|
.has_camera - Used to control the rate at which the field touch sound/visual effects are done (4hz)
|
|
.forcefield_offset - Offset to one of the gens from origin, used for lightning effects
|
|
|
|
================================================================================================*/
|
|
|
|
void() CheckDistance;
|
|
entity(entity fieldgen) Find_OtherGen;
|
|
float(entity fieldgen1, entity fieldgen2) FieldGens_CanLink;
|
|
float(entity fieldgen) FieldGen_CanIdle;
|
|
float(entity field) IsValidFieldGen;
|
|
float (vector targ, vector check) vis2orig;
|
|
float(entity field) IsValidField;
|
|
void(entity tfield, entity gen1) Field_UpdateSounds;
|
|
void(entity tfield) Field_MakeVisual;
|
|
float(entity tfield) FieldIsImproved;
|
|
float(entity tfield) FieldIsMalfunctioning;
|
|
void() Field_touch;
|
|
void() Field_touch_SUB;
|
|
float(entity e1, entity e2) EntsTouching2;
|
|
void(entity tfield, vector where, entity thing) FieldExplosion;
|
|
float(entity tfield, entity who) Field_ItCanPass;
|
|
float(entity tfield, entity who) Field_ShouldDamage;
|
|
|
|
#ifdef FIELD_FORCEMODE
|
|
|
|
entity(entity myself) GetMyFieldGen;
|
|
float(entity tfield) Field_GetForcedStatus;
|
|
void(float value) SetFieldForcedStatus; // player function (self = player) cuts disabled time also
|
|
float() GetFieldForcedStatus; // player
|
|
|
|
#endif
|
|
|
|
//=========================================================================================
|
|
// field generator model and sounds
|
|
|
|
void() Field_Precache =
|
|
{
|
|
precache_sound ("misc/null.wav");
|
|
precache_sound2("misc/ffhum.wav");
|
|
precache_sound2("misc/ffbeep.wav");
|
|
precache_sound2("misc/ffield.wav");
|
|
precache_sound2("misc/ffact.wav");
|
|
precache_sound2("misc/fffail.wav");
|
|
precache_model2("progs/ffgen2.mdl");
|
|
};
|
|
|
|
//================================================================================================
|
|
// checks for field generators and removes itself if needed, checks for stuck entities on field
|
|
|
|
void() Field_think =
|
|
{
|
|
if (self.classname != "force_field")
|
|
{
|
|
RPrint("BUG: Not a force field entity was in 'FieldThink()'!\n");
|
|
return;
|
|
}
|
|
|
|
self.has_camera = FALSE;
|
|
|
|
// checks for removal...
|
|
if (!IsValidFieldGen(self.demon_one) || !IsValidFieldGen(self.demon_two))
|
|
{
|
|
if (IsValidFieldGen(self.demon_one))
|
|
{
|
|
self.demon_one.fieldgen_hasfield = FALSE;
|
|
self.demon_one.fieldgen_field = NIL;
|
|
}
|
|
|
|
if (IsValidFieldGen(self.demon_two))
|
|
{
|
|
self.demon_two.fieldgen_hasfield = FALSE;
|
|
self.demon_two.fieldgen_field = NIL;
|
|
}
|
|
|
|
self.demon_one = NIL;
|
|
self.demon_two = NIL;
|
|
|
|
dremove(self);
|
|
|
|
RPrint("Debug: Field entity removed in Field_think()\n"); //shouldnt happen
|
|
}
|
|
else
|
|
self.nextthink = time + 0.25; // 4hz check
|
|
|
|
if (IsValidFieldGen(self.demon_one))
|
|
if (self.demon_one.fieldgen_status == FIELDGEN_ISWORKING)
|
|
Field_MakeVisual(self);
|
|
|
|
// checks for anything stuck in field :)
|
|
local entity te;
|
|
local float frange;
|
|
|
|
frange = FIELDGEN_RANGE;
|
|
|
|
if (FieldIsImproved(self) & IMPROVED_FOUR)
|
|
frange = FIELDGEN_HACKEDRANGE;
|
|
|
|
te = findradius(self.origin,frange);
|
|
while (te)
|
|
{
|
|
if (te != self)
|
|
if (te != self.demon_one)
|
|
if (te != self.demon_two)
|
|
if (te.velocity == '0 0 0')
|
|
// if (!IsBuilding(te)) // no screwing with the buildings :)
|
|
if (te.classname != "force_field")
|
|
if (EntsTouching2(te,self))
|
|
{
|
|
other = te;
|
|
deathmsg = DMSG_STUCK_FORCEFIELD;
|
|
self.dmg = FIELDGEN_DMGINSIDE; // this gonna hurt
|
|
Field_touch_SUB();
|
|
}
|
|
te = te.chain;
|
|
}
|
|
};
|
|
|
|
//=============================================================================================
|
|
// is one entity actually inside the other one? (this avoids using a stupid trigger)
|
|
|
|
float(entity e1, entity e2) EntsTouching2 =
|
|
{
|
|
if (hullpointcontents (e2, e1.mins, e1.maxs, e1.origin) != CONTENTS_SOLID)
|
|
return FALSE;
|
|
return TRUE;
|
|
};
|
|
|
|
//=================================================================================
|
|
// these 2 functions return the current hacks that should apply to the field
|
|
|
|
float(entity tfield) FieldIsImproved =
|
|
{
|
|
if (IsValidFieldGen(tfield.demon_one) && IsValidFieldGen(tfield.demon_two))
|
|
return tfield.demon_one.num_mines | tfield.demon_two.num_mines;
|
|
|
|
if (IsValidFieldGen(tfield.demon_one))
|
|
return tfield.demon_one.num_mines;
|
|
|
|
if (IsValidFieldGen(tfield.demon_two))
|
|
return tfield.demon_two.num_mines;
|
|
|
|
return 0;
|
|
};
|
|
|
|
float(entity tfield) FieldIsMalfunctioning =
|
|
{
|
|
if (IsValidFieldGen(tfield.demon_one) && IsValidFieldGen(tfield.demon_two))
|
|
return tfield.demon_one.is_malfunctioning | tfield.demon_two.is_malfunctioning;
|
|
|
|
if (IsValidFieldGen(tfield.demon_one))
|
|
return tfield.demon_one.is_malfunctioning;
|
|
|
|
if (IsValidFieldGen(tfield.demon_two))
|
|
return tfield.demon_two.is_malfunctioning;
|
|
|
|
return 0;
|
|
};
|
|
|
|
//=================================================================
|
|
// self is the field, disables it
|
|
|
|
void() DisableField =
|
|
{
|
|
if (IsValidFieldGen(self.demon_one))
|
|
{
|
|
self.demon_one.fieldgen_status = FIELDGEN_ISDISABLED;
|
|
self.demon_one.no_grenades_2 = time + FIELDGEN_TIMEDISABLED;
|
|
}
|
|
|
|
if (IsValidFieldGen(self.demon_two))
|
|
{
|
|
self.demon_two.fieldgen_status = FIELDGEN_ISDISABLED;
|
|
self.demon_two.no_grenades_2 = time + FIELDGEN_TIMEDISABLED;
|
|
}
|
|
|
|
sound (self, CHAN_VOICE, "misc/ffbeep.wav", 0.4, ATTN_IDLE);
|
|
};
|
|
|
|
//=========================================================================================
|
|
// applies damage and makes the sound and visual effect for the forcefield shock
|
|
|
|
void() Field_touch_SUB =
|
|
{
|
|
local float doFX;
|
|
local float shoulddamage;
|
|
doFX = TRUE;
|
|
|
|
if (FieldIsMalfunctioning(self) & SCREWUP_THREE) // reduce output
|
|
self.dmg = 1;
|
|
|
|
if (other.classname == "player") // PLAYERS
|
|
{
|
|
//dprint (vtos (other.origin) + "\n");
|
|
if (other.playerclass == PC_UNDEFINED) // Observers
|
|
return;
|
|
|
|
if (Field_ItCanPass(self, other))
|
|
{
|
|
DisableField();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
self.velocity = self.velocity - (self.velocity*4) + '0 0 1';
|
|
|
|
shoulddamage = Field_ShouldDamage(self,other);
|
|
if (shoulddamage == 1)
|
|
TF_T_Damage (other, self, self.real_owner, self.dmg, TF_TD_NOTTEAM, TF_TD_ELECTRICITY);
|
|
else if (shoulddamage == 2) // hacked to hurt teammates
|
|
{
|
|
if (deathmsg == DMSG_FORCEFIELD)
|
|
deathmsg = DMSG_FF_HACKED;
|
|
else /* if (deathmsg == DMSG_FORCEFIELD_STUCK) */
|
|
deathmsg = DMSG_FF_STUCK_HACKED;
|
|
TF_T_Damage (other, self, self.martyr_enemy, self.dmg, TF_TD_NOTTEAM, TF_TD_ELECTRICITY);
|
|
}
|
|
if (shoulddamage)
|
|
self.demon_one.has_camera = self.demon_two.has_camera = 0;
|
|
}
|
|
|
|
}
|
|
else if (other.classname != "force_field") // non player entities
|
|
{
|
|
if (IsMonster(other))
|
|
{
|
|
if (Field_ItCanPass(self, other))
|
|
{
|
|
DisableField();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
shoulddamage = Field_ShouldDamage(self,other);
|
|
if (shoulddamage == 1)
|
|
TF_T_Damage (other, self, self.real_owner, self.dmg, TF_TD_NOTTEAM, TF_TD_ELECTRICITY);
|
|
else if (shoulddamage == 2) // hacked to hurt teammates
|
|
{
|
|
if (deathmsg == DMSG_FORCEFIELD)
|
|
deathmsg = DMSG_FF_HACKED;
|
|
else /* if (deathmsg == DMSG_FORCEFIELD_STUCK) */
|
|
deathmsg = DMSG_FF_STUCK_HACKED;
|
|
TF_T_Damage (other, self, self.martyr_enemy, self.dmg, TF_TD_NOTTEAM, TF_TD_ELECTRICITY);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if(other.movetype != MOVETYPE_NONE && other.movetype != MOVETYPE_PUSH && other.movetype != MOVETYPE_BOUNCE && other.movetype != MOVETYPE_BOUNCEMISSILE)
|
|
{
|
|
checkmove(other.origin, other.mins, other.maxs, other.origin + '0 0 1', MOVE_NORMAL, other);
|
|
|
|
if (trace_fraction == 1)
|
|
{
|
|
local vector forward = crossproduct(normalize(self.demon_one.origin - self.demon_two.origin), '0 0 1');
|
|
|
|
if (crossproduct (normalize(self.origin - other.origin), normalize(self.demon_one.origin -self.demon_two.origin))
|
|
* '0 0 1' > 0)
|
|
other.velocity = -300 * forward;
|
|
else
|
|
other.velocity = 300 * forward;
|
|
|
|
other.velocity_z += 50;
|
|
|
|
setorigin(other, other.origin + '0 0 1');
|
|
}
|
|
|
|
else if (IsBuilding(other))
|
|
{
|
|
sprint(other.real_owner, PRINT_HIGH, "The forcefield short-circuits your structure\n");
|
|
TF_T_Damage(other, NIL, NIL, 9999, TF_TD_IGNOREARMOUR, 0);
|
|
}
|
|
}
|
|
|
|
FieldExplosion(self,other.origin,other);
|
|
PutFieldWork(self);
|
|
|
|
};
|
|
|
|
#ifdef FIELD_FORCEMODE
|
|
|
|
//==========================================================================
|
|
// gets one of our field generators
|
|
|
|
entity(entity myself) GetMyFieldGen =
|
|
{
|
|
local entity te;
|
|
local float foundit;
|
|
te = NIL;
|
|
foundit = FALSE;
|
|
|
|
te = find(NIL, classname, "building_fieldgen");
|
|
while (te && foundit == FALSE)
|
|
{
|
|
if (te.real_owner == myself) // is it ours?
|
|
foundit = TRUE; // yeah, found it
|
|
|
|
if (foundit == FALSE) // our search must continue...
|
|
te = find(te, classname, "building_fieldgen");
|
|
}
|
|
|
|
return te;
|
|
};
|
|
|
|
|
|
//=========================================================================
|
|
// these functions retrieve and set the current 'forced status' on a field
|
|
|
|
float(entity tfield) Field_GetForcedStatus =
|
|
{
|
|
if (IsValidFieldGen(tfield.demon_one) && IsValidFieldGen(tfield.demon_two))
|
|
{
|
|
if (tfield.demon_two.has_tesla || tfield.demon_one.has_tesla)
|
|
return TRUE;
|
|
}
|
|
else if (IsValidFieldGen(tfield.demon_one))
|
|
{
|
|
if (tfield.demon_one.has_tesla)
|
|
return TRUE;
|
|
}
|
|
else if (IsValidFieldGen(tfield.demon_two))
|
|
{
|
|
if (tfield.demon_two.has_tesla)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
};
|
|
|
|
//==============================================================================
|
|
// player functions called on menu.qc to disable/enable forced status on field
|
|
|
|
void(float value) SetFieldForcedStatus =
|
|
{
|
|
local entity gen1, gen2;
|
|
gen1 = GetMyFieldGen(self);
|
|
gen2 = Find_OtherGen(gen1);
|
|
|
|
if (IsValidFieldGen(gen1))
|
|
{
|
|
gen1.has_tesla = value;
|
|
if (value)
|
|
gen1.no_grenades_2 = time;
|
|
}
|
|
|
|
if (IsValidFieldGen(gen2))
|
|
{
|
|
gen2.has_tesla = value;
|
|
if (value)
|
|
gen2.no_grenades_2 = time;
|
|
}
|
|
};
|
|
|
|
float() GetFieldForcedStatus =
|
|
{
|
|
local entity gen1, gen2;
|
|
gen1 = GetMyFieldGen(self);
|
|
gen2 = Find_OtherGen(gen1);
|
|
|
|
if (IsValidFieldGen(gen1) && IsValidFieldGen(gen2))
|
|
{
|
|
if (gen1.has_tesla || gen2.has_tesla)
|
|
return TRUE;
|
|
}
|
|
else if (IsValidFieldGen(gen1))
|
|
return gen1.has_tesla;
|
|
else if (IsValidFieldGen(gen2))
|
|
return gen2.has_tesla;
|
|
|
|
return FALSE;
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
//=========================================================================
|
|
// returns TRUE if 'who' entity should be able to pass thru the field
|
|
|
|
float(entity tfield, entity who) Field_ItCanPass =
|
|
{
|
|
if (FieldIsMalfunctioning(tfield) & SCREWUP_ONE)
|
|
return FALSE;
|
|
|
|
/*
|
|
if (who == tfield.real_owner) // field owner - always pass
|
|
return TRUE;
|
|
*/
|
|
if (Field_GetForcedStatus(tfield))
|
|
return FALSE;
|
|
|
|
if (who.classname == "player") // PLAYERS
|
|
{
|
|
if (Teammate(who.team_no, tfield.real_owner.team_no)) // teammate
|
|
return TRUE;
|
|
|
|
if (Teammate(who.undercover_team, tfield.real_owner.team_no)) // spies disguised as our team
|
|
return TRUE;
|
|
}
|
|
else if (IsMonster(who)) // MONSTERS/ARMY
|
|
{
|
|
if (Teammate(who.real_owner.team_no, tfield.real_owner.team_no)) // team monster
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
};
|
|
|
|
//=========================================================================
|
|
// returns 1 or 2 if 'who' entity should be damaged by the field
|
|
// 1 if it's an enemy, 2 if a friendly and has been hacked
|
|
|
|
float(entity tfield, entity who) Field_ShouldDamage =
|
|
{
|
|
local float fr;
|
|
if (FieldIsMalfunctioning(tfield) & SCREWUP_ONE)
|
|
fr = 2;
|
|
else
|
|
fr = 0;
|
|
|
|
if (who.classname == "player") // PLAYERS
|
|
{
|
|
if (who == tfield.real_owner) // field owner
|
|
return fr;
|
|
|
|
if (Teammate(who.team_no, tfield.real_owner.team_no)) // teammate
|
|
return fr;
|
|
|
|
if (Teammate(who.undercover_team, tfield.real_owner.team_no)) // spies disguised as our team
|
|
return fr;
|
|
}
|
|
else if (IsMonster(who)) // MONSTERS/ARMY
|
|
{
|
|
if (Teammate(who.real_owner.team_no, tfield.real_owner.team_no)) // team monster
|
|
return fr;
|
|
}
|
|
|
|
return 1;
|
|
};
|
|
|
|
//=============================================================================
|
|
// applies the particle effect and electric sound (at max 4hz rate)
|
|
|
|
void(entity tfield, vector where, entity thing) FieldExplosion =
|
|
{
|
|
if (!tfield.has_camera)
|
|
{
|
|
if (!thing || thing.is_removed) return;
|
|
|
|
local vector whereFX;
|
|
whereFX = where;
|
|
whereFX_z = tfield.origin_z;
|
|
|
|
spawnFOG(whereFX);
|
|
sound(tfield,CHAN_BODY,"effects/crunch.wav",0.5,ATTN_NORM);
|
|
|
|
tfield.has_camera = TRUE; // cya soon
|
|
}
|
|
};
|
|
|
|
void(entity field) PutFieldWork =
|
|
{
|
|
if (IsValidFieldGen(field.demon_one))
|
|
field.demon_one.tp_grenades_1 = time + FIELDGEN_SHOCKTIME;
|
|
if (IsValidFieldGen(field.demon_two))
|
|
field.demon_two.tp_grenades_1 = time + FIELDGEN_SHOCKTIME;
|
|
};
|
|
|
|
void() Field_touch =
|
|
{
|
|
if (other.classname == "force_field") return; //avoid weird loops with other fields
|
|
|
|
deathmsg = DMSG_FORCEFIELD;
|
|
if (self.dmg == FIELDGEN_DMGINSIDE)
|
|
deathmsg = DMSG_STUCK_FORCEFIELD;
|
|
else
|
|
self.dmg = FIELDGEN_DMG;
|
|
|
|
Field_touch_SUB();
|
|
};
|
|
|
|
//===================================================================================
|
|
// creates the force field between the 2 generators (only if none currently on)
|
|
|
|
void(entity gen1, entity gen2) Create_Field =
|
|
{
|
|
//local string foo;
|
|
|
|
if (gen1.fieldgen_hasfield || gen2.fieldgen_hasfield)
|
|
return;
|
|
|
|
//foo = "gen1: " + vtos (gen1.origin) + " " + vtos (gen1.absmin) + " " + vtos (gen1.absmax);
|
|
//dprint(foo + "\n");
|
|
//foo = "gen2: " + vtos (gen2.origin) + " " + vtos (gen2.absmin) + " " + vtos (gen2.absmax);
|
|
//dprint(foo + "\n");
|
|
/* dprint("gen1:\n");
|
|
eprint(gen1.fieldgen_field);
|
|
dprint("gen2:\n");
|
|
eprint(gen2.fieldgen_field);
|
|
dprint("done\n"); */
|
|
|
|
if (gen1.fieldgen_field || gen2.fieldgen_field) // CHECK
|
|
return;
|
|
|
|
// already checked b4 on CanLink -> CanIdle
|
|
/*if (!IsValidFieldGen(gen1) || !IsValidFieldGen(gen2))
|
|
return;*/
|
|
|
|
gen1.fieldgen_status = FIELDGEN_ISENABLED;
|
|
gen2.fieldgen_status = FIELDGEN_ISENABLED;
|
|
|
|
gen1.fieldgen_hasfield = TRUE;
|
|
gen2.fieldgen_hasfield = TRUE;
|
|
|
|
local entity tfield;
|
|
|
|
// generate field
|
|
tfield = spawn();
|
|
tfield.classname = "force_field";
|
|
tfield.owner = NIL;
|
|
tfield.real_owner = gen1.real_owner; // --> player
|
|
|
|
tfield.think = Field_think;
|
|
tfield.touch = Field_touch;
|
|
tfield.nextthink = time + 0.25;
|
|
|
|
// set pos and size
|
|
tfield.size_x = vlen (gen1.origin - gen2.origin) - 10;
|
|
tfield.size_y = 4;
|
|
tfield.size_z = 48; // was 64, but 48 is more realistic
|
|
tfield.maxs = tfield.size * 0.5; // FIXME: / 2 is broken
|
|
tfield.mins = -tfield.maxs;
|
|
tfield.origin = AVG (gen1.origin, gen2.origin);
|
|
tfield.origin_z = AVG (gen1.absmax_z, gen2.absmax_z) - tfield.maxs_z - 1;
|
|
tfield.forcefield_offset = (gen2.origin - gen1.origin);
|
|
|
|
local vector right, forward, up;
|
|
|
|
up = '0 0 1';
|
|
right = normalize (gen2.origin - gen1.origin);
|
|
forward = crossproduct (up, right);
|
|
|
|
// local string foo;
|
|
// foo = "right: " + vtos (right) + "\nforward: " + vtos (forward)
|
|
// + "\nup: " + vtos (up) + "\nmins: " + vtos (tfield.mins)
|
|
// + " maxs: " + vtos (tfield.maxs) + "\norigin: " + vtos (tfield.origin) + "\n";
|
|
// dprint (foo);
|
|
tfield.rotated_bbox = getboxhull();
|
|
rotate_bbox (tfield.rotated_bbox, right, forward, up, tfield.mins,
|
|
tfield.maxs);
|
|
|
|
local vector mins, maxs;
|
|
mins = getboxbounds (tfield.rotated_bbox, 0);
|
|
maxs = getboxbounds (tfield.rotated_bbox, 1);
|
|
|
|
// apply stuff
|
|
tfield.movetype = MOVETYPE_NONE;
|
|
tfield.solid = SOLID_BBOX;
|
|
setsize(tfield, mins, maxs);
|
|
setorigin(tfield, tfield.origin);
|
|
// foo = "min: " + vtos (mins) + "\n" +
|
|
// "max: " + vtos (maxs) + "\n" +
|
|
// "absmin: " + vtos (tfield.absmin) + "\n" +
|
|
// "absmax: " + vtos (tfield.absmax) + "\n";
|
|
// dprint (foo + "\n");
|
|
|
|
// assign the pointers on the field generators
|
|
gen1.fieldgen_field = tfield;
|
|
gen2.fieldgen_field = tfield;
|
|
|
|
// assign the pointers to generators on ourselves
|
|
tfield.demon_one = gen1;
|
|
tfield.demon_two = gen2;
|
|
|
|
//make activating sound
|
|
sound (tfield, CHAN_VOICE, "misc/ffact.wav", 0.2, ATTN_NORM);
|
|
|
|
// flash generators
|
|
gen1.effects = EF_DIMLIGHT;
|
|
gen1.has_teleporter = TRUE;
|
|
gen1.skin = 2;
|
|
gen2.effects = EF_DIMLIGHT;
|
|
gen2.has_teleporter = TRUE;
|
|
gen2.skin = 2;
|
|
|
|
if (gen1.martyr_enemy)
|
|
tfield.martyr_enemy = gen1.martyr_enemy;
|
|
else if (gen1.martyr_enemy)
|
|
tfield.martyr_enemy = gen2.martyr_enemy;
|
|
else
|
|
tfield.martyr_enemy = NIL;
|
|
|
|
/* // make sure the field goes off instantly if there's somebody in it
|
|
local entity oldself;
|
|
oldself = self;
|
|
self = tfield;
|
|
self.think ();
|
|
self = oldself; */
|
|
};
|
|
|
|
//=================================================================0
|
|
// removes the force field (if any)
|
|
|
|
void(entity gen1, entity gen2) Remove_Field =
|
|
{
|
|
if (IsValidFieldGen(gen1))
|
|
{
|
|
if (IsValidField(gen1.fieldgen_field))
|
|
{
|
|
freeboxhull (gen1.fieldgen_field.rotated_bbox);
|
|
dremove(gen1.fieldgen_field);
|
|
}
|
|
gen1.fieldgen_hasfield = FALSE;
|
|
gen1.fieldgen_field = NIL;
|
|
if (IsValidFieldGen(gen2))
|
|
{
|
|
gen2.fieldgen_hasfield = FALSE;
|
|
gen2.fieldgen_field = NIL;
|
|
}
|
|
}
|
|
else if (IsValidFieldGen(gen2))
|
|
{
|
|
if (IsValidField(gen2.fieldgen_field))
|
|
{
|
|
freeboxhull (gen2.fieldgen_field.rotated_bbox);
|
|
dremove(gen2.fieldgen_field);
|
|
}
|
|
gen2.fieldgen_hasfield = FALSE;
|
|
gen2.fieldgen_field = NIL;
|
|
}
|
|
};
|
|
|
|
float(entity field) IsValidField =
|
|
{
|
|
if (!field)
|
|
return FALSE;
|
|
|
|
if (field.classname != "force_field")
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
};
|
|
|
|
float(entity field) IsValidFieldGen =
|
|
{
|
|
if (!field)
|
|
return FALSE;
|
|
|
|
if (field.classname != "building_fieldgen")
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
};
|
|
|
|
//========================================================
|
|
// starts or removes sounds on the field
|
|
|
|
void(entity tfield, entity gen1) Field_UpdateSounds =
|
|
{
|
|
//fieldgen_status : hum
|
|
//fieldgen_hasfield : shield
|
|
|
|
if (IsValidField(tfield)) // only if there is a field currently
|
|
{
|
|
local float playhum, playshield;
|
|
playhum = FALSE; playshield = FALSE;
|
|
|
|
/*if (gen1.fieldgen_status == FIELDGEN_ISOFF) // for some reason we r not working
|
|
{
|
|
playhum = FALSE;
|
|
playshield = FALSE;
|
|
}
|
|
else if (gen1.fieldgen_status == FIELDGEN_ISIDLE) // awaiting for link
|
|
{
|
|
playhum = FALSE;
|
|
playshield = FALSE;
|
|
}
|
|
else if (gen1.fieldgen_status == FIELDGEN_ISDISABLED) // teammate passing thru the field?
|
|
{
|
|
playhum = FALSE;
|
|
playshield = FALSE;
|
|
}
|
|
else*/
|
|
|
|
if (gen1.fieldgen_status == FIELDGEN_ISENABLED)
|
|
{
|
|
playhum = TRUE;
|
|
playshield = FALSE;
|
|
}
|
|
else if (gen1.fieldgen_status == FIELDGEN_ISWORKING)
|
|
{
|
|
playhum = TRUE;
|
|
playshield = TRUE;
|
|
}
|
|
|
|
// MAKE THE SHIT SOUND !
|
|
if (!playhum)
|
|
{
|
|
if (tfield.fieldgen_status)
|
|
{
|
|
sound(tfield,CHAN_MISC,"misc/null.wav",0.5,ATTN_NORM);
|
|
tfield.fieldgen_status = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!tfield.fieldgen_status || tfield.has_tesla < time)
|
|
{
|
|
sound(tfield,CHAN_MISC,"misc/ffhum.wav",0.3,ATTN_NORM);
|
|
tfield.has_tesla = time + 1;
|
|
tfield.fieldgen_status = TRUE;
|
|
}
|
|
}
|
|
|
|
if(!playshield)
|
|
{
|
|
if (tfield.fieldgen_hasfield)
|
|
{
|
|
sound(tfield,CHAN_ITEM,"misc/null.wav",0.2,ATTN_NORM);
|
|
tfield.fieldgen_hasfield = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!tfield.fieldgen_hasfield || tfield.has_sentry < time)
|
|
{
|
|
//TODO?: lower volume as (FIELDGEN_SHOCKTIME - time) decreases
|
|
|
|
sound(tfield,CHAN_ITEM,"misc/ffield.wav",0.4,ATTN_NORM);
|
|
tfield.has_sentry = time + 1;
|
|
tfield.fieldgen_hasfield = TRUE;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
//====================================================================
|
|
// do the lightning stuff while field is FIELDGEN_ISWORKING
|
|
|
|
void(entity tfield) Field_MakeVisual =
|
|
{
|
|
if (IsValidField(tfield))
|
|
{
|
|
local vector f;
|
|
|
|
f = tfield.origin + (tfield.forcefield_offset * 0.5 * (random() * 2 - 1));
|
|
|
|
WriteByte (MSG_MULTICAST, SVC_TEMPENTITY);
|
|
if (random() > 0.5)
|
|
WriteByte (MSG_MULTICAST, TE_LIGHTNING2);
|
|
else
|
|
WriteByte (MSG_MULTICAST, TE_LIGHTNING1);
|
|
WriteEntity (MSG_MULTICAST, tfield);
|
|
WriteCoord (MSG_MULTICAST, f_x);
|
|
WriteCoord (MSG_MULTICAST, f_y);
|
|
WriteCoord (MSG_MULTICAST, tfield.origin_z + tfield.mins_z);
|
|
WriteCoord (MSG_MULTICAST, f_x);
|
|
WriteCoord (MSG_MULTICAST, f_y);
|
|
WriteCoord (MSG_MULTICAST, tfield.origin_z + tfield.maxs_z);
|
|
}
|
|
};
|
|
|
|
//==================================================
|
|
// called every frame by the field generators
|
|
|
|
void() FieldGen_think =
|
|
{
|
|
local entity othergen;
|
|
othergen = Find_OtherGen(self); // get our brother
|
|
|
|
if (FieldGens_CanLink(self,othergen))
|
|
Create_Field(self,othergen); // checks redundancy itself
|
|
else
|
|
Remove_Field(self,othergen); // checks redundancy itself
|
|
|
|
// field main loop (ai? heh.. my cat is smarter than these force fields)
|
|
if (self.fieldgen_status == FIELDGEN_ISOFF) // for some reason we r not working
|
|
{
|
|
self.effects = 0;
|
|
self.skin = 0;
|
|
|
|
if (FieldGen_CanIdle(self)) // can we go idle?
|
|
self.fieldgen_status = FIELDGEN_ISIDLE;
|
|
|
|
}
|
|
else if (self.fieldgen_status == FIELDGEN_ISIDLE) // awaiting for link
|
|
{
|
|
self.effects = 0;
|
|
self.skin = 0;
|
|
|
|
if (self.no_grenades_1 < time) // trying to link sound/flash time
|
|
{
|
|
sound (self, CHAN_WEAPON, "misc/fffail.wav", 0.5, ATTN_IDLE);
|
|
self.skin = 1;
|
|
self.effects = EF_DIMLIGHT;
|
|
self.no_grenades_1 = time + FIELDGEN_LINKTIME;
|
|
}
|
|
|
|
}
|
|
else if (self.fieldgen_status == FIELDGEN_ISDISABLED) // teammate passing thru the field?
|
|
{
|
|
self.effects = 0;
|
|
self.skin = 0;
|
|
|
|
// time check
|
|
if (self.no_grenades_2 < time) // can we go idle?
|
|
{
|
|
self.fieldgen_status = FIELDGEN_ISIDLE;
|
|
self.tp_grenades_1 = 0;
|
|
}
|
|
|
|
}
|
|
else if (self.fieldgen_status == FIELDGEN_ISENABLED)
|
|
{
|
|
if (!self.has_teleporter)
|
|
{
|
|
self.effects = 0;
|
|
self.skin = 1;
|
|
}
|
|
|
|
if (self.fieldgen_hasfield == FALSE)
|
|
self.fieldgen_status = FIELDGEN_ISIDLE;
|
|
|
|
if (self.tp_grenades_1 >= time)
|
|
self.fieldgen_status = FIELDGEN_ISWORKING;
|
|
}
|
|
else if (self.fieldgen_status == FIELDGEN_ISWORKING)
|
|
{
|
|
self.effects = EF_DIMLIGHT;
|
|
self.skin = 2;
|
|
|
|
if (self.has_camera <= time)
|
|
{
|
|
self.ammo_cells = self.ammo_cells - FIELDGEN_CELLSCOST;
|
|
self.has_camera = time + 1;
|
|
}
|
|
|
|
if (self.fieldgen_hasfield == FALSE)
|
|
self.fieldgen_status = FIELDGEN_ISIDLE;
|
|
else if (self.tp_grenades_1 <= time)
|
|
self.fieldgen_status = FIELDGEN_ISENABLED;
|
|
}
|
|
|
|
Field_UpdateSounds(self.fieldgen_field, self); // update force field sounds
|
|
|
|
if (!FieldGen_CanIdle(self)) // turn us off if needed
|
|
self.fieldgen_status = FIELDGEN_ISOFF;
|
|
|
|
self.has_teleporter = FALSE; // resets 'flash' status bypass
|
|
self.nextthink = time + 0.1;
|
|
};
|
|
|
|
//=======================================================================
|
|
// returns TRUE if the generator could currently go to idle status
|
|
|
|
float(entity fieldgen) FieldGen_CanIdle =
|
|
{
|
|
if (!(IsValidFieldGen(fieldgen)))
|
|
return FALSE;
|
|
|
|
if (fieldgen.ammo_cells >= FIELDGEN_CELLSCOST &&
|
|
!(fieldgen.is_malfunctioning & SCREWUP_FOUR)
|
|
&& fieldgen.health > 0)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
};
|
|
|
|
//=======================================================================
|
|
// returns TRUE if both generators could currently generate the field
|
|
|
|
float(entity fieldgen1, entity fieldgen2) FieldGens_CanLink =
|
|
{
|
|
if (!(IsValidFieldGen(fieldgen1)) || !(IsValidFieldGen(fieldgen2)))
|
|
return FALSE;
|
|
|
|
if (!visible2(fieldgen1,fieldgen2))
|
|
return FALSE;
|
|
|
|
local float r;
|
|
r = vlen(fieldgen1.origin - fieldgen2.origin); // get distance between generators
|
|
|
|
// if ((fieldgen1.num_mines & IMPROVED_FOUR && fieldgen2.num_mines & IMPROVED_FOUR) && r > FIELDGEN_HACKEDRANGE2)
|
|
// return FALSE;
|
|
|
|
/*if (fieldgen1.num_mines & IMPROVED_FOUR || fieldgen2.num_mines & IMPROVED_FOUR)
|
|
{
|
|
if ((fieldgen1.num_mines & IMPROVED_FOUR && fieldgen2.num_mines & IMPROVED_FOUR) && r > FIELDGEN_HACKEDRANGE2)
|
|
return FALSE;
|
|
else
|
|
if ((fieldgen1.num_mines & IMPROVED_FOUR || fieldgen2.num_mines & IMPROVED_FOUR) && r > FIELDGEN_HACKEDRANGE)
|
|
return FALSE;
|
|
}*/
|
|
|
|
if ((fieldgen1.num_mines & IMPROVED_FOUR || fieldgen2.num_mines & IMPROVED_FOUR) && r > FIELDGEN_HACKEDRANGE)
|
|
return FALSE;
|
|
|
|
if (r > FIELDGEN_RANGE && !(fieldgen1.num_mines & IMPROVED_FOUR || fieldgen2.num_mines & IMPROVED_FOUR))
|
|
return FALSE;
|
|
|
|
if (fabs(fieldgen1.origin_z - fieldgen2.origin_z) > FIELDGEN_MAXZ)
|
|
return FALSE;
|
|
|
|
if (fieldgen1.fieldgen_status == FIELDGEN_ISDISABLED || fieldgen2.fieldgen_status == FIELDGEN_ISDISABLED)
|
|
return FALSE;
|
|
|
|
if (fieldgen1.fieldgen_status == FIELDGEN_ISOFF || fieldgen2.fieldgen_status == FIELDGEN_ISOFF)
|
|
return FALSE;
|
|
|
|
if (FieldGen_CanIdle(fieldgen1) && FieldGen_CanIdle(fieldgen2))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
};
|
|
|
|
//=============================================================================================
|
|
// initialize field generator stuff just after beeing built, called on engineer.qc
|
|
|
|
void(entity field) Field_Built =
|
|
{
|
|
field.touch = NIL;
|
|
field.think = FieldGen_think;
|
|
field.nextthink = time + 0.1;
|
|
field.fieldgen_status = FIELDGEN_ISIDLE; // we start on IDLE status (searching for other gen to link)
|
|
field.fieldgen_hasfield = FALSE;
|
|
field.no_grenades_1 = time + 3;
|
|
field.fieldgen_field = NIL;
|
|
field.martyr_enemy = NIL;
|
|
};
|
|
|
|
//==============================================================
|
|
// returns our other generator (if any)
|
|
|
|
entity(entity fieldgen) Find_OtherGen =
|
|
{
|
|
local entity te;
|
|
local float foundit;
|
|
te = NIL;
|
|
foundit = FALSE;
|
|
|
|
te = find(NIL, classname, "building_fieldgen");
|
|
while (te && foundit == FALSE)
|
|
{
|
|
if (te.real_owner == fieldgen.real_owner) // is it ours?
|
|
if (te != fieldgen) // and not the same generator..
|
|
foundit = TRUE; // yeah, found it
|
|
|
|
if (foundit == FALSE) // our search must continue...
|
|
te = find(te, classname, "building_fieldgen");
|
|
}
|
|
|
|
return te;
|
|
};
|
|
|
|
//=========================================================================================
|
|
// returns the place where field gen could be built related to player current pos and yaw
|
|
// called on engineer.qc, place is the origin passed where other kind of buildings are built
|
|
// FIXME: changed to allow placing anywhere. comments need updating
|
|
|
|
void (vector place) WhereGen =
|
|
{
|
|
// if we have no field generator currently, it can be placed anywhere
|
|
if (self.has_fieldgen == 0)
|
|
return;
|
|
|
|
local float r, foundit;
|
|
local entity te = NIL;
|
|
|
|
foundit = FALSE;
|
|
|
|
// find the other generator
|
|
do {
|
|
te = find (te, classname, "building_fieldgen");
|
|
if (te.real_owner == self)
|
|
foundit = TRUE;
|
|
} while (te && foundit == FALSE);
|
|
/*
|
|
te = find (NIL, classname, "building_fieldgen");
|
|
while (te && foundit == FALSE)
|
|
{
|
|
if (te.real_owner == self) // is it ours?
|
|
foundit = TRUE; // yeah, found it
|
|
|
|
if (foundit == FALSE) // our search must continue...
|
|
te = find (te, classname, "building_fieldgen");
|
|
}*/
|
|
|
|
// check for error getting the other gen
|
|
if (!te || te.classname != "building_fieldgen" || foundit == FALSE)
|
|
{
|
|
RPrint("BUG: Error on field generator placement routine. 'WhereGen()'\n");
|
|
return;
|
|
}
|
|
|
|
// print message if they wont link
|
|
if (!vis2orig (te.origin,place))
|
|
sprint (self, PRINT_HIGH,
|
|
"Your field generators won't link, there are obstacles between them!\n");
|
|
|
|
r = vlen(te.origin - place); // get distance between generators
|
|
|
|
if (r > FIELDGEN_HACKEDRANGE && te.num_mines & IMPROVED_FOUR)
|
|
sprint (self, PRINT_HIGH,
|
|
"Your field generators are too far away to link, even hacked\n");
|
|
|
|
if (r > FIELDGEN_RANGE && !(te.num_mines & IMPROVED_FOUR))
|
|
sprint (self, PRINT_HIGH,
|
|
"Your field generators are too far away to link\n");
|
|
|
|
if (fabs (place_z - te.origin_z) > FIELDGEN_MAXZ)
|
|
sprint (self, PRINT_HIGH,
|
|
"Your field generators are at different heights, they won't link\n");
|
|
|
|
// return the final building place
|
|
return;
|
|
};
|
|
|
|
//======================================================================
|
|
// damn! our field generator was destroyed. Force field must go down..
|
|
|
|
void() FieldGen_Die =
|
|
{
|
|
self.real_owner.has_fieldgen = self.real_owner.has_fieldgen - 1;
|
|
if (self.real_owner.has_fieldgen < 0) self.real_owner.has_fieldgen = 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);
|
|
|
|
// check if field should be removed..
|
|
local entity othergen;
|
|
othergen = Find_OtherGen(self);
|
|
if (IsValidFieldGen(othergen))
|
|
{
|
|
Remove_Field(self, othergen);
|
|
}
|
|
else
|
|
{
|
|
Remove_Field(self, NIL); // extra removal, not needed i think...
|
|
}
|
|
|
|
sprint(self.real_owner, PRINT_HIGH, "Your field generator was destroyed.\n");
|
|
|
|
//TODO: gibs, one of the tesla, one custom
|
|
|
|
dremove(self);
|
|
};
|
|
|
|
//=========================================================================
|
|
// Engineer has used a Spanner on the field generator
|
|
|
|
void(entity field) Engineer_UseFieldGen =
|
|
{
|
|
self.building = field;
|
|
|
|
if (Teammate(self.building.real_owner.team_no,self.team_no) && self.building.is_malfunctioning & SCREWUP_THREE)
|
|
{
|
|
//if (self.building.is_malfunctioning & SCREWUP_FOUR)
|
|
//{
|
|
sprint(self,PRINT_HIGH,"Trapped field generator, have a nice day!\n");
|
|
|
|
deathmsg = DMSG_FGTRAP;
|
|
//used to do radius damage
|
|
TF_T_Damage(self, self.building, self.building.martyr_enemy, FGTRAP_DMG, 0, TF_TD_ELECTRICITY);
|
|
return;
|
|
//}
|
|
}
|
|
|
|
local entity dist_checker;
|
|
local string st;
|
|
|
|
sprint(self, PRINT_HIGH, "Field Generator has ");
|
|
st = ftos(field.health);
|
|
sprint(self, PRINT_HIGH, st);
|
|
sprint(self, PRINT_HIGH, "¯");
|
|
st = ftos(field.max_health);
|
|
sprint(self, PRINT_HIGH, st);
|
|
sprint(self, PRINT_HIGH, " èåáìôè, ");
|
|
st = ftos(field.ammo_cells);
|
|
sprint(self, PRINT_HIGH, st);
|
|
sprint(self, PRINT_HIGH, "¯");
|
|
st = ftos(field.maxammo_cells);
|
|
sprint(self, PRINT_HIGH, st);
|
|
sprint(self, PRINT_HIGH, " ãåììó\n");
|
|
|
|
// Pop up the menu
|
|
self.current_menu = MENU_ENGINEER_FIX_FIELDGEN;
|
|
self.menu_count = MENU_REFRESH_RATE;
|
|
|
|
//dodgy
|
|
if (teamplay != 0 && !Teammate(self.building.real_owner.team_no,self.team_no)) {
|
|
Menu_EngineerFix_FieldGen_Input(4);
|
|
return;
|
|
}
|
|
|
|
// Start a Distance checker, which removes the menu if the player
|
|
// gets too far away from the sentry.
|
|
dist_checker = spawn();
|
|
dist_checker.classname = "timer";
|
|
dist_checker.owner = self;
|
|
dist_checker.enemy = field;
|
|
dist_checker.think = CheckDistance;
|
|
dist_checker.nextthink = time + 0.3;
|
|
};
|