#if defined(CSQC)
#include "../dpdefs/csprogsdefs.qh"
#include "defs.qh"
+ #include "../common/triggers/triggers.qh"
#include "../common/util.qh"
#include "autocvars.qh"
#include "bgmscript.qh"
#ifndef BGMSCRIPT_H
#define BGMSCRIPT_H
-.string bgmscript;
-.float bgmscriptattack;
-.float bgmscriptdecay;
-.float bgmscriptsustain;
-.float bgmscriptrelease;
-
.float just_toggled;
void BGMScript_InitEntity(entity e);
}
}
+void LocalCommand_find(int request, int argc)
+{
+ switch(request)
+ {
+ case CMD_REQUEST_COMMAND:
+ {
+ entity client;
+
+ for(client = world; (client = find(client, classname, argv(1))); )
+ print(etos(client), "\n");
+
+ return;
+ }
+
+ default:
+ print("Incorrect parameters for ^2find^7\n");
+ case CMD_REQUEST_USAGE:
+ {
+ print("\nUsage:^3 cl_cmd find classname\n");
+ print(" Where 'classname' is the classname to search for.\n");
+ return;
+ }
+ }
+}
+
void LocalCommand_sendcvar(int request, int argc)
{
switch(request)
CLIENT_COMMAND("handlevote", LocalCommand_handlevote(request, arguments), "System to handle selecting a vote or option") \
CLIENT_COMMAND("hud", LocalCommand_hud(request, arguments), "Commands regarding/controlling the HUD system") \
CLIENT_COMMAND("localprint", LocalCommand_localprint(request, arguments), "Create your own centerprint sent to yourself") \
+ CLIENT_COMMAND("find", LocalCommand_find(request, arguments), "Search through entities for matching classname") \
CLIENT_COMMAND("mv_download", LocalCommand_mv_download(request, arguments), "Retrieve mapshot picture from the server") \
CLIENT_COMMAND("sendcvar", LocalCommand_sendcvar(request, arguments), "Send a cvar to the server (like weaponpriority)") \
/* nothing */
pointparticles(self.team, org, '0 0 0', 1);
}
-void DamageEffect(vector hitorg, float dmg, int type, int specnum)
+void DamageEffect(vector hitorg, float thedamage, int type, int specnum)
{
// particle effects for players and objects damaged by weapons (eg: flames coming out of victims shot with rockets)
return; // allow a single damage on non-skeletal models
}
- life = bound(autocvar_cl_damageeffect_lifetime_min, dmg * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max);
+ life = bound(autocvar_cl_damageeffect_lifetime_min, thedamage * autocvar_cl_damageeffect_lifetime, autocvar_cl_damageeffect_lifetime_max);
effectname = get_weaponinfo(DEATH_WEAPONOF(type)).netname;
void Ent_DamageInfo(float isNew)
{
- float dmg, rad, edge, thisdmg;
+ float thedamage, rad, edge, thisdmg;
bool hitplayer = false;
int species, forcemul;
vector force, thisforce;
w_org.y = ReadCoord();
w_org.z = ReadCoord();
- dmg = ReadByte();
+ thedamage = ReadByte();
rad = ReadByte();
edge = ReadByte();
force = decompressShortVector(ReadShort());
continue;
if(thisdmg < 0)
thisdmg = 0;
- if(dmg)
+ if(thedamage)
{
- thisdmg = dmg + (edge - dmg) * thisdmg;
- thisforce = forcemul * vlen(force) * (thisdmg / dmg) * normalize(self.origin - w_org);
+ thisdmg = thedamage + (edge - thedamage) * thisdmg;
+ thisforce = forcemul * vlen(force) * (thisdmg / thedamage) * normalize(self.origin - w_org);
}
else
{
if(vlen(nearest - w_org) > bound(MIN_DAMAGEEXTRARADIUS, self.damageextraradius, MAX_DAMAGEEXTRARADIUS))
continue;
- thisdmg = dmg;
+ thisdmg = thedamage;
thisforce = forcemul * force;
}
.string fx_texture;
.float fx_lifetime;
-void SUB_Remove()
-{ remove(self); }
-
void b_draw()
{
//Draw_CylindricLine(self.fx_start, self.fx_end, self.fx_with, self.fx_texture, 0, time * 3, '1 1 1', 0.7, DRAWFLAG_ADDITIVE, view_origin);
#include "../common/monsters/monsters.qh"
+#include "../common/triggers/include.qh"
+
#include "../warpzonelib/client.qh"
// --------------------------------------------------------------------------
// --------------------------------------------------------------------------
// BEGIN OPTIONAL CSQC FUNCTIONS
-void trigger_touch_generic(void() touchfunc)
-{
- entity e;
- for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
- if(e.isplayermodel)
- {
- vector emin = e.absmin, emax = e.absmax;
- if(self.solid == SOLID_BSP)
- {
- emin -= '1 1 1';
- emax += '1 1 1';
- }
- if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
- {
- other = e;
- touchfunc();
- }
- }
-}
void Ent_RemoveEntCS()
{
void Ent_RadarLink();
void Ent_Init();
void Ent_ScoresInfo();
-#ifdef CSQC
-void ent_func_ladder();
-void ent_trigger_push();
-void ent_target_push();
-void ent_conveyor();
-#endif
void CSQC_Ent_Update(float bIsNewEntity)
{
float t;
case ENT_CLIENT_TRIGGER_PUSH: ent_trigger_push(); break;
case ENT_CLIENT_TARGET_PUSH: ent_target_push(); break;
case ENT_CLIENT_CONVEYOR: ent_conveyor(); break;
+ case ENT_CLIENT_DOOR: ent_door(); break;
+ case ENT_CLIENT_DOOR_TRIGGER: ent_door_trigger(); break;
+ case ENT_CLIENT_PLAT: ent_plat(); break;
+ case ENT_CLIENT_PLAT_TRIGGER: ent_plat_trigger(); break;
+ case ENT_CLIENT_SWAMP: ent_swamp(); break;
default:
//error(strcat(_("unknown entity type in CSQC_Ent_Update: %d\n"), self.enttype));
../common/weapons/weapons.qc // TODO
+../common/triggers/include.qc
+
../csqcmodellib/cl_model.qc
../csqcmodellib/cl_player.qc
../csqcmodellib/interpolate.qc
../server/movelib.qc
-../server/t_halflife.qc
-../server/t_items.qc
-../server/t_jumppads.qc
-../server/t_plats.qc
../server/mutators/mutator_multijump.qc
../server/vehicles/bumblebee.qc
+../server/t_items.qc
+
../warpzonelib/anglestransform.qc
../warpzonelib/client.qc
../warpzonelib/common.qc
R_EndPolygon();
}
-void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float height, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
+void drawhealthbar(vector org, float rot, float h, vector sz, vector hotspot, float width, float theheight, float margin, float border, float align, vector rgb, float a, vector hrgb, float ha, float f)
{
vector o, ri, up;
float owidth; // outer width
up = rotate(up, rot);
owidth = width + 2 * border;
- o = o - up * (margin + border + height) + ri * (sz.x - owidth) * 0.5;
+ o = o - up * (margin + border + theheight) + ri * (sz.x - owidth) * 0.5;
drawquad(o - up * border, ri * owidth, up * border, "", rgb, a, f);
- drawquad(o + up * height, ri * owidth, up * border, "", rgb, a, f);
- drawquad(o, ri * border, up * height, "", rgb, a, f);
- drawquad(o + ri * (owidth - border), ri * border, up * height, "", rgb, a, f);
- drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * height, "", hrgb, ha, f);
+ drawquad(o + up * theheight, ri * owidth, up * border, "", rgb, a, f);
+ drawquad(o, ri * border, up * theheight, "", rgb, a, f);
+ drawquad(o + ri * (owidth - border), ri * border, up * theheight, "", rgb, a, f);
+ drawquad(o + ri * (border + align * ((1 - h) * width)), ri * width * h, up * theheight, "", hrgb, ha, f);
}
// returns location of sprite text
const int ENT_CLIENT_TRIGGER_PUSH = 62;
const int ENT_CLIENT_TARGET_PUSH = 63;
const int ENT_CLIENT_CONVEYOR = 64;
+const int ENT_CLIENT_DOOR = 65;
+const int ENT_CLIENT_DOOR_TRIGGER = 66;
+const int ENT_CLIENT_PLAT = 67;
+const int ENT_CLIENT_PLAT_TRIGGER = 68;
+const int ENT_CLIENT_SWAMP = 69;
const int ENT_CLIENT_HEALING_ORB = 80;
#include "../../server/campaign.qh"
#include "../../server/command/common.qh"
#include "../../server/command/cmd.qh"
+ #include "../triggers/triggers.qh"
#include "../../csqcmodellib/sv_model.qh"
#include "../../server/round_handler.qh"
#include "../../server/tturrets/include/turrets.qh"
#endif // SVQC
#ifdef CSQC
-void SUB_Remove()
-{ remove(self); }
-
void cl_notice_read()
{
entity _notice;
#include "physics.qh"
+#include "triggers/trigger/swamp.qh"
+#include "triggers/trigger/jumppads.qh"
#ifdef SVQC
PM_Accelerate(wishdir, wishspeed, wishspeed, PHYS_ACCELERATE*maxspd_mod, 1, 0, 0, 0);
}
-void PM_check_jumppad()
-{
-#ifdef CSQC
- entity oldself = self;
-
- for(self = world; (self = find(self, classname, "jumppad")); )
- trigger_push_draw();
-
- self = oldself;
-#endif
-}
-
void PM_jetpack(float maxspd_mod)
{
//makevectors(PHYS_INPUT_ANGLES(self).y * '0 1 0');
maxspeed_mod = 1;
-#ifdef SVQC
- if (self.in_swamp) {
+ if (self.in_swamp)
maxspeed_mod *= self.swamp_slowdown; //cvar("g_balance_swamp_moverate");
- }
-#endif
// conveyors: first fix velocity
if (self.conveyor.state)
#endif
CheckPlayerJump();
- PM_check_jumppad();
-
if (self.flags & /* FL_WATERJUMP */ 2048)
{
self.velocity_x = self.movedir_x;
#ifdef CSQC
- #include "../server/t_jumppads.qh"
-
float PM_multijump_checkjump();
void PM_multijump();
- .float speed;
- .float watertype;
- .float jumppadcount;
- .float ladder_time;
- .entity ladder_entity;
+
+ .float watertype;
+
+// TODO
+ #define IS_CLIENT(s) (s).isplayermodel
+ #define IS_PLAYER(s) (s).isplayermodel
+ #define isPushable(s) (s).isplayermodel
float player_multijump;
float player_jumpheight;
--- /dev/null
+#ifdef SVQC
+.float height;
+void func_bobbing_controller_think()
+{
+ vector v;
+ self.nextthink = time + 0.1;
+
+ if(self.owner.active != ACTIVE_ACTIVE)
+ {
+ self.owner.velocity = '0 0 0';
+ return;
+ }
+
+ // calculate sinewave using makevectors
+ makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
+ v = self.owner.destvec + self.owner.movedir * v_forward_y;
+ if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
+ // * 10 so it will arrive in 0.1 sec
+ self.owner.velocity = (v - self.owner.origin) * 10;
+}
+
+/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
+Brush model that moves back and forth on one axis (default Z).
+speed : how long one cycle takes in seconds (default 4)
+height : how far the cycle moves (default 32)
+phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
+noise : path/name of looping .wav file to play.
+dmg : Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime : See above.
+*/
+void spawnfunc_func_bobbing()
+{
+ entity controller;
+ if (self.noise != "")
+ {
+ precache_sound(self.noise);
+ soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+ }
+ if (!self.speed)
+ self.speed = 4;
+ if (!self.height)
+ self.height = 32;
+ // center of bobbing motion
+ self.destvec = self.origin;
+ // time scale to get degrees
+ self.cnt = 360 / self.speed;
+
+ self.active = ACTIVE_ACTIVE;
+
+ // damage when blocked
+ self.blocked = generic_plat_blocked;
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+ self.dmgtime2 = time;
+
+ // how far to bob
+ if (self.spawnflags & 1) // X
+ self.movedir = '1 0 0' * self.height;
+ else if (self.spawnflags & 2) // Y
+ self.movedir = '0 1 0' * self.height;
+ else // Z
+ self.movedir = '0 0 1' * self.height;
+
+ if (!InitMovingBrushTrigger())
+ return;
+
+ // wait for targets to spawn
+ controller = spawn();
+ controller.classname = "func_bobbing_controller";
+ controller.owner = self;
+ controller.nextthink = time + 1;
+ controller.think = func_bobbing_controller_think;
+ self.nextthink = self.ltime + 999999999;
+ self.think = SUB_NullThink; // for PushMove
+
+ // Savage: Reduce bandwith, critical on e.g. nexdm02
+ self.effects |= EF_LOWPRECISION;
+
+ // TODO make a reset function for this one
+}
+#endif
--- /dev/null
+#ifdef SVQC
+#include "../../../server/weapons/common.qh"
+
+.entity sprite;
+
+.float dmg;
+.float dmg_edge;
+.float dmg_radius;
+.float dmg_force;
+.float debrismovetype;
+.float debrissolid;
+.vector debrisvelocity;
+.vector debrisvelocityjitter;
+.vector debrisavelocityjitter;
+.float debristime;
+.float debristimejitter;
+.float debrisfadetime;
+.float debrisdamageforcescale;
+.float debrisskin;
+
+.string mdl_dead; // or "" to hide when broken
+.string debris; // space separated list of debris models
+// other fields:
+// mdl = particle effect name
+// count = particle effect multiplier
+// targetname = target to trigger to unbreak the model
+// target = targets to trigger when broken
+// health = amount of damage it can take
+// spawnflags:
+// 1 = start disabled (needs to be triggered to activate)
+// 2 = indicate damage
+// notes:
+// for mdl_dead to work, origin must be set (using a common/origin brush).
+// Otherwise mdl_dead will be displayed at the map origin, and nobody would
+// want that!
+
+void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
+
+//
+// func_breakable
+// - basically func_assault_destructible for general gameplay use
+//
+void LaunchDebris (string debrisname, vector force)
+{
+ entity dbr = spawn();
+ setorigin(dbr, self.absmin
+ + '1 0 0' * random() * (self.absmax.x - self.absmin.x)
+ + '0 1 0' * random() * (self.absmax.y - self.absmin.y)
+ + '0 0 1' * random() * (self.absmax.z - self.absmin.z));
+ setmodel (dbr, debrisname );
+ dbr.skin = self.debrisskin;
+ dbr.colormap = self.colormap; // inherit team colors
+ dbr.owner = self; // do not be affected by our own explosion
+ dbr.movetype = self.debrismovetype;
+ dbr.solid = self.debrissolid;
+ if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out
+ setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it
+ dbr.velocity_x = self.debrisvelocity.x + self.debrisvelocityjitter.x * crandom();
+ dbr.velocity_y = self.debrisvelocity.y + self.debrisvelocityjitter.y * crandom();
+ dbr.velocity_z = self.debrisvelocity.z + self.debrisvelocityjitter.z * crandom();
+ self.velocity = self.velocity + force * self.debrisdamageforcescale;
+ dbr.avelocity_x = random()*self.debrisavelocityjitter.x;
+ dbr.avelocity_y = random()*self.debrisavelocityjitter.y;
+ dbr.avelocity_z = random()*self.debrisavelocityjitter.z;
+ dbr.damageforcescale = self.debrisdamageforcescale;
+ if(dbr.damageforcescale)
+ dbr.takedamage = DAMAGE_YES;
+ SUB_SetFade(dbr, time + self.debristime + crandom() * self.debristimejitter, self.debrisfadetime);
+}
+
+void func_breakable_colormod()
+{
+ float h;
+ if (!(self.spawnflags & 2))
+ return;
+ h = self.health / self.max_health;
+ if(h < 0.25)
+ self.colormod = '1 0 0';
+ else if(h <= 0.75)
+ self.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5);
+ else
+ self.colormod = '1 1 1';
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+void func_breakable_look_destroyed()
+{
+ float floorZ;
+
+ if(self.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first
+ self.dropped_origin = self.origin;
+
+ if(self.mdl_dead == "")
+ self.model = "";
+ else {
+ if (self.origin == '0 0 0') { // probably no origin brush, so don't spawn in the middle of the map..
+ floorZ = self.absmin.z;
+ setorigin(self,((self.absmax+self.absmin)*.5));
+ self.origin_z = floorZ;
+ }
+ setmodel(self, self.mdl_dead);
+ }
+
+ self.solid = SOLID_NOT;
+}
+
+void func_breakable_look_restore()
+{
+ setmodel(self, self.mdl);
+ if(self.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow
+ setorigin(self, self.dropped_origin);
+ self.solid = SOLID_BSP;
+}
+
+void func_breakable_behave_destroyed()
+{
+ self.health = self.max_health;
+ self.takedamage = DAMAGE_NO;
+ self.bot_attack = false;
+ self.event_damage = func_null;
+ self.state = 1;
+ func_breakable_colormod();
+}
+
+void func_breakable_behave_restore()
+{
+ self.health = self.max_health;
+ if(self.sprite)
+ {
+ WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+ self.takedamage = DAMAGE_AIM;
+ self.bot_attack = true;
+ self.event_damage = func_breakable_damage;
+ self.state = 0;
+ self.nextthink = 0; // cancel auto respawn
+ func_breakable_colormod();
+}
+
+void func_breakable_destroyed()
+{
+ func_breakable_look_destroyed();
+ func_breakable_behave_destroyed();
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+void func_breakable_restore()
+{
+ func_breakable_look_restore();
+ func_breakable_behave_restore();
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+vector debrisforce; // global, set before calling this
+void func_breakable_destroy() {
+ float n, i;
+ string oldmsg;
+
+ activator = self.owner;
+ self.owner = world; // set by W_PrepareExplosionByDamage
+
+ // now throw around the debris
+ n = tokenize_console(self.debris);
+ for(i = 0; i < n; ++i)
+ LaunchDebris(argv(i), debrisforce);
+
+ func_breakable_destroyed();
+
+ if(self.noise)
+ sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+ if(self.dmg)
+ RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, world, self.dmg_force, DEATH_HURTTRIGGER, world);
+
+ if(self.cnt)
+ pointparticles(self.cnt, self.absmin * 0.5 + self.absmax * 0.5, '0 0 0', self.count);
+
+ if(self.respawntime)
+ {
+ self.think = func_breakable_restore;
+ self.nextthink = time + self.respawntime + crandom() * self.respawntimejitter;
+ }
+
+ oldmsg = self.message;
+ self.message = "";
+ SUB_UseTargets();
+ self.message = oldmsg;
+}
+
+void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
+{
+ if(self.state == 1)
+ return;
+ if(self.spawnflags & DOOR_NOSPLASH)
+ if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+ return;
+ if(self.team)
+ if(attacker.team == self.team)
+ return;
+ self.health = self.health - damage;
+ if(self.sprite)
+ {
+ WaypointSprite_Ping(self.sprite);
+ WaypointSprite_UpdateHealth(self.sprite, self.health);
+ }
+ func_breakable_colormod();
+
+ if(self.health <= 0)
+ {
+ debrisforce = force;
+ W_PrepareExplosionByDamage(attacker, func_breakable_destroy);
+ }
+}
+
+void func_breakable_reset()
+{
+ self.team = self.team_saved;
+ func_breakable_look_restore();
+ if(self.spawnflags & 1)
+ func_breakable_behave_destroyed();
+ else
+ func_breakable_behave_restore();
+
+ CSQCMODEL_AUTOUPDATE();
+}
+
+// destructible walls that can be used to trigger target_objective_decrease
+void spawnfunc_func_breakable() {
+ float n, i;
+ if(!self.health)
+ self.health = 100;
+ self.max_health = self.health;
+
+ // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
+ if(!self.debrismovetype) self.debrismovetype = MOVETYPE_BOUNCE;
+ if(!self.debrissolid) self.debrissolid = SOLID_NOT;
+ if(self.debrisvelocity == '0 0 0') self.debrisvelocity = '0 0 140';
+ if(self.debrisvelocityjitter == '0 0 0') self.debrisvelocityjitter = '70 70 70';
+ if(self.debrisavelocityjitter == '0 0 0') self.debrisavelocityjitter = '600 600 600';
+ if(!self.debristime) self.debristime = 3.5;
+ if(!self.debristimejitter) self.debristime = 2.5;
+
+ if(self.mdl != "")
+ self.cnt = particleeffectnum(self.mdl);
+ if(self.count == 0)
+ self.count = 1;
+
+ if(self.message == "")
+ self.message = "got too close to an explosion";
+ if(self.message2 == "")
+ self.message2 = "was pushed into an explosion by";
+ if(!self.dmg_radius)
+ self.dmg_radius = 150;
+ if(!self.dmg_force)
+ self.dmg_force = 200;
+
+ self.mdl = self.model;
+ SetBrushEntityModel();
+
+ self.use = func_breakable_restore;
+
+ // precache all the models
+ if (self.mdl_dead)
+ precache_model(self.mdl_dead);
+ n = tokenize_console(self.debris);
+ for(i = 0; i < n; ++i)
+ precache_model(argv(i));
+ if(self.noise)
+ precache_sound(self.noise);
+
+ self.team_saved = self.team;
+ self.dropped_origin = self.origin;
+
+ self.reset = func_breakable_reset;
+ func_breakable_reset();
+
+ CSQCMODEL_AUTOINIT();
+}
+
+// for use in maps with a "model" key set
+void spawnfunc_misc_breakablemodel() {
+ spawnfunc_func_breakable();
+}
+#endif
--- /dev/null
+#ifdef SVQC
+// button and multiple button
+
+void() button_wait;
+void() button_return;
+
+void button_wait()
+{
+ self.state = STATE_TOP;
+ self.nextthink = self.ltime + self.wait;
+ self.think = button_return;
+ activator = self.enemy;
+ SUB_UseTargets();
+ self.frame = 1; // use alternate textures
+}
+
+void button_done()
+{
+ self.state = STATE_BOTTOM;
+}
+
+void button_return()
+{
+ self.state = STATE_DOWN;
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
+ self.frame = 0; // use normal textures
+ if (self.health)
+ self.takedamage = DAMAGE_YES; // can be shot again
+}
+
+
+void button_blocked()
+{
+ // do nothing, just don't come all the way back out
+}
+
+
+void button_fire()
+{
+ self.health = self.max_health;
+ self.takedamage = DAMAGE_NO; // will be reset upon return
+
+ if (self.state == STATE_UP || self.state == STATE_TOP)
+ return;
+
+ if (self.noise != "")
+ sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+ self.state = STATE_UP;
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
+}
+
+void button_reset()
+{
+ self.health = self.max_health;
+ setorigin(self, self.pos1);
+ self.frame = 0; // use normal textures
+ self.state = STATE_BOTTOM;
+ if (self.health)
+ self.takedamage = DAMAGE_YES; // can be shot again
+}
+
+void button_use()
+{
+ if(self.active != ACTIVE_ACTIVE)
+ return;
+
+ self.enemy = activator;
+ button_fire ();
+}
+
+void button_touch()
+{
+ if (!other)
+ return;
+ if (!other.iscreature)
+ return;
+ if(other.velocity * self.movedir < 0)
+ return;
+ self.enemy = other;
+ if (other.owner)
+ self.enemy = other.owner;
+ button_fire ();
+}
+
+void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if(self.spawnflags & DOOR_NOSPLASH)
+ if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+ return;
+ self.health = self.health - damage;
+ if (self.health <= 0)
+ {
+ self.enemy = damage_attacker;
+ button_fire ();
+ }
+}
+
+
+/*QUAKED spawnfunc_func_button (0 .5 .8) ?
+When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
+
+"angle" determines the opening direction
+"target" all entities with a matching targetname will be used
+"speed" override the default 40 speed
+"wait" override the default 1 second wait (-1 = never return)
+"lip" override the default 4 pixel lip remaining at end of move
+"health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
+"sounds"
+0) steam metal
+1) wooden clunk
+2) metallic click
+3) in-out
+*/
+void spawnfunc_func_button()
+{
+ SetMovedir ();
+
+ if (!InitMovingBrushTrigger())
+ return;
+ self.effects |= EF_LOWPRECISION;
+
+ self.blocked = button_blocked;
+ self.use = button_use;
+
+// if (self.health == 0) // all buttons are now shootable
+// self.health = 10;
+ if (self.health)
+ {
+ self.max_health = self.health;
+ self.event_damage = button_damage;
+ self.takedamage = DAMAGE_YES;
+ }
+ else
+ self.touch = button_touch;
+
+ if (!self.speed)
+ self.speed = 40;
+ if (!self.wait)
+ self.wait = 1;
+ if (!self.lip)
+ self.lip = 4;
+
+ if(self.noise != "")
+ precache_sound(self.noise);
+
+ self.active = ACTIVE_ACTIVE;
+
+ self.pos1 = self.origin;
+ self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
+ self.flags |= FL_NOTARGET;
+
+ button_reset();
+}
+#endif
--- /dev/null
+void conveyor_think()
+{
+#ifdef CSQC
+ // TODO: check if this is what is causing the glitchiness when switching between them
+ float dt = time - self.move_time;
+ self.move_time = time;
+ if(dt <= 0) { return; }
+#endif
+ entity e;
+
+ // set myself as current conveyor where possible
+ for(e = world; (e = findentity(e, conveyor, self)); )
+ e.conveyor = world;
+
+ if(self.state)
+ {
+ for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+ if(!e.conveyor.state)
+ if(isPushable(e))
+ {
+ vector emin = e.absmin;
+ vector emax = e.absmax;
+ if(self.solid == SOLID_BSP)
+ {
+ emin -= '1 1 1';
+ emax += '1 1 1';
+ }
+ if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+ if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+ e.conveyor = self;
+ }
+
+ for(e = world; (e = findentity(e, conveyor, self)); )
+ {
+ if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
+ continue; // done in SV_PlayerPhysics continue;
+
+ setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
+ move_out_of_solid(e);
+#ifdef SVQC
+ UpdateCSQCProjectile(e);
+#endif
+ /*
+ // stupid conveyor code
+ tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
+ if(trace_fraction > 0)
+ setorigin(e, trace_endpos);
+ */
+ }
+ }
+
+#ifdef SVQC
+ self.nextthink = time;
+#endif
+}
+
+#ifdef SVQC
+
+void conveyor_use()
+{
+ self.state = !self.state;
+
+ self.SendFlags |= 2;
+}
+
+void conveyor_reset()
+{
+ self.state = (self.spawnflags & 1);
+
+ self.SendFlags |= 2;
+}
+
+float conveyor_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
+ WriteByte(MSG_ENTITY, sf);
+
+ if(sf & 1)
+ {
+ WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.movedir_x);
+ WriteCoord(MSG_ENTITY, self.movedir_y);
+ WriteCoord(MSG_ENTITY, self.movedir_z);
+
+ WriteByte(MSG_ENTITY, self.speed);
+ WriteByte(MSG_ENTITY, self.state);
+
+ WriteString(MSG_ENTITY, self.targetname);
+ WriteString(MSG_ENTITY, self.target);
+ }
+
+ if(sf & 2)
+ WriteByte(MSG_ENTITY, self.state);
+
+ return true;
+}
+
+void conveyor_init()
+{
+ if (!self.speed)
+ self.speed = 200;
+ self.movedir = self.movedir * self.speed;
+ self.think = conveyor_think;
+ self.nextthink = time;
+ IFTARGETED
+ {
+ self.use = conveyor_use;
+ self.reset = conveyor_reset;
+ conveyor_reset();
+ }
+ else
+ self.state = 1;
+
+ FixSize(self);
+
+ Net_LinkEntity(self, 0, false, conveyor_send);
+
+ self.SendFlags |= 1;
+}
+
+void spawnfunc_trigger_conveyor()
+{
+ SetMovedir();
+ EXACTTRIGGER_INIT;
+ conveyor_init();
+}
+
+void spawnfunc_func_conveyor()
+{
+ SetMovedir();
+ InitMovingBrushTrigger();
+ self.movetype = MOVETYPE_NONE;
+ conveyor_init();
+}
+
+#elif defined(CSQC)
+
+void conveyor_init()
+{
+ self.draw = conveyor_think;
+ self.drawmask = MASK_NORMAL;
+
+ self.movetype = MOVETYPE_NONE;
+ self.model = "";
+ self.solid = SOLID_TRIGGER;
+ self.move_origin = self.origin;
+ self.move_time = time;
+}
+
+void ent_conveyor()
+{
+ float sf = ReadByte();
+
+ if(sf & 1)
+ {
+ self.warpzone_isboxy = ReadByte();
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.movedir_x = ReadCoord();
+ self.movedir_y = ReadCoord();
+ self.movedir_z = ReadCoord();
+
+ self.speed = ReadByte();
+ self.state = ReadByte();
+
+ self.targetname = strzone(ReadString());
+ self.target = strzone(ReadString());
+
+ conveyor_init();
+ }
+
+ if(sf & 2)
+ self.state = ReadByte();
+}
+#endif
--- /dev/null
+#ifdef CSQC
+void ent_conveyor();
+#endif
--- /dev/null
+/*
+
+Doors are similar to buttons, but can spawn a fat trigger field around them
+to open without a touch, and they link together to form simultanious
+double/quad doors.
+
+Door.owner is the master door. If there is only one door, it points to itself.
+If multiple doors, all will point to a single one.
+
+Door.enemy chains from the master door through all doors linked in the chain.
+
+*/
+
+
+/*
+=============================================================================
+
+THINK FUNCTIONS
+
+=============================================================================
+*/
+
+void() door_go_down;
+void() door_go_up;
+void() door_rotating_go_down;
+void() door_rotating_go_up;
+
+void door_blocked()
+{
+ if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO))
+ { // KIll Kill Kill!!
+#ifdef SVQC
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+ }
+ else
+ {
+#ifdef SVQC
+ if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
+ Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+
+ //Dont chamge direction for dead or dying stuff
+ if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
+ {
+ if (self.wait >= 0)
+ {
+ if (self.state == STATE_DOWN)
+ if (self.classname == "door")
+ {
+ door_go_up ();
+ } else
+ {
+ door_rotating_go_up ();
+ }
+ else
+ if (self.classname == "door")
+ {
+ door_go_down ();
+ } else
+ {
+ door_rotating_go_down ();
+ }
+ }
+ }
+#ifdef SVQC
+ else
+ {
+ //gib dying stuff just to make sure
+ if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+#endif
+ }
+}
+
+void door_hit_top()
+{
+ if (self.noise1 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.state = STATE_TOP;
+ if (self.spawnflags & DOOR_TOGGLE)
+ return; // don't come down automatically
+ if (self.classname == "door")
+ {
+ self.think = door_go_down;
+ } else
+ {
+ self.think = door_rotating_go_down;
+ }
+ self.nextthink = self.ltime + self.wait;
+}
+
+void door_hit_bottom()
+{
+ if (self.noise1 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.state = STATE_BOTTOM;
+}
+
+void door_go_down()
+{
+ if (self.noise2 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ if (self.max_health)
+ {
+ self.takedamage = DAMAGE_YES;
+ self.health = self.max_health;
+ }
+ print(
+#ifdef SVQC
+ "Server ",
+#elif defined(CSQC)
+ "Client ",
+#endif
+ "going down at time ", ftos(time), "\n");
+
+ self.state = STATE_DOWN;
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
+}
+
+void door_go_up()
+{
+ if (self.state == STATE_UP)
+ return; // already going up
+
+ if (self.state == STATE_TOP)
+ { // reset top wait time
+ self.nextthink = self.ltime + self.wait;
+ return;
+ }
+
+ if (self.noise2 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ self.state = STATE_UP;
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
+
+ string oldmessage;
+ oldmessage = self.message;
+ self.message = "";
+ SUB_UseTargets();
+ self.message = oldmessage;
+}
+
+
+/*
+=============================================================================
+
+ACTIVATION FUNCTIONS
+
+=============================================================================
+*/
+
+float door_check_keys(void)
+{
+ local entity door;
+
+
+ if (self.owner)
+ door = self.owner;
+ else
+ door = self;
+
+ // no key needed
+ if (!door.itemkeys)
+ return true;
+
+ // this door require a key
+ // only a player can have a key
+ if (!IS_PLAYER(other))
+ return false;
+
+#ifdef SVQC
+ if (item_keys_usekey(door, other))
+ {
+ // some keys were used
+ if (other.key_door_messagetime <= time)
+ {
+
+ play2(other, "misc/talk.wav");
+ Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
+ other.key_door_messagetime = time + 2;
+ }
+ }
+ else
+ {
+ // no keys were used
+ if (other.key_door_messagetime <= time)
+ {
+ play2(other, "misc/talk.wav");
+ Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
+
+ other.key_door_messagetime = time + 2;
+ }
+ }
+#endif
+
+ if (door.itemkeys)
+ {
+#ifdef SVQC
+ // door is now unlocked
+ play2(other, "misc/talk.wav");
+ Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
+#endif
+ return true;
+ }
+ else
+ return false;
+}
+
+void door_fire()
+{
+ entity oself;
+ entity starte;
+
+ if (self.owner != self)
+ objerror ("door_fire: self.owner != self");
+
+ oself = self;
+
+ if (self.spawnflags & DOOR_TOGGLE)
+ {
+ if (self.state == STATE_UP || self.state == STATE_TOP)
+ {
+ starte = self;
+ do
+ {
+ if (self.classname == "door")
+ {
+ door_go_down ();
+ }
+ else
+ {
+ door_rotating_go_down ();
+ }
+ self = self.enemy;
+ } while ( (self != starte) && (self != world) );
+ self = oself;
+ return;
+ }
+ }
+
+// trigger all paired doors
+ starte = self;
+ do
+ {
+ if (self.classname == "door")
+ {
+ door_go_up ();
+ } else
+ {
+ // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
+ if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
+ {
+ self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
+ self.pos2 = '0 0 0' - self.pos2;
+ }
+ // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
+ if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
+ && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
+ {
+ door_rotating_go_up ();
+ }
+ }
+ self = self.enemy;
+ } while ( (self != starte) && (self != world) );
+ self = oself;
+}
+
+void door_use()
+{
+ entity oself;
+
+ //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
+
+ if (self.owner)
+ {
+ oself = self;
+ self = self.owner;
+ door_fire ();
+ self = oself;
+ }
+}
+
+void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ entity oself;
+ if(self.spawnflags & DOOR_NOSPLASH)
+ if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+ return;
+ self.health = self.health - damage;
+
+ if (self.itemkeys)
+ {
+ // don't allow opening doors through damage if keys are required
+ return;
+ }
+
+ if (self.health <= 0)
+ {
+ oself = self;
+ self = self.owner;
+ self.health = self.max_health;
+ self.takedamage = DAMAGE_NO; // wil be reset upon return
+ door_use ();
+ self = oself;
+ }
+}
+
+
+/*
+================
+door_touch
+
+Prints messages
+================
+*/
+
+void door_touch()
+{
+ if (!IS_PLAYER(other))
+ return;
+ if (self.owner.attack_finished_single > time)
+ return;
+
+ self.owner.attack_finished_single = time + 2;
+
+#ifdef SVQC
+ if (!(self.owner.dmg) && (self.owner.message != ""))
+ {
+ if (IS_CLIENT(other))
+ centerprint(other, self.owner.message);
+ play2(other, "misc/talk.wav");
+ }
+#endif
+}
+
+void door_generic_plat_blocked()
+{
+
+ if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
+#ifdef SVQC
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+ }
+ else
+ {
+
+#ifdef SVQC
+ if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
+ Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+
+ //Dont chamge direction for dead or dying stuff
+ if(PHYS_DEAD(other) && (other.takedamage == DAMAGE_NO))
+ {
+ if (self.wait >= 0)
+ {
+ if (self.state == STATE_DOWN)
+ door_rotating_go_up ();
+ else
+ door_rotating_go_down ();
+ }
+ }
+#ifdef SVQC
+ else
+ {
+ //gib dying stuff just to make sure
+ if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+#endif
+ }
+}
+
+void door_rotating_hit_top()
+{
+ if (self.noise1 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.state = STATE_TOP;
+ if (self.spawnflags & DOOR_TOGGLE)
+ return; // don't come down automatically
+ self.think = door_rotating_go_down;
+ self.nextthink = self.ltime + self.wait;
+}
+
+void door_rotating_hit_bottom()
+{
+ if (self.noise1 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
+ {
+ self.pos2 = '0 0 0' - self.pos2;
+ self.lip = 0;
+ }
+ self.state = STATE_BOTTOM;
+}
+
+void door_rotating_go_down()
+{
+ if (self.noise2 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ if (self.max_health)
+ {
+ self.takedamage = DAMAGE_YES;
+ self.health = self.max_health;
+ }
+
+ self.state = STATE_DOWN;
+ SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
+}
+
+void door_rotating_go_up()
+{
+ if (self.state == STATE_UP)
+ return; // already going up
+
+ if (self.state == STATE_TOP)
+ { // reset top wait time
+ self.nextthink = self.ltime + self.wait;
+ return;
+ }
+ if (self.noise2 != "")
+ sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ self.state = STATE_UP;
+ SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
+
+ string oldmessage;
+ oldmessage = self.message;
+ self.message = "";
+ SUB_UseTargets();
+ self.message = oldmessage;
+}
+
+
+/*
+=========================================
+door trigger
+
+Spawned if a door lacks a real activator
+=========================================
+*/
+
+void door_trigger_touch()
+{
+ if (other.health < 1)
+#ifdef SVQC
+ if (!(other.iscreature && !PHYS_DEAD(other)))
+#elif defined(CSQC)
+ if(!(IS_CLIENT(other) && !PHYS_DEAD(other)))
+ return;
+#endif
+
+ if (time < self.attack_finished_single)
+ return;
+
+ // check if door is locked
+ if (!door_check_keys())
+ return;
+
+ self.attack_finished_single = time + 1;
+
+ activator = other;
+
+ self = self.owner;
+ door_use ();
+}
+
+#ifdef SVQC
+
+float door_trigger_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR_TRIGGER);
+
+ WriteShort(MSG_ENTITY, num_for_edict(self.owner));
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ return true;
+}
+
+void door_trigger_link(entity trig)
+{
+ Net_LinkEntity(trig, false, 0, door_trigger_send);
+}
+
+void spawn_field(vector fmins, vector fmaxs)
+{
+ entity trigger;
+ vector t1 = fmins, t2 = fmaxs;
+
+ trigger = spawn();
+ trigger.classname = "doortriggerfield";
+ trigger.movetype = MOVETYPE_NONE;
+ trigger.solid = SOLID_TRIGGER;
+ trigger.owner = self;
+ trigger.touch = door_trigger_touch;
+
+ setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
+ door_trigger_link(trigger);
+}
+
+#elif defined(CSQC)
+
+void ent_door_trigger()
+{
+ float entnum = ReadShort();
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.owner = findfloat(world, sv_entnum, entnum); // if owner doesn't exist, it shouldn't matter much
+ self.classname = "doortriggerfield";
+ self.movetype = MOVETYPE_NONE;
+ self.solid = SOLID_TRIGGER;
+ self.trigger_touch = door_trigger_touch;
+ self.draw = trigger_draw_generic;
+ self.drawmask = MASK_NORMAL;
+ self.move_time = time;
+}
+
+#endif
+#ifdef SVQC
+/*
+=============
+LinkDoors
+
+
+=============
+*/
+
+entity LinkDoors_nextent(entity cur, entity near, entity pass)
+{
+ while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
+ {
+ }
+ return cur;
+}
+
+float LinkDoors_isconnected(entity e1, entity e2, entity pass)
+{
+ float DELTA = 4;
+ if((e1.absmin_x > e2.absmax_x + DELTA)
+ || (e1.absmin_y > e2.absmax_y + DELTA)
+ || (e1.absmin_z > e2.absmax_z + DELTA)
+ || (e2.absmin_x > e1.absmax_x + DELTA)
+ || (e2.absmin_y > e1.absmax_y + DELTA)
+ || (e2.absmin_z > e1.absmax_z + DELTA)
+ ) { return false; }
+ return true;
+}
+
+void door_link();
+void LinkDoors()
+{
+ entity t;
+ vector cmins, cmaxs;
+
+ door_link();
+
+ if (self.enemy)
+ return; // already linked by another door
+ if (self.spawnflags & 4)
+ {
+ self.owner = self.enemy = self;
+
+ if (self.health)
+ return;
+ IFTARGETED
+ return;
+ if (self.items)
+ return;
+ spawn_field(self.absmin, self.absmax);
+
+ return; // don't want to link this door
+ }
+
+ FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
+
+ // set owner, and make a loop of the chain
+ dprint("LinkDoors: linking doors:");
+ for(t = self; ; t = t.enemy)
+ {
+ dprint(" ", etos(t));
+ t.owner = self;
+ if(t.enemy == world)
+ {
+ t.enemy = self;
+ break;
+ }
+ }
+ dprint("\n");
+
+ // collect health, targetname, message, size
+ cmins = self.absmin;
+ cmaxs = self.absmax;
+ for(t = self; ; t = t.enemy)
+ {
+ if(t.health && !self.health)
+ self.health = t.health;
+ if((t.targetname != "") && (self.targetname == ""))
+ self.targetname = t.targetname;
+ if((t.message != "") && (self.message == ""))
+ self.message = t.message;
+ if (t.absmin_x < cmins_x)
+ cmins_x = t.absmin_x;
+ if (t.absmin_y < cmins_y)
+ cmins_y = t.absmin_y;
+ if (t.absmin_z < cmins_z)
+ cmins_z = t.absmin_z;
+ if (t.absmax_x > cmaxs_x)
+ cmaxs_x = t.absmax_x;
+ if (t.absmax_y > cmaxs_y)
+ cmaxs_y = t.absmax_y;
+ if (t.absmax_z > cmaxs_z)
+ cmaxs_z = t.absmax_z;
+ if(t.enemy == self)
+ break;
+ }
+
+ // distribute health, targetname, message
+ for(t = self; t; t = t.enemy)
+ {
+ t.health = self.health;
+ t.targetname = self.targetname;
+ t.message = self.message;
+ if(t.enemy == self)
+ break;
+ }
+
+ // shootable, or triggered doors just needed the owner/enemy links,
+ // they don't spawn a field
+
+ if (self.health)
+ return;
+ IFTARGETED
+ return;
+ if (self.items)
+ return;
+
+ spawn_field(cmins, cmaxs);
+}
+
+
+/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
+if two doors touch, they are assumed to be connected and operate as a unit.
+
+TOGGLE causes the door to wait in both the start and end states for a trigger event.
+
+START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
+
+GOLD_KEY causes the door to open only if the activator holds a gold key.
+
+SILVER_KEY causes the door to open only if the activator holds a silver key.
+
+"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle" determines the opening direction
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health" if set, door must be shot open
+"speed" movement speed (100 default)
+"wait" wait before returning (3 default, -1 = never return)
+"lip" lip remaining at end of move (8 default)
+"dmg" damage to inflict when blocked (2 default)
+"sounds"
+0) no sound
+1) stone
+2) base
+3) stone chain
+4) screechy metal
+FIXME: only one sound set available at the time being
+
+*/
+
+float door_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_DOOR);
+ WriteByte(MSG_ENTITY, sf);
+
+ if(sf & SF_TRIGGER_INIT)
+ {
+ WriteString(MSG_ENTITY, self.classname);
+ WriteByte(MSG_ENTITY, self.spawnflags);
+ WriteShort(MSG_ENTITY, ((self.owner == self || !self.owner) ? -1 : num_for_edict(self.owner)));
+ WriteShort(MSG_ENTITY, ((self.enemy == self || !self.enemy) ? -1 : num_for_edict(self.enemy)));
+ WriteShort(MSG_ENTITY, num_for_edict(self));
+
+ WriteByte(MSG_ENTITY, self.scale);
+
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteString(MSG_ENTITY, self.model);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.movedir_x);
+ WriteCoord(MSG_ENTITY, self.movedir_y);
+ WriteCoord(MSG_ENTITY, self.movedir_z);
+
+ WriteAngle(MSG_ENTITY, self.angles_x);
+ WriteAngle(MSG_ENTITY, self.angles_y);
+ WriteAngle(MSG_ENTITY, self.angles_z);
+
+ WriteCoord(MSG_ENTITY, self.pos1_x);
+ WriteCoord(MSG_ENTITY, self.pos1_y);
+ WriteCoord(MSG_ENTITY, self.pos1_z);
+ WriteCoord(MSG_ENTITY, self.pos2_x);
+ WriteCoord(MSG_ENTITY, self.pos2_y);
+ WriteCoord(MSG_ENTITY, self.pos2_z);
+
+ WriteCoord(MSG_ENTITY, self.size_x);
+ WriteCoord(MSG_ENTITY, self.size_y);
+ WriteCoord(MSG_ENTITY, self.size_z);
+
+ WriteShort(MSG_ENTITY, self.wait);
+ WriteShort(MSG_ENTITY, self.speed);
+ WriteByte(MSG_ENTITY, self.lip);
+ WriteByte(MSG_ENTITY, self.state);
+ WriteShort(MSG_ENTITY, self.ltime);
+ }
+
+ if(sf & SF_TRIGGER_RESET)
+ {
+ // client makes use of this, we do not
+ }
+
+ if(sf & SF_TRIGGER_UPDATE)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.pos1_x);
+ WriteCoord(MSG_ENTITY, self.pos1_y);
+ WriteCoord(MSG_ENTITY, self.pos1_z);
+ WriteCoord(MSG_ENTITY, self.pos2_x);
+ WriteCoord(MSG_ENTITY, self.pos2_y);
+ WriteCoord(MSG_ENTITY, self.pos2_z);
+ }
+
+ return true;
+}
+
+void door_link()
+{
+ // set size now, as everything is loaded
+ FixSize(self);
+ Net_LinkEntity(self, false, 0, door_send);
+}
+
+void door_init_startopen()
+{
+ setorigin (self, self.pos2);
+ self.pos2 = self.pos1;
+ self.pos1 = self.origin;
+
+ self.SendFlags |= SF_TRIGGER_UPDATE;
+}
+
+#endif
+
+void door_reset()
+{
+ setorigin(self, self.pos1);
+ self.velocity = '0 0 0';
+ self.state = STATE_BOTTOM;
+ self.think = func_null;
+ self.nextthink = 0;
+
+#ifdef SVQC
+ self.SendFlags |= SF_TRIGGER_RESET;
+#endif
+}
+
+#ifdef SVQC
+
+// spawnflags require key (for now only func_door)
+void spawnfunc_func_door()
+{
+ // Quake 1 keys compatibility
+ if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
+ self.itemkeys |= ITEM_KEY_BIT(0);
+ if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
+ self.itemkeys |= ITEM_KEY_BIT(1);
+
+ SetMovedir ();
+
+ self.max_health = self.health;
+ if (!InitMovingBrushTrigger())
+ return;
+ self.effects |= EF_LOWPRECISION;
+ self.classname = "door";
+
+ self.blocked = door_blocked;
+ self.use = door_use;
+
+ if(self.dmg && (self.message == ""))
+ self.message = "was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+
+ if (self.sounds > 0)
+ {
+ precache_sound ("plats/medplat1.wav");
+ precache_sound ("plats/medplat2.wav");
+ self.noise2 = "plats/medplat1.wav";
+ self.noise1 = "plats/medplat2.wav";
+ }
+
+ if (!self.speed)
+ self.speed = 100;
+ if (!self.wait)
+ self.wait = 3;
+ if (!self.lip)
+ self.lip = 8;
+
+ self.pos1 = self.origin;
+ self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
+
+// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+// but spawn in the open position
+ if (self.spawnflags & DOOR_START_OPEN)
+ InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
+
+ self.state = STATE_BOTTOM;
+
+ if (self.health)
+ {
+ self.takedamage = DAMAGE_YES;
+ self.event_damage = door_damage;
+ }
+
+ if (self.items)
+ self.wait = -1;
+
+ self.touch = door_touch;
+
+// LinkDoors can't be done until all of the doors have been spawned, so
+// the sizes can be detected properly.
+ InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
+
+ self.reset = door_reset;
+}
+
+#elif defined(CSQC)
+
+void ent_door()
+{
+ float sf = ReadByte();
+
+ if(sf & SF_TRIGGER_INIT)
+ {
+ self.classname = strzone(ReadString());
+ self.spawnflags = ReadByte();
+ float myowner = ReadShort();
+ float myenemy = ReadShort();
+ self.sv_entnum = ReadShort();
+
+ self.scale = ReadByte();
+
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.mdl = strzone(ReadString());
+ setmodel(self, self.mdl);
+
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.movedir_x = ReadCoord();
+ self.movedir_y = ReadCoord();
+ self.movedir_z = ReadCoord();
+
+ self.angles_x = ReadAngle();
+ self.angles_y = ReadAngle();
+ self.angles_z = ReadAngle();
+
+ self.pos1_x = ReadCoord();
+ self.pos1_y = ReadCoord();
+ self.pos1_z = ReadCoord();
+ self.pos2_x = ReadCoord();
+ self.pos2_y = ReadCoord();
+ self.pos2_z = ReadCoord();
+
+ self.size_x = ReadCoord();
+ self.size_y = ReadCoord();
+ self.size_z = ReadCoord();
+
+ self.wait = ReadShort();
+ self.speed = ReadShort();
+ self.lip = ReadByte();
+ self.state = ReadByte();
+ self.ltime = ReadShort();
+
+ self.movetype = MOVETYPE_PUSH;
+ self.solid = SOLID_BSP;
+ self.trigger_touch = door_touch;
+ self.draw = trigger_draw_generic;
+ self.drawmask = MASK_NORMAL;
+ self.move_time = time;
+ self.use = door_use;
+ self.blocked = door_blocked;
+
+ print(ftos(self.entnum), " ", ftos(self.sv_entnum), "\n");
+
+ self.owner = ((myowner == -1) ? self : findfloat(world, sv_entnum, myowner));
+ self.enemy = ((myenemy == -1) ? self : findfloat(world, sv_entnum, myenemy));
+ }
+
+ if(sf & SF_TRIGGER_RESET)
+ {
+ door_reset();
+ }
+
+ if(sf & SF_TRIGGER_UPDATE)
+ {
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.pos1_x = ReadCoord();
+ self.pos1_y = ReadCoord();
+ self.pos1_z = ReadCoord();
+ self.pos2_x = ReadCoord();
+ self.pos2_y = ReadCoord();
+ self.pos2_z = ReadCoord();
+ }
+}
+
+#endif
--- /dev/null
+// door constants
+const float DOOR_START_OPEN = 1;
+const float DOOR_DONT_LINK = 4;
+const float DOOR_TOGGLE = 32;
+
+const float DOOR_NOSPLASH = 256; // generic anti-splashdamage spawnflag
+
+const float SPAWNFLAGS_GOLD_KEY = 8;
+const float SPAWNFLAGS_SILVER_KEY = 16;
+
+#ifdef CSQC
+// stuff for preload
+void ent_door();
+void ent_door_trigger();
+
+// abused
+.float attack_finished_single;
+#endif
--- /dev/null
+#ifdef SVQC
+/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
+if two doors touch, they are assumed to be connected and operate as a unit.
+
+TOGGLE causes the door to wait in both the start and end states for a trigger event.
+
+BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
+The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
+must have set trigger_reverse to 1.
+BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
+
+START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
+
+"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
+"angle" determines the destination angle for opening. negative values reverse the direction.
+"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
+"health" if set, door must be shot open
+"speed" movement speed (100 default)
+"wait" wait before returning (3 default, -1 = never return)
+"dmg" damage to inflict when blocked (2 default)
+"sounds"
+0) no sound
+1) stone
+2) base
+3) stone chain
+4) screechy metal
+FIXME: only one sound set available at the time being
+*/
+
+void door_rotating_reset()
+{
+ self.angles = self.pos1;
+ self.avelocity = '0 0 0';
+ self.state = STATE_BOTTOM;
+ self.think = func_null;
+ self.nextthink = 0;
+}
+
+void door_rotating_init_startopen()
+{
+ self.angles = self.movedir;
+ self.pos2 = '0 0 0';
+ self.pos1 = self.movedir;
+}
+
+
+void spawnfunc_func_door_rotating()
+{
+
+ //if (!self.deathtype) // map makers can override this
+ // self.deathtype = " got in the way";
+
+ // I abuse "movedir" for denoting the axis for now
+ if (self.spawnflags & 64) // X (untested)
+ self.movedir = '0 0 1';
+ else if (self.spawnflags & 128) // Y (untested)
+ self.movedir = '1 0 0';
+ else // Z
+ self.movedir = '0 1 0';
+
+ if (self.angles_y==0) self.angles_y = 90;
+
+ self.movedir = self.movedir * self.angles_y;
+ self.angles = '0 0 0';
+
+ self.max_health = self.health;
+ self.avelocity = self.movedir;
+ if (!InitMovingBrushTrigger())
+ return;
+ self.velocity = '0 0 0';
+ //self.effects |= EF_LOWPRECISION;
+ self.classname = "door_rotating";
+
+ self.blocked = door_blocked;
+ self.use = door_use;
+
+ if(self.spawnflags & 8)
+ self.dmg = 10000;
+
+ if(self.dmg && (self.message == ""))
+ self.message = "was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+
+ if (self.sounds > 0)
+ {
+ precache_sound ("plats/medplat1.wav");
+ precache_sound ("plats/medplat2.wav");
+ self.noise2 = "plats/medplat1.wav";
+ self.noise1 = "plats/medplat2.wav";
+ }
+
+ if (!self.speed)
+ self.speed = 50;
+ if (!self.wait)
+ self.wait = 1;
+ self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
+
+ self.pos1 = '0 0 0';
+ self.pos2 = self.movedir;
+
+// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
+// but spawn in the open position
+ if (self.spawnflags & DOOR_START_OPEN)
+ InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
+
+ self.state = STATE_BOTTOM;
+
+ if (self.health)
+ {
+ self.takedamage = DAMAGE_YES;
+ self.event_damage = door_damage;
+ }
+
+ if (self.items)
+ self.wait = -1;
+
+ self.touch = door_touch;
+
+// LinkDoors can't be done until all of the doors have been spawned, so
+// the sizes can be detected properly.
+ InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
+
+ self.reset = door_rotating_reset;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void() fd_secret_move1;
+void() fd_secret_move2;
+void() fd_secret_move3;
+void() fd_secret_move4;
+void() fd_secret_move5;
+void() fd_secret_move6;
+void() fd_secret_done;
+
+const float SECRET_OPEN_ONCE = 1; // stays open
+const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
+const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
+const float SECRET_NO_SHOOT = 8; // only opened by trigger
+const float SECRET_YES_SHOOT = 16; // shootable even if targeted
+
+void fd_secret_use()
+{
+ float temp;
+ string message_save;
+
+ self.health = 10000;
+ self.bot_attack = true;
+
+ // exit if still moving around...
+ if (self.origin != self.oldorigin)
+ return;
+
+ message_save = self.message;
+ self.message = ""; // no more message
+ SUB_UseTargets(); // fire all targets / killtargets
+ self.message = message_save;
+
+ self.velocity = '0 0 0';
+
+ // Make a sound, wait a little...
+
+ if (self.noise1 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.nextthink = self.ltime + 0.1;
+
+ temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
+ makevectors(self.mangle);
+
+ if (!self.t_width)
+ {
+ if (self.spawnflags & SECRET_1ST_DOWN)
+ self.t_width = fabs(v_up * self.size);
+ else
+ self.t_width = fabs(v_right * self.size);
+ }
+
+ if (!self.t_length)
+ self.t_length = fabs(v_forward * self.size);
+
+ if (self.spawnflags & SECRET_1ST_DOWN)
+ self.dest1 = self.origin - v_up * self.t_width;
+ else
+ self.dest1 = self.origin + v_right * (self.t_width * temp);
+
+ self.dest2 = self.dest1 + v_forward * self.t_length;
+ SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
+ if (self.noise2 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+}
+
+void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ fd_secret_use();
+}
+
+// Wait after first movement...
+void fd_secret_move1()
+{
+ self.nextthink = self.ltime + 1.0;
+ self.think = fd_secret_move2;
+ if (self.noise3 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+// Start moving sideways w/sound...
+void fd_secret_move2()
+{
+ if (self.noise2 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
+}
+
+// Wait here until time to go back...
+void fd_secret_move3()
+{
+ if (self.noise3 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+ if (!(self.spawnflags & SECRET_OPEN_ONCE))
+ {
+ self.nextthink = self.ltime + self.wait;
+ self.think = fd_secret_move4;
+ }
+}
+
+// Move backward...
+void fd_secret_move4()
+{
+ if (self.noise2 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
+}
+
+// Wait 1 second...
+void fd_secret_move5()
+{
+ self.nextthink = self.ltime + 1.0;
+ self.think = fd_secret_move6;
+ if (self.noise3 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+void fd_secret_move6()
+{
+ if (self.noise2 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
+ SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
+}
+
+void fd_secret_done()
+{
+ if (self.spawnflags&SECRET_YES_SHOOT)
+ {
+ self.health = 10000;
+ self.takedamage = DAMAGE_YES;
+ //self.th_pain = fd_secret_use;
+ }
+ if (self.noise3 != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
+}
+
+void secret_blocked()
+{
+ if (time < self.attack_finished_single)
+ return;
+ self.attack_finished_single = time + 0.5;
+ //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
+}
+
+/*
+==============
+secret_touch
+
+Prints messages
+================
+*/
+void secret_touch()
+{
+ if (!other.iscreature)
+ return;
+ if (self.attack_finished_single > time)
+ return;
+
+ self.attack_finished_single = time + 2;
+
+ if (self.message)
+ {
+ if (IS_CLIENT(other))
+ centerprint(other, self.message);
+ play2(other, "misc/talk.wav");
+ }
+}
+
+void secret_reset()
+{
+ if (self.spawnflags&SECRET_YES_SHOOT)
+ {
+ self.health = 10000;
+ self.takedamage = DAMAGE_YES;
+ }
+ setorigin(self, self.oldorigin);
+ self.think = func_null;
+ self.nextthink = 0;
+}
+
+/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
+Basic secret door. Slides back, then to the side. Angle determines direction.
+wait = # of seconds before coming back
+1st_left = 1st move is left of arrow
+1st_down = 1st move is down from arrow
+always_shoot = even if targeted, keep shootable
+t_width = override WIDTH to move back (or height if going down)
+t_length = override LENGTH to move sideways
+"dmg" damage to inflict when blocked (2 default)
+
+If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
+"sounds"
+1) medieval
+2) metal
+3) base
+*/
+
+void spawnfunc_func_door_secret()
+{
+ /*if (!self.deathtype) // map makers can override this
+ self.deathtype = " got in the way";*/
+
+ if (!self.dmg)
+ self.dmg = 2;
+
+ // Magic formula...
+ self.mangle = self.angles;
+ self.angles = '0 0 0';
+ self.classname = "door";
+ if (!InitMovingBrushTrigger())
+ return;
+ self.effects |= EF_LOWPRECISION;
+
+ self.touch = secret_touch;
+ self.blocked = secret_blocked;
+ self.speed = 50;
+ self.use = fd_secret_use;
+ IFTARGETED
+ {
+ }
+ else
+ self.spawnflags |= SECRET_YES_SHOOT;
+
+ if(self.spawnflags&SECRET_YES_SHOOT)
+ {
+ self.health = 10000;
+ self.takedamage = DAMAGE_YES;
+ self.event_damage = fd_secret_damage;
+ }
+ self.oldorigin = self.origin;
+ if (!self.wait)
+ self.wait = 5; // 5 seconds before closing
+
+ self.reset = secret_reset;
+ secret_reset();
+}
+#endif
--- /dev/null
+#ifdef SVQC
+/*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
+Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
+netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
+speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
+height: amplitude modifier (default 32)
+phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
+noise: path/name of looping .wav file to play.
+dmg: Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime: See above.
+*/
+
+void func_fourier_controller_think()
+{
+ vector v;
+ float n, i, t;
+
+ self.nextthink = time + 0.1;
+ if(self.owner.active != ACTIVE_ACTIVE)
+ {
+ self.owner.velocity = '0 0 0';
+ return;
+ }
+
+
+ n = floor((tokenize_console(self.owner.netname)) / 5);
+ t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
+
+ v = self.owner.destvec;
+
+ for(i = 0; i < n; ++i)
+ {
+ makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
+ v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward_y;
+ }
+
+ if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
+ // * 10 so it will arrive in 0.1 sec
+ self.owner.velocity = (v - self.owner.origin) * 10;
+}
+
+void spawnfunc_func_fourier()
+{
+ entity controller;
+ if (self.noise != "")
+ {
+ precache_sound(self.noise);
+ soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+ }
+
+ if (!self.speed)
+ self.speed = 4;
+ if (!self.height)
+ self.height = 32;
+ self.destvec = self.origin;
+ self.cnt = 360 / self.speed;
+
+ self.blocked = generic_plat_blocked;
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+ self.dmgtime2 = time;
+
+ if(self.netname == "")
+ self.netname = "1 0 0 0 1";
+
+ if (!InitMovingBrushTrigger())
+ return;
+
+ self.active = ACTIVE_ACTIVE;
+
+ // wait for targets to spawn
+ controller = spawn();
+ controller.classname = "func_fourier_controller";
+ controller.owner = self;
+ controller.nextthink = time + 1;
+ controller.think = func_fourier_controller_think;
+ self.nextthink = self.ltime + 999999999;
+ self.think = SUB_NullThink; // for PushMove
+
+ // Savage: Reduce bandwith, critical on e.g. nexdm02
+ self.effects |= EF_LOWPRECISION;
+
+ // TODO make a reset function for this one
+}
+#endif
--- /dev/null
+#include "include.qh"
+
+#include "bobbing.qc"
+#include "breakable.qc"
+#include "button.qc"
+#include "conveyor.qc"
+#include "door.qc"
+#include "door_rotating.qc"
+#include "door_secret.qc"
+#include "fourier.qc"
+#include "ladder.qc"
+#include "pendulum.qc"
+#include "plat.qc"
+#include "pointparticles.qc"
+#include "rainsnow.qc"
+#include "rotating.qc"
+#include "stardust.qc"
+#include "train.qc"
+#include "vectormamamam.qc"
--- /dev/null
+#ifndef TRIGGERS_FUNC_INCLUDE_H
+#define TRIGGERS_FUNC_INCLUDE_H
+
+#include "conveyor.qh"
+#include "door.qh"
+#include "ladder.qh"
+#include "plat.qh"
+
+#endif
--- /dev/null
+void func_ladder_touch()
+{
+#ifdef SVQC
+ if (!other.iscreature)
+ return;
+ if (other.vehicle_flags & VHF_ISVEHICLE)
+ return;
+#endif
+#ifdef CSQC
+ if(other.classname != "csqcmodel")
+ return;
+#endif
+
+ EXACTTRIGGER_TOUCH;
+
+ other.ladder_time = time + 0.1;
+ other.ladder_entity = self;
+}
+
+#ifdef SVQC
+float func_ladder_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_LADDER);
+
+ WriteString(MSG_ENTITY, self.classname);
+ WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+ WriteByte(MSG_ENTITY, self.skin);
+ WriteByte(MSG_ENTITY, self.speed);
+ WriteByte(MSG_ENTITY, self.scale);
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.movedir_x);
+ WriteCoord(MSG_ENTITY, self.movedir_y);
+ WriteCoord(MSG_ENTITY, self.movedir_z);
+
+ WriteCoord(MSG_ENTITY, self.angles_x);
+ WriteCoord(MSG_ENTITY, self.angles_y);
+ WriteCoord(MSG_ENTITY, self.angles_z);
+
+ return true;
+}
+
+void func_ladder_link()
+{
+ Net_LinkEntity(self, false, 0, func_ladder_send);
+}
+
+void spawnfunc_func_ladder()
+{
+ EXACTTRIGGER_INIT;
+ self.touch = func_ladder_touch;
+
+ func_ladder_link();
+}
+
+void spawnfunc_func_water()
+{
+ EXACTTRIGGER_INIT;
+ self.touch = func_ladder_touch;
+
+ func_ladder_link();
+}
+
+#elif defined(CSQC)
+.float speed;
+
+void ent_func_ladder()
+{
+ self.classname = strzone(ReadString());
+ self.warpzone_isboxy = ReadByte();
+ self.skin = ReadByte();
+ self.speed = ReadByte();
+ self.scale = ReadByte();
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+ self.movedir_x = ReadCoord();
+ self.movedir_y = ReadCoord();
+ self.movedir_z = ReadCoord();
+ self.angles_x = ReadCoord();
+ self.angles_y = ReadCoord();
+ self.angles_z = ReadCoord();
+
+ self.solid = SOLID_TRIGGER;
+ self.draw = trigger_draw_generic;
+ self.trigger_touch = func_ladder_touch;
+ self.drawmask = MASK_NORMAL;
+ self.move_time = time;
+}
+#endif
--- /dev/null
+.float ladder_time;
+.entity ladder_entity;
+
+#ifdef CSQC
+void ent_func_ladder();
+#endif
--- /dev/null
+#ifdef SVQC
+.float freq;
+void func_pendulum_controller_think()
+{
+ float v;
+ self.nextthink = time + 0.1;
+
+ if (!(self.owner.active == ACTIVE_ACTIVE))
+ {
+ self.owner.avelocity_x = 0;
+ return;
+ }
+
+ // calculate sinewave using makevectors
+ makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
+ v = self.owner.speed * v_forward_y + self.cnt;
+ if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
+ {
+ // * 10 so it will arrive in 0.1 sec
+ self.owner.avelocity_z = (remainder(v - self.owner.angles_z, 360)) * 10;
+ }
+}
+
+void spawnfunc_func_pendulum()
+{
+ entity controller;
+ if (self.noise != "")
+ {
+ precache_sound(self.noise);
+ soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+ }
+
+ self.active = ACTIVE_ACTIVE;
+
+ // keys: angle, speed, phase, noise, freq
+
+ if(!self.speed)
+ self.speed = 30;
+ // not initializing self.dmg to 2, to allow damageless pendulum
+
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+ self.dmgtime2 = time;
+
+ self.blocked = generic_plat_blocked;
+
+ self.avelocity_z = 0.0000001;
+ if (!InitMovingBrushTrigger())
+ return;
+
+ if(!self.freq)
+ {
+ // find pendulum length (same formula as Q3A)
+ self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins_z))));
+ }
+
+ // copy initial angle
+ self.cnt = self.angles_z;
+
+ // wait for targets to spawn
+ controller = spawn();
+ controller.classname = "func_pendulum_controller";
+ controller.owner = self;
+ controller.nextthink = time + 1;
+ controller.think = func_pendulum_controller_think;
+ self.nextthink = self.ltime + 999999999;
+ self.think = SUB_NullThink; // for PushMove
+
+ //self.effects |= EF_LOWPRECISION;
+
+ // TODO make a reset function for this one
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void plat_delayedinit()
+{
+ plat_spawn_inside_trigger (); // the "start moving" trigger
+}
+
+float plat_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_PLAT);
+ WriteByte(MSG_ENTITY, sf);
+
+ if(sf & SF_TRIGGER_INIT)
+ {
+ WriteShort(MSG_ENTITY, num_for_edict(self));
+ WriteString(MSG_ENTITY, self.target);
+ WriteString(MSG_ENTITY, self.target2);
+ WriteString(MSG_ENTITY, self.target3);
+ WriteString(MSG_ENTITY, self.target4);
+ WriteString(MSG_ENTITY, self.targetname);
+
+ WriteByte(MSG_ENTITY, self.platmovetype_start);
+ WriteByte(MSG_ENTITY, self.platmovetype_turn);
+ WriteByte(MSG_ENTITY, self.platmovetype_end);
+
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteString(MSG_ENTITY, self.model);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.pos1_x);
+ WriteCoord(MSG_ENTITY, self.pos1_y);
+ WriteCoord(MSG_ENTITY, self.pos1_z);
+ WriteCoord(MSG_ENTITY, self.pos2_x);
+ WriteCoord(MSG_ENTITY, self.pos2_y);
+ WriteCoord(MSG_ENTITY, self.pos2_z);
+
+ WriteCoord(MSG_ENTITY, self.size_x);
+ WriteCoord(MSG_ENTITY, self.size_y);
+ WriteCoord(MSG_ENTITY, self.size_z);
+
+ WriteAngle(MSG_ENTITY, self.angles_x);
+ WriteAngle(MSG_ENTITY, self.angles_y);
+ WriteAngle(MSG_ENTITY, self.angles_z);
+
+ WriteAngle(MSG_ENTITY, self.mangle_x);
+ WriteAngle(MSG_ENTITY, self.mangle_y);
+ WriteAngle(MSG_ENTITY, self.mangle_z);
+
+ WriteShort(MSG_ENTITY, self.speed);
+ WriteShort(MSG_ENTITY, self.height);
+ WriteByte(MSG_ENTITY, self.lip);
+ WriteByte(MSG_ENTITY, self.state);
+
+ WriteShort(MSG_ENTITY, self.dmg);
+ }
+
+ if(sf & SF_TRIGGER_RESET)
+ {
+ // used on client
+ }
+
+ return true;
+}
+
+void plat_link()
+{
+ Net_LinkEntity(self, 0, false, plat_send);
+}
+
+void spawnfunc_func_plat()
+{
+ if (self.sounds == 0)
+ self.sounds = 2;
+
+ if(self.spawnflags & 4)
+ self.dmg = 10000;
+
+ if(self.dmg && (self.message == ""))
+ self.message = "was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+
+ if (self.sounds == 1)
+ {
+ precache_sound ("plats/plat1.wav");
+ precache_sound ("plats/plat2.wav");
+ self.noise = "plats/plat1.wav";
+ self.noise1 = "plats/plat2.wav";
+ }
+
+ if (self.sounds == 2)
+ {
+ precache_sound ("plats/medplat1.wav");
+ precache_sound ("plats/medplat2.wav");
+ self.noise = "plats/medplat1.wav";
+ self.noise1 = "plats/medplat2.wav";
+ }
+
+ if (self.sound1)
+ {
+ precache_sound (self.sound1);
+ self.noise = self.sound1;
+ }
+ if (self.sound2)
+ {
+ precache_sound (self.sound2);
+ self.noise1 = self.sound2;
+ }
+
+ self.mangle = self.angles;
+ self.angles = '0 0 0';
+
+ self.classname = "plat";
+ if (!InitMovingBrushTrigger())
+ return;
+ self.effects |= EF_LOWPRECISION;
+ setsize (self, self.mins , self.maxs);
+
+ self.blocked = plat_crush;
+
+ if (!self.speed)
+ self.speed = 150;
+ if (!self.lip)
+ self.lip = 16;
+ if (!self.height)
+ self.height = self.size_z - self.lip;
+
+ self.pos1 = self.origin;
+ self.pos2 = self.origin;
+ self.pos2_z = self.origin_z - self.height;
+
+ self.reset = plat_reset;
+ plat_reset();
+
+ plat_link();
+
+ InitializeEntity(self, plat_delayedinit, INITPRIO_FINDTARGET);
+}
+#elif defined(CSQC)
+void plat_draw()
+{
+
+}
+
+void ent_plat()
+{
+ float sf = ReadByte();
+
+ if(sf & SF_TRIGGER_INIT)
+ {
+ self.sv_entnum = ReadShort();
+ self.target = strzone(ReadString());
+ self.target2 = strzone(ReadString());
+ self.target3 = strzone(ReadString());
+ self.target4 = strzone(ReadString());
+ self.targetname = strzone(ReadString());
+
+ self.platmovetype_start = ReadByte();
+ self.platmovetype_turn = ReadByte();
+ self.platmovetype_end = ReadByte();
+
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.model = strzone(ReadString());
+ setmodel(self, self.model);
+
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.pos1_x = ReadCoord();
+ self.pos1_y = ReadCoord();
+ self.pos1_z = ReadCoord();
+ self.pos2_x = ReadCoord();
+ self.pos2_y = ReadCoord();
+ self.pos2_z = ReadCoord();
+
+ self.size_x = ReadCoord();
+ self.size_y = ReadCoord();
+ self.size_z = ReadCoord();
+
+ self.angles_x = ReadAngle();
+ self.angles_y = ReadAngle();
+ self.angles_z = ReadAngle();
+
+ self.mangle_x = ReadAngle();
+ self.mangle_y = ReadAngle();
+ self.mangle_z = ReadAngle();
+
+ self.speed = ReadShort();
+ self.height = ReadShort();
+ self.lip = ReadByte();
+ self.state = ReadByte();
+
+ self.dmg = ReadShort();
+
+ self.solid = SOLID_BSP;
+ self.movetype = MOVETYPE_PUSH;
+ self.drawmask = MASK_NORMAL;
+ self.draw = plat_draw;
+ self.use = plat_use;
+
+ plat_reset(); // also called here
+ }
+
+ if(sf & SF_TRIGGER_RESET)
+ {
+ plat_reset();
+ }
+}
+#endif
--- /dev/null
+#ifdef CSQC
+void ent_plat();
+#endif
--- /dev/null
+#ifdef SVQC
+// NOTE: also contains func_sparks
+
+float pointparticles_SendEntity(entity to, float fl)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
+
+ // optional features to save space
+ fl = fl & 0x0F;
+ if(self.spawnflags & 2)
+ fl |= 0x10; // absolute count on toggle-on
+ if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
+ fl |= 0x20; // 4 bytes - saves CPU
+ if(self.waterlevel || self.count != 1)
+ fl |= 0x40; // 4 bytes - obscure features almost never used
+ if(self.mins != '0 0 0' || self.maxs != '0 0 0')
+ fl |= 0x80; // 14 bytes - saves lots of space
+
+ WriteByte(MSG_ENTITY, fl);
+ if(fl & 2)
+ {
+ if(self.state)
+ WriteCoord(MSG_ENTITY, self.impulse);
+ else
+ WriteCoord(MSG_ENTITY, 0); // off
+ }
+ if(fl & 4)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+ }
+ if(fl & 1)
+ {
+ if(self.model != "null")
+ {
+ WriteShort(MSG_ENTITY, self.modelindex);
+ if(fl & 0x80)
+ {
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+ }
+ }
+ else
+ {
+ WriteShort(MSG_ENTITY, 0);
+ if(fl & 0x80)
+ {
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+ }
+ }
+ WriteShort(MSG_ENTITY, self.cnt);
+ if(fl & 0x20)
+ {
+ WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
+ WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
+ }
+ if(fl & 0x40)
+ {
+ WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
+ WriteByte(MSG_ENTITY, self.count * 16.0);
+ }
+ WriteString(MSG_ENTITY, self.noise);
+ if(self.noise != "")
+ {
+ WriteByte(MSG_ENTITY, floor(self.atten * 64));
+ WriteByte(MSG_ENTITY, floor(self.volume * 255));
+ }
+ WriteString(MSG_ENTITY, self.bgmscript);
+ if(self.bgmscript != "")
+ {
+ WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
+ WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
+ WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
+ WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
+ }
+ }
+ return 1;
+}
+
+void pointparticles_use()
+{
+ self.state = !self.state;
+ self.SendFlags |= 2;
+}
+
+void pointparticles_think()
+{
+ if(self.origin != self.oldorigin)
+ {
+ self.SendFlags |= 4;
+ self.oldorigin = self.origin;
+ }
+ self.nextthink = time;
+}
+
+void pointparticles_reset()
+{
+ if(self.spawnflags & 1)
+ self.state = 1;
+ else
+ self.state = 0;
+}
+
+void spawnfunc_func_pointparticles()
+{
+ if(self.model != "")
+ setmodel(self, self.model);
+ if(self.noise != "")
+ precache_sound (self.noise);
+
+ if(!self.bgmscriptsustain)
+ self.bgmscriptsustain = 1;
+ else if(self.bgmscriptsustain < 0)
+ self.bgmscriptsustain = 0;
+
+ if(!self.atten)
+ self.atten = ATTEN_NORM;
+ else if(self.atten < 0)
+ self.atten = 0;
+ if(!self.volume)
+ self.volume = 1;
+ if(!self.count)
+ self.count = 1;
+ if(!self.impulse)
+ self.impulse = 1;
+
+ if(!self.modelindex)
+ {
+ setorigin(self, self.origin + self.mins);
+ setsize(self, '0 0 0', self.maxs - self.mins);
+ }
+ if(!self.cnt)
+ self.cnt = particleeffectnum(self.mdl);
+
+ Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
+
+ IFTARGETED
+ {
+ self.use = pointparticles_use;
+ self.reset = pointparticles_reset;
+ self.reset();
+ }
+ else
+ self.state = 1;
+ self.think = pointparticles_think;
+ self.nextthink = time;
+}
+
+void spawnfunc_func_sparks()
+{
+ // self.cnt is the amount of sparks that one burst will spawn
+ if(self.cnt < 1) {
+ self.cnt = 25.0; // nice default value
+ }
+
+ // self.wait is the probability that a sparkthink will spawn a spark shower
+ // range: 0 - 1, but 0 makes little sense, so...
+ if(self.wait < 0.05) {
+ self.wait = 0.25; // nice default value
+ }
+
+ self.count = self.cnt;
+ self.mins = '0 0 0';
+ self.maxs = '0 0 0';
+ self.velocity = '0 0 -1';
+ self.mdl = "TE_SPARK";
+ self.impulse = 10 * self.wait; // by default 2.5/sec
+ self.wait = 0;
+ self.cnt = 0; // use mdl
+
+ spawnfunc_func_pointparticles();
+}
+#endif
--- /dev/null
+#ifdef SVQC
+float rainsnow_SendEntity(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
+ WriteByte(MSG_ENTITY, self.state);
+ WriteCoord(MSG_ENTITY, self.origin_x + self.mins_x);
+ WriteCoord(MSG_ENTITY, self.origin_y + self.mins_y);
+ WriteCoord(MSG_ENTITY, self.origin_z + self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x - self.mins_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y - self.mins_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z - self.mins_z);
+ WriteShort(MSG_ENTITY, compressShortVector(self.dest));
+ WriteShort(MSG_ENTITY, self.count);
+ WriteByte(MSG_ENTITY, self.cnt);
+ return 1;
+}
+
+/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
+This is an invisible area like a trigger, which rain falls inside of.
+
+Keys:
+"velocity"
+ falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
+"cnt"
+ sets color of rain (default 12 - white)
+"count"
+ adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
+*/
+void spawnfunc_func_rain()
+{
+ self.dest = self.velocity;
+ self.velocity = '0 0 0';
+ if (!self.dest)
+ self.dest = '0 0 -700';
+ self.angles = '0 0 0';
+ self.movetype = MOVETYPE_NONE;
+ self.solid = SOLID_NOT;
+ SetBrushEntityModel();
+ if (!self.cnt)
+ self.cnt = 12;
+ if (!self.count)
+ self.count = 2000;
+ self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
+ if (self.count < 1)
+ self.count = 1;
+ if(self.count > 65535)
+ self.count = 65535;
+
+ self.state = 1; // 1 is rain, 0 is snow
+ self.Version = 1;
+
+ Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
+}
+
+
+/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
+This is an invisible area like a trigger, which snow falls inside of.
+
+Keys:
+"velocity"
+ falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
+"cnt"
+ sets color of rain (default 12 - white)
+"count"
+ adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
+*/
+void spawnfunc_func_snow()
+{
+ self.dest = self.velocity;
+ self.velocity = '0 0 0';
+ if (!self.dest)
+ self.dest = '0 0 -300';
+ self.angles = '0 0 0';
+ self.movetype = MOVETYPE_NONE;
+ self.solid = SOLID_NOT;
+ SetBrushEntityModel();
+ if (!self.cnt)
+ self.cnt = 12;
+ if (!self.count)
+ self.count = 2000;
+ self.count = 0.01 * self.count * (self.size_x / 1024) * (self.size_y / 1024);
+ if (self.count < 1)
+ self.count = 1;
+ if(self.count > 65535)
+ self.count = 65535;
+
+ self.state = 0; // 1 is rain, 0 is snow
+ self.Version = 1;
+
+ Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void func_rotating_setactive(float astate)
+{
+
+ if (astate == ACTIVE_TOGGLE)
+ {
+ if(self.active == ACTIVE_ACTIVE)
+ self.active = ACTIVE_NOT;
+ else
+ self.active = ACTIVE_ACTIVE;
+ }
+ else
+ self.active = astate;
+
+ if(self.active == ACTIVE_NOT)
+ self.avelocity = '0 0 0';
+ else
+ self.avelocity = self.pos1;
+}
+
+/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
+Brush model that spins in place on one axis (default Z).
+speed : speed to rotate (in degrees per second)
+noise : path/name of looping .wav file to play.
+dmg : Do this mutch dmg every .dmgtime intervall when blocked
+dmgtime : See above.
+*/
+
+void spawnfunc_func_rotating()
+{
+ if (self.noise != "")
+ {
+ precache_sound(self.noise);
+ ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
+ }
+
+ self.active = ACTIVE_ACTIVE;
+ self.setactive = func_rotating_setactive;
+
+ if (!self.speed)
+ self.speed = 100;
+ // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+ if (self.spawnflags & 4) // X (untested)
+ self.avelocity = '0 0 1' * self.speed;
+ // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+ else if (self.spawnflags & 8) // Y (untested)
+ self.avelocity = '1 0 0' * self.speed;
+ // FIXME: test if this turns the right way, then remove this comment (negate as needed)
+ else // Z
+ self.avelocity = '0 1 0' * self.speed;
+
+ self.pos1 = self.avelocity;
+
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+
+
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+
+ self.dmgtime2 = time;
+
+ if (!InitMovingBrushTrigger())
+ return;
+ // no EF_LOWPRECISION here, as rounding angles is bad
+
+ self.blocked = generic_plat_blocked;
+
+ // wait for targets to spawn
+ self.nextthink = self.ltime + 999999999;
+ self.think = SUB_NullThink; // for PushMove
+
+ // TODO make a reset function for this one
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void spawnfunc_func_stardust()
+{
+ self.effects = EF_STARDUST;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+.float train_wait_turning;
+void() train_next;
+void train_wait()
+{
+ entity oldself;
+ oldself = self;
+ self = self.enemy;
+ SUB_UseTargets();
+ self = oldself;
+ self.enemy = world;
+
+ // if turning is enabled, the train will turn toward the next point while waiting
+ if(self.platmovetype_turn && !self.train_wait_turning)
+ {
+ entity targ, cp;
+ vector ang;
+ targ = find(world, targetname, self.target);
+ if((self.spawnflags & 1) && targ.curvetarget)
+ cp = find(world, targetname, targ.curvetarget);
+ else
+ cp = world;
+
+ if(cp) // bezier curves movement
+ ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
+ else // linear movement
+ ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
+ ang = vectoangles(ang);
+ ang_x = -ang_x; // flip up / down orientation
+
+ if(self.wait > 0) // slow turning
+ SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
+ else // instant turning
+ SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
+ self.train_wait_turning = true;
+ return;
+ }
+
+ if(self.noise != "")
+ stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
+
+ if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
+ {
+ self.train_wait_turning = false;
+ train_next();
+ }
+ else
+ {
+ self.think = train_next;
+ self.nextthink = self.ltime + self.wait;
+ }
+}
+
+void train_next()
+{
+ entity targ, cp = world;
+ vector cp_org = '0 0 0';
+
+ targ = find(world, targetname, self.target);
+ self.target = targ.target;
+ if (self.spawnflags & 1)
+ {
+ if(targ.curvetarget)
+ {
+ cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
+ cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
+ }
+ }
+ if (self.target == "")
+ objerror("train_next: no next target");
+ self.wait = targ.wait;
+ if (!self.wait)
+ self.wait = 0.1;
+
+ if(targ.platmovetype)
+ {
+ // this path_corner contains a movetype overrider, apply it
+ self.platmovetype_start = targ.platmovetype_start;
+ self.platmovetype_end = targ.platmovetype_end;
+ }
+ else
+ {
+ // this path_corner doesn't contain a movetype overrider, use the train's defaults
+ self.platmovetype_start = self.platmovetype_start_default;
+ self.platmovetype_end = self.platmovetype_end_default;
+ }
+
+ if (targ.speed)
+ {
+ if (cp)
+ SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+ else
+ SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
+ }
+ else
+ {
+ if (cp)
+ SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+ else
+ SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
+ }
+
+ if(self.noise != "")
+ sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+}
+
+void func_train_find()
+{
+ entity targ;
+ targ = find(world, targetname, self.target);
+ self.target = targ.target;
+ if (self.target == "")
+ objerror("func_train_find: no next target");
+ setorigin(self, targ.origin - self.view_ofs);
+ self.nextthink = self.ltime + 1;
+ self.think = train_next;
+}
+
+/*QUAKED spawnfunc_func_train (0 .5 .8) ?
+Ridable platform, targets spawnfunc_path_corner path to follow.
+speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
+target : targetname of first spawnfunc_path_corner (starts here)
+*/
+void spawnfunc_func_train()
+{
+ if (self.noise != "")
+ precache_sound(self.noise);
+
+ if (self.target == "")
+ objerror("func_train without a target");
+ if (!self.speed)
+ self.speed = 100;
+
+ if (!InitMovingBrushTrigger())
+ return;
+ self.effects |= EF_LOWPRECISION;
+
+ if (self.spawnflags & 2)
+ {
+ self.platmovetype_turn = true;
+ self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
+ }
+ else
+ self.view_ofs = self.mins;
+
+ // wait for targets to spawn
+ InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
+
+ self.blocked = generic_plat_blocked;
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message2 == ""))
+ self.message2 = "was squished by";
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+ self.dmgtime2 = time;
+
+ if(!set_platmovetype(self, self.platmovetype))
+ return;
+ self.platmovetype_start_default = self.platmovetype_start;
+ self.platmovetype_end_default = self.platmovetype_end;
+
+ // TODO make a reset function for this one
+}
+#endif
--- /dev/null
+#ifdef SVQC
+// reusing some fields havocbots declared
+.entity wp00, wp01, wp02, wp03;
+
+.float targetfactor, target2factor, target3factor, target4factor;
+.vector targetnormal, target2normal, target3normal, target4normal;
+
+vector func_vectormamamam_origin(entity o, float t)
+{
+ vector v, p;
+ float f;
+ entity e;
+
+ f = o.spawnflags;
+ v = '0 0 0';
+
+ e = o.wp00;
+ if(e)
+ {
+ p = e.origin + t * e.velocity;
+ if(f & 1)
+ v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
+ else
+ v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
+ }
+
+ e = o.wp01;
+ if(e)
+ {
+ p = e.origin + t * e.velocity;
+ if(f & 2)
+ v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
+ else
+ v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
+ }
+
+ e = o.wp02;
+ if(e)
+ {
+ p = e.origin + t * e.velocity;
+ if(f & 4)
+ v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
+ else
+ v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
+ }
+
+ e = o.wp03;
+ if(e)
+ {
+ p = e.origin + t * e.velocity;
+ if(f & 8)
+ v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
+ else
+ v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
+ }
+
+ return v;
+}
+
+void func_vectormamamam_controller_think()
+{
+ self.nextthink = time + 0.1;
+
+ if(self.owner.active != ACTIVE_ACTIVE)
+ {
+ self.owner.velocity = '0 0 0';
+ return;
+ }
+
+ if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
+ self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
+}
+
+void func_vectormamamam_findtarget()
+{
+ if(self.target != "")
+ self.wp00 = find(world, targetname, self.target);
+
+ if(self.target2 != "")
+ self.wp01 = find(world, targetname, self.target2);
+
+ if(self.target3 != "")
+ self.wp02 = find(world, targetname, self.target3);
+
+ if(self.target4 != "")
+ self.wp03 = find(world, targetname, self.target4);
+
+ if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
+ objerror("No reference entity found, so there is nothing to move. Aborting.");
+
+ self.destvec = self.origin - func_vectormamamam_origin(self, 0);
+
+ entity controller;
+ controller = spawn();
+ controller.classname = "func_vectormamamam_controller";
+ controller.owner = self;
+ controller.nextthink = time + 1;
+ controller.think = func_vectormamamam_controller_think;
+}
+
+void spawnfunc_func_vectormamamam()
+{
+ if (self.noise != "")
+ {
+ precache_sound(self.noise);
+ soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
+ }
+
+ if(!self.targetfactor)
+ self.targetfactor = 1;
+
+ if(!self.target2factor)
+ self.target2factor = 1;
+
+ if(!self.target3factor)
+ self.target3factor = 1;
+
+ if(!self.target4factor)
+ self.target4factor = 1;
+
+ if(vlen(self.targetnormal))
+ self.targetnormal = normalize(self.targetnormal);
+
+ if(vlen(self.target2normal))
+ self.target2normal = normalize(self.target2normal);
+
+ if(vlen(self.target3normal))
+ self.target3normal = normalize(self.target3normal);
+
+ if(vlen(self.target4normal))
+ self.target4normal = normalize(self.target4normal);
+
+ self.blocked = generic_plat_blocked;
+ if(self.dmg && (self.message == ""))
+ self.message = " was squished";
+ if(self.dmg && (self.message == ""))
+ self.message2 = "was squished by";
+ if(self.dmg && (!self.dmgtime))
+ self.dmgtime = 0.25;
+ self.dmgtime2 = time;
+
+ if(self.netname == "")
+ self.netname = "1 0 0 0 1";
+
+ if (!InitMovingBrushTrigger())
+ return;
+
+ // wait for targets to spawn
+ self.nextthink = self.ltime + 999999999;
+ self.think = SUB_NullThink; // for PushMove
+
+ // Savage: Reduce bandwith, critical on e.g. nexdm02
+ self.effects |= EF_LOWPRECISION;
+
+ self.active = ACTIVE_ACTIVE;
+
+ InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
+}
+#endif
--- /dev/null
+#include "include.qh"
+
+// some required common stuff
+#include "subs.qc"
+#include "triggers.qc"
+#include "platforms.qc"
+
+// func
+#include "func/include.qc"
+
+// misc
+#include "misc/include.qc"
+
+// target
+#include "target/include.qc"
+
+// trigger
+#include "trigger/include.qc"
+
--- /dev/null
+#ifndef TRIGGERS_INCLUDE_H
+#define TRIGGERS_INCLUDE_H
+
+// some required common stuff
+#ifdef CSQC
+ #include "../../server/item_key.qh"
+#endif
+#include "triggers.qh"
+#include "subs.qh"
+#include "platforms.qh"
+
+// func
+#include "func/include.qh"
+
+// misc
+#include "misc/include.qh"
+
+// target
+#include "target/include.qh"
+
+// trigger
+#include "trigger/include.qh"
+
+#endif
--- /dev/null
+#ifdef SVQC
+void spawnfunc_path_corner()
+{
+ // setup values for overriding train movement
+ // if a second value does not exist, both start and end speeds are the single value specified
+ if(!set_platmovetype(self, self.platmovetype))
+ return;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void follow_init()
+{
+ entity src, dst;
+ src = world;
+ dst = world;
+ if(self.killtarget != "")
+ src = find(world, targetname, self.killtarget);
+ if(self.target != "")
+ dst = find(world, targetname, self.target);
+
+ if(!src && !dst)
+ {
+ objerror("follow: could not find target/killtarget");
+ return;
+ }
+
+ if(self.jointtype)
+ {
+ // already done :P entity must stay
+ self.aiment = src;
+ self.enemy = dst;
+ }
+ else if(!src || !dst)
+ {
+ objerror("follow: could not find target/killtarget");
+ return;
+ }
+ else if(self.spawnflags & 1)
+ {
+ // attach
+ if(self.spawnflags & 2)
+ {
+ setattachment(dst, src, self.message);
+ }
+ else
+ {
+ attach_sameorigin(dst, src, self.message);
+ }
+
+ dst.solid = SOLID_NOT; // solid doesn't work with attachment
+ remove(self);
+ }
+ else
+ {
+ if(self.spawnflags & 2)
+ {
+ dst.movetype = MOVETYPE_FOLLOW;
+ dst.aiment = src;
+ // dst.punchangle = '0 0 0'; // keep unchanged
+ dst.view_ofs = dst.origin;
+ dst.v_angle = dst.angles;
+ }
+ else
+ {
+ follow_sameorigin(dst, src);
+ }
+
+ remove(self);
+ }
+}
+
+void spawnfunc_misc_follow()
+{
+ InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
+}
+#endif
--- /dev/null
+#include "include.qh"
+
+#include "corner.qc"
+#include "follow.qc"
+#include "laser.qc"
--- /dev/null
+#ifndef TRIGGERS_MISC_INCLUDE_H
+#define TRIGGERS_MISC_INCLUDE_H
+
+// nothing yet
+
+#endif
--- /dev/null
+#ifdef SVQC
+.float modelscale;
+void misc_laser_aim()
+{
+ vector a;
+ if(self.enemy)
+ {
+ if(self.spawnflags & 2)
+ {
+ if(self.enemy.origin != self.mangle)
+ {
+ self.mangle = self.enemy.origin;
+ self.SendFlags |= 2;
+ }
+ }
+ else
+ {
+ a = vectoangles(self.enemy.origin - self.origin);
+ a_x = -a_x;
+ if(a != self.mangle)
+ {
+ self.mangle = a;
+ self.SendFlags |= 2;
+ }
+ }
+ }
+ else
+ {
+ if(self.angles != self.mangle)
+ {
+ self.mangle = self.angles;
+ self.SendFlags |= 2;
+ }
+ }
+ if(self.origin != self.oldorigin)
+ {
+ self.SendFlags |= 1;
+ self.oldorigin = self.origin;
+ }
+}
+
+void misc_laser_init()
+{
+ if(self.target != "")
+ self.enemy = find(world, targetname, self.target);
+}
+
+.entity pusher;
+void misc_laser_think()
+{
+ vector o;
+ entity oldself;
+ entity hitent;
+ vector hitloc;
+
+ self.nextthink = time;
+
+ if(!self.state)
+ return;
+
+ misc_laser_aim();
+
+ if(self.enemy)
+ {
+ o = self.enemy.origin;
+ if (!(self.spawnflags & 2))
+ o = self.origin + normalize(o - self.origin) * 32768;
+ }
+ else
+ {
+ makevectors(self.mangle);
+ o = self.origin + v_forward * 32768;
+ }
+
+ if(self.dmg || self.enemy.target != "")
+ {
+ traceline(self.origin, o, MOVE_NORMAL, self);
+ }
+ hitent = trace_ent;
+ hitloc = trace_endpos;
+
+ if(self.enemy.target != "") // DETECTOR laser
+ {
+ if(trace_ent.iscreature)
+ {
+ self.pusher = hitent;
+ if(!self.count)
+ {
+ self.count = 1;
+
+ oldself = self;
+ self = self.enemy;
+ activator = self.pusher;
+ SUB_UseTargets();
+ self = oldself;
+ }
+ }
+ else
+ {
+ if(self.count)
+ {
+ self.count = 0;
+
+ oldself = self;
+ self = self.enemy;
+ activator = self.pusher;
+ SUB_UseTargets();
+ self = oldself;
+ }
+ }
+ }
+
+ if(self.dmg)
+ {
+ if(self.team)
+ if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
+ return;
+ if(hitent.takedamage)
+ Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
+ }
+}
+
+float laser_SendEntity(entity to, float fl)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
+ fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
+ if(self.spawnflags & 2)
+ fl |= 0x80;
+ if(self.alpha)
+ fl |= 0x40;
+ if(self.scale != 1 || self.modelscale != 1)
+ fl |= 0x20;
+ if(self.spawnflags & 4)
+ fl |= 0x10;
+ WriteByte(MSG_ENTITY, fl);
+ if(fl & 1)
+ {
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+ }
+ if(fl & 8)
+ {
+ WriteByte(MSG_ENTITY, self.colormod_x * 255.0);
+ WriteByte(MSG_ENTITY, self.colormod_y * 255.0);
+ WriteByte(MSG_ENTITY, self.colormod_z * 255.0);
+ if(fl & 0x40)
+ WriteByte(MSG_ENTITY, self.alpha * 255.0);
+ if(fl & 0x20)
+ {
+ WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
+ WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
+ }
+ if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
+ WriteShort(MSG_ENTITY, self.cnt + 1);
+ }
+ if(fl & 2)
+ {
+ if(fl & 0x80)
+ {
+ WriteCoord(MSG_ENTITY, self.enemy.origin_x);
+ WriteCoord(MSG_ENTITY, self.enemy.origin_y);
+ WriteCoord(MSG_ENTITY, self.enemy.origin_z);
+ }
+ else
+ {
+ WriteAngle(MSG_ENTITY, self.mangle_x);
+ WriteAngle(MSG_ENTITY, self.mangle_y);
+ }
+ }
+ if(fl & 4)
+ WriteByte(MSG_ENTITY, self.state);
+ return 1;
+}
+
+/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
+Any object touching the beam will be hurt
+Keys:
+"target"
+ spawnfunc_target_position where the laser ends
+"mdl"
+ name of beam end effect to use
+"colormod"
+ color of the beam (default: red)
+"dmg"
+ damage per second (-1 for a laser that kills immediately)
+*/
+void laser_use()
+{
+ self.state = !self.state;
+ self.SendFlags |= 4;
+ misc_laser_aim();
+}
+
+void laser_reset()
+{
+ if(self.spawnflags & 1)
+ self.state = 1;
+ else
+ self.state = 0;
+}
+
+void spawnfunc_misc_laser()
+{
+ if(self.mdl)
+ {
+ if(self.mdl == "none")
+ self.cnt = -1;
+ else
+ {
+ self.cnt = particleeffectnum(self.mdl);
+ if(self.cnt < 0)
+ if(self.dmg)
+ self.cnt = particleeffectnum("laser_deadly");
+ }
+ }
+ else if(!self.cnt)
+ {
+ if(self.dmg)
+ self.cnt = particleeffectnum("laser_deadly");
+ else
+ self.cnt = -1;
+ }
+ if(self.cnt < 0)
+ self.cnt = -1;
+
+ if(self.colormod == '0 0 0')
+ if(!self.alpha)
+ self.colormod = '1 0 0';
+ if(self.message == "")
+ self.message = "saw the light";
+ if (self.message2 == "")
+ self.message2 = "was pushed into a laser by";
+ if(!self.scale)
+ self.scale = 1;
+ if(!self.modelscale)
+ self.modelscale = 1;
+ else if(self.modelscale < 0)
+ self.modelscale = 0;
+ self.think = misc_laser_think;
+ self.nextthink = time;
+ InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
+
+ self.mangle = self.angles;
+
+ Net_LinkEntity(self, false, 0, laser_SendEntity);
+
+ IFTARGETED
+ {
+ self.reset = laser_reset;
+ laser_reset();
+ self.use = laser_use;
+ }
+ else
+ self.state = 1;
+}
+#endif
--- /dev/null
+void generic_plat_blocked()
+{
+#ifdef SVQC
+ if(self.dmg && other.takedamage != DAMAGE_NO)
+ {
+ if(self.dmgtime2 < time)
+ {
+ Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ self.dmgtime2 = time + self.dmgtime;
+ }
+
+ // Gib dead/dying stuff
+ if(other.deadflag != DEAD_NO)
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+#endif
+}
+
+#ifdef SVQC
+float plat_trigger_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_PLAT_TRIGGER);
+ WriteShort(MSG_ENTITY, num_for_edict(self.enemy));
+
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+ return true;
+}
+
+void plat_spawn_inside_trigger()
+{
+ entity trigger;
+ vector tmin, tmax;
+
+ trigger = spawn();
+ trigger.touch = plat_center_touch;
+ trigger.movetype = MOVETYPE_NONE;
+ trigger.solid = SOLID_TRIGGER;
+ trigger.enemy = self;
+
+ tmin = self.absmin + '25 25 0';
+ tmax = self.absmax - '25 25 -8';
+ tmin_z = tmax_z - (self.pos1_z - self.pos2_z + 8);
+ if (self.spawnflags & PLAT_LOW_TRIGGER)
+ tmax_z = tmin_z + 8;
+
+ if (self.size_x <= 50)
+ {
+ tmin_x = (self.mins_x + self.maxs_x) / 2;
+ tmax_x = tmin_x + 1;
+ }
+ if (self.size_y <= 50)
+ {
+ tmin_y = (self.mins_y + self.maxs_y) / 2;
+ tmax_y = tmin_y + 1;
+ }
+
+ if(tmin_x < tmax_x)
+ if(tmin_y < tmax_y)
+ if(tmin_z < tmax_z)
+ {
+ setsize (trigger, tmin, tmax);
+ Net_LinkEntity(trigger, false, 0, plat_trigger_send);
+ return;
+ }
+
+ // otherwise, something is fishy...
+ remove(trigger);
+ objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
+}
+#elif defined(CSQC)
+void ent_plat_trigger()
+{
+ float myenemy = ReadShort();
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.enemy = findfloat(world, sv_entnum, myenemy);
+ if(!self.enemy) { print("^1BAD BAD BAD!!!\n"); }
+ self.drawmask = MASK_NORMAL;
+ self.draw = trigger_draw_generic;
+ self.trigger_touch = plat_center_touch;
+ self.solid = SOLID_TRIGGER;
+}
+#endif
+
+void plat_hit_top()
+{
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.state = 1;
+ self.think = plat_go_down;
+ self.nextthink = self.ltime + 3;
+}
+
+void plat_hit_bottom()
+{
+ sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
+ self.state = 2;
+}
+
+void plat_go_down()
+{
+ sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
+ self.state = 3;
+ SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
+}
+
+void plat_go_up()
+{
+ sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
+ self.state = 4;
+ SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
+}
+
+void plat_center_touch()
+{
+#ifdef SVQC
+ if (!other.iscreature)
+ return;
+
+ if (other.health <= 0)
+ return;
+#elif defined(CSQC)
+ if (!IS_PLAYER(other))
+ return;
+#endif
+
+#ifdef CSQC
+ print("Got this far\n");
+#endif
+
+ self = self.enemy;
+ if (self.state == 2)
+ plat_go_up ();
+ else if (self.state == 1)
+ self.nextthink = self.ltime + 1; // delay going down
+}
+
+void plat_outside_touch()
+{
+#ifdef SVQC
+ if (!other.iscreature)
+ return;
+
+ if (other.health <= 0)
+ return;
+#elif defined(CSQC)
+ if (!IS_PLAYER(other))
+ return;
+#endif
+
+ self = self.enemy;
+ if (self.state == 1)
+ plat_go_down ();
+}
+
+void plat_trigger_use()
+{
+ if (self.think)
+ return; // already activated
+ plat_go_down();
+}
+
+
+void plat_crush()
+{
+ if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO))
+ { // KIll Kill Kill!!
+#ifdef SVQC
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+#endif
+ }
+ else
+ {
+#ifdef SVQC
+ if((self.dmg) && (other.takedamage != DAMAGE_NO))
+ { // Shall we bite?
+ Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ // Gib dead/dying stuff
+ if(other.deadflag != DEAD_NO)
+ Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+#endif
+
+ if (self.state == 4)
+ plat_go_down ();
+ else if (self.state == 3)
+ plat_go_up ();
+ // when in other states, then the plat_crush event came delayed after
+ // plat state already had changed
+ // this isn't a bug per se!
+ }
+}
+
+void plat_use()
+{
+ self.use = func_null;
+ if (self.state != 4)
+ objerror ("plat_use: not in up state");
+ plat_go_down();
+}
+
+.string sound1, sound2;
+
+void plat_reset()
+{
+ IFTARGETED
+ {
+ setorigin (self, self.pos1);
+ self.state = 4;
+ self.use = plat_use;
+ }
+ else
+ {
+ setorigin (self, self.pos2);
+ self.state = 2;
+ self.use = plat_trigger_use;
+ }
+
+#ifdef SVQC
+ self.SendFlags |= SF_TRIGGER_RESET;
+#endif
+}
+
+#ifdef SVQC
+.float platmovetype_start_default, platmovetype_end_default;
+float set_platmovetype(entity e, string s)
+{
+ // sets platmovetype_start and platmovetype_end based on a string consisting of two values
+
+ float n;
+ n = tokenize_console(s);
+ if(n > 0)
+ e.platmovetype_start = stof(argv(0));
+ else
+ e.platmovetype_start = 0;
+
+ if(n > 1)
+ e.platmovetype_end = stof(argv(1));
+ else
+ e.platmovetype_end = e.platmovetype_start;
+
+ if(n > 2)
+ if(argv(2) == "force")
+ return true; // no checking, return immediately
+
+ if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
+ {
+ objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
+ return false;
+ }
+
+ return true;
+}
+#endif
--- /dev/null
+#ifndef PLATFORMS_H
+#define PLATFORMS_H
+
+.float dmgtime2;
+
+void() plat_center_touch;
+void() plat_outside_touch;
+void() plat_trigger_use;
+void() plat_go_up;
+void() plat_go_down;
+void() plat_crush;
+const float PLAT_LOW_TRIGGER = 1;
+
+.float dmg;
+
+#ifdef CSQC
+void ent_plat_trigger();
+#endif
+
+#endif
--- /dev/null
+void SUB_NullThink(void) { }
+
+void() SUB_CalcMoveDone;
+void() SUB_CalcAngleMoveDone;
+//void() SUB_UseTargets;
+
+/*
+==================
+SUB_Remove
+
+Remove self
+==================
+*/
+void SUB_Remove()
+{
+ remove (self);
+}
+
+/*
+==================
+SUB_Friction
+
+Applies some friction to self
+==================
+*/
+.float friction;
+void SUB_Friction (void)
+{
+ self.nextthink = time;
+ if(self.flags & FL_ONGROUND)
+ self.velocity = self.velocity * (1 - frametime * self.friction);
+}
+
+/*
+==================
+SUB_VanishOrRemove
+
+Makes client invisible or removes non-client
+==================
+*/
+void SUB_VanishOrRemove (entity ent)
+{
+ if (IS_CLIENT(ent))
+ {
+ // vanish
+ ent.alpha = -1;
+ ent.effects = 0;
+#ifdef SVQC
+ ent.glow_size = 0;
+ ent.pflags = 0;
+#endif
+ }
+ else
+ {
+ // remove
+ remove (ent);
+ }
+}
+
+void SUB_SetFade_Think (void)
+{
+ if(self.alpha == 0)
+ self.alpha = 1;
+ self.think = SUB_SetFade_Think;
+ self.nextthink = time;
+ self.alpha -= frametime * self.fade_rate;
+ if (self.alpha < 0.01)
+ SUB_VanishOrRemove(self);
+ else
+ self.nextthink = time;
+}
+
+/*
+==================
+SUB_SetFade
+
+Fade 'ent' out when time >= 'when'
+==================
+*/
+void SUB_SetFade (entity ent, float when, float fading_time)
+{
+ ent.fade_rate = 1/fading_time;
+ ent.think = SUB_SetFade_Think;
+ ent.nextthink = when;
+}
+
+/*
+=============
+SUB_CalcMove
+
+calculate self.velocity and self.nextthink to reach dest from
+self.origin traveling at speed
+===============
+*/
+void SUB_CalcMoveDone (void)
+{
+ // After moving, set origin to exact final destination
+
+ setorigin (self, self.finaldest);
+ self.velocity = '0 0 0';
+ self.nextthink = -1;
+ if (self.think1)
+ self.think1 ();
+}
+
+.float platmovetype_turn;
+void SUB_CalcMove_controller_think (void)
+{
+ entity oldself;
+ float traveltime;
+ float phasepos;
+ float nexttick;
+ vector delta;
+ vector delta2;
+ vector veloc;
+ vector angloc;
+ vector nextpos;
+ delta = self.destvec;
+ delta2 = self.destvec2;
+ if(time < self.animstate_endtime)
+ {
+ nexttick = time + PHYS_INPUT_FRAMETIME;
+
+ traveltime = self.animstate_endtime - self.animstate_starttime;
+ phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
+ phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
+ nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
+ // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
+
+ if(self.owner.platmovetype_turn)
+ {
+ vector destangle;
+ destangle = delta + 2 * delta2 * phasepos;
+ destangle = vectoangles(destangle);
+ destangle_x = -destangle_x; // flip up / down orientation
+
+ // take the shortest distance for the angles
+ self.owner.angles_x -= 360 * floor((self.owner.angles_x - destangle_x) / 360 + 0.5);
+ self.owner.angles_y -= 360 * floor((self.owner.angles_y - destangle_y) / 360 + 0.5);
+ self.owner.angles_z -= 360 * floor((self.owner.angles_z - destangle_z) / 360 + 0.5);
+ angloc = destangle - self.owner.angles;
+ angloc = angloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
+ self.owner.avelocity = angloc;
+ }
+ if(nexttick < self.animstate_endtime)
+ veloc = nextpos - self.owner.origin;
+ else
+ veloc = self.finaldest - self.owner.origin;
+ veloc = veloc * (1 / PHYS_INPUT_FRAMETIME); // so it arrives for the next frame
+
+ self.owner.velocity = veloc;
+ self.nextthink = nexttick;
+ }
+ else
+ {
+ // derivative: delta + 2 * delta2 (e.g. for angle positioning)
+ oldself = self;
+ self.owner.think = self.think1;
+ self = self.owner;
+ remove(oldself);
+ self.think();
+ }
+}
+
+void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector destin)
+{
+ // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
+ // 2 * control * t - 2 * control * t * t + destin * t * t
+ // 2 * control * t + (destin - 2 * control) * t * t
+
+ controller.origin = org; // starting point
+ control -= org;
+ destin -= org;
+
+ controller.destvec = 2 * control; // control point
+ controller.destvec2 = destin - 2 * control; // quadratic part required to reach end point
+ // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (destin - control)
+}
+
+void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector destin)
+{
+ // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + destin * t * t
+ // 2 * control * t - 2 * control * t * t + destin * t * t
+ // 2 * control * t + (destin - 2 * control) * t * t
+
+ controller.origin = org; // starting point
+ destin -= org;
+
+ controller.destvec = destin; // end point
+ controller.destvec2 = '0 0 0';
+}
+
+float TSPEED_TIME = -1;
+float TSPEED_LINEAR = 0;
+float TSPEED_START = 1;
+float TSPEED_END = 2;
+// TODO average too?
+
+void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
+{
+ float traveltime;
+ entity controller;
+
+ if (!tspeed)
+ objerror ("No speed is defined!");
+
+ self.think1 = func;
+ self.finaldest = tdest;
+ self.think = SUB_CalcMoveDone;
+
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
+ break;
+ case TSPEED_END:
+ traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
+ break;
+ case TSPEED_LINEAR:
+ traveltime = vlen(tdest - self.origin) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
+
+ if (traveltime < 0.1) // useless anim
+ {
+ self.velocity = '0 0 0';
+ self.nextthink = self.ltime + 0.1;
+ return;
+ }
+
+ controller = spawn();
+ controller.classname = "SUB_CalcMove_controller";
+ controller.owner = self;
+ controller.platmovetype = self.platmovetype;
+ controller.platmovetype_start = self.platmovetype_start;
+ controller.platmovetype_end = self.platmovetype_end;
+ SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
+ controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
+ controller.animstate_starttime = time;
+ controller.animstate_endtime = time + traveltime;
+ controller.think = SUB_CalcMove_controller_think;
+ controller.think1 = self.think;
+
+ // the thinking is now done by the controller
+ self.think = SUB_NullThink; // for PushMove
+ self.nextthink = self.ltime + traveltime;
+
+ // invoke controller
+ self = controller;
+ self.think();
+ self = self.owner;
+}
+
+void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
+{
+ vector delta;
+ float traveltime;
+
+ if (!tspeed)
+ objerror ("No speed is defined!");
+
+ self.think1 = func;
+ self.finaldest = tdest;
+ self.think = SUB_CalcMoveDone;
+
+ if (tdest == self.origin)
+ {
+ self.velocity = '0 0 0';
+ self.nextthink = self.ltime + 0.1;
+ return;
+ }
+
+ delta = tdest - self.origin;
+
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ case TSPEED_END:
+ case TSPEED_LINEAR:
+ traveltime = vlen (delta) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
+
+ // Very short animations don't really show off the effect
+ // of controlled animation, so let's just use linear movement.
+ // Alternatively entities can choose to specify non-controlled movement.
+ // The only currently implemented alternative movement is linear (value 1)
+ if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
+ {
+ self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
+ self.nextthink = self.ltime + traveltime;
+ return;
+ }
+
+ // now just run like a bezier curve...
+ SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
+}
+
+void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
+{
+ entity oldself;
+
+ oldself = self;
+ self = ent;
+
+ SUB_CalcMove (tdest, tspeedtype, tspeed, func);
+
+ self = oldself;
+}
+
+/*
+=============
+SUB_CalcAngleMove
+
+calculate self.avelocity and self.nextthink to reach destangle from
+self.angles rotating
+
+The calling function should make sure self.think is valid
+===============
+*/
+void SUB_CalcAngleMoveDone (void)
+{
+ // After rotating, set angle to exact final angle
+ self.angles = self.finalangle;
+ self.avelocity = '0 0 0';
+ self.nextthink = -1;
+ if (self.think1)
+ self.think1 ();
+}
+
+// FIXME: I fixed this function only for rotation around the main axes
+void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
+{
+ vector delta;
+ float traveltime;
+
+ if (!tspeed)
+ objerror ("No speed is defined!");
+
+ // take the shortest distance for the angles
+ self.angles_x -= 360 * floor((self.angles_x - destangle_x) / 360 + 0.5);
+ self.angles_y -= 360 * floor((self.angles_y - destangle_y) / 360 + 0.5);
+ self.angles_z -= 360 * floor((self.angles_z - destangle_z) / 360 + 0.5);
+ delta = destangle - self.angles;
+
+ switch(tspeedtype)
+ {
+ default:
+ case TSPEED_START:
+ case TSPEED_END:
+ case TSPEED_LINEAR:
+ traveltime = vlen (delta) / tspeed;
+ break;
+ case TSPEED_TIME:
+ traveltime = tspeed;
+ break;
+ }
+
+ self.think1 = func;
+ self.finalangle = destangle;
+ self.think = SUB_CalcAngleMoveDone;
+
+ if (traveltime < 0.1)
+ {
+ self.avelocity = '0 0 0';
+ self.nextthink = self.ltime + 0.1;
+ return;
+ }
+
+ self.avelocity = delta * (1 / traveltime);
+ self.nextthink = self.ltime + traveltime;
+}
+
+void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
+{
+ entity oldself;
+
+ oldself = self;
+ self = ent;
+
+ SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
+
+ self = oldself;
+}
--- /dev/null
+#ifndef SUBS_H
+#define SUBS_H
+
+void SUB_Remove();
+void SUB_SetFade (entity ent, float when, float fading_time);
+void SUB_VanishOrRemove (entity ent);
+
+.vector finaldest, finalangle; //plat.qc stuff
+.void() think1;
+.float state;
+.float t_length, t_width;
+
+.vector destvec;
+.vector destvec2;
+
+// player animation state
+.float animstate_startframe;
+.float animstate_numframes;
+.float animstate_framerate;
+.float animstate_starttime;
+.float animstate_endtime;
+.float animstate_override;
+.float animstate_looping;
+
+.float delay;
+.float wait;
+.float lip;
+.float speed;
+.float sounds;
+.string platmovetype;
+.float platmovetype_start, platmovetype_end;
+
+entity activator;
+
+.string killtarget;
+
+.vector pos1, pos2;
+.vector mangle;
+
+.string target2;
+.string target3;
+.string target4;
+.string curvetarget;
+.float target_random;
+.float trigger_reverse;
+
+// Keys player is holding
+.float itemkeys;
+// message delay for func_door locked by keys and key locks
+// this field is used on player entities
+.float key_door_messagetime;
+
+.vector dest1, dest2;
+
+#ifdef CSQC
+// this stuff is defined in the server side engine VM, so we must define it separately here
+.float takedamage;
+const float DAMAGE_NO = 0;
+const float DAMAGE_YES = 1;
+const float DAMAGE_AIM = 2;
+
+float STATE_TOP = 0;
+float STATE_BOTTOM = 1;
+float STATE_UP = 2;
+float STATE_DOWN = 3;
+
+.string noise, noise1, noise2, noise3; // contains names of wavs to play
+
+.float max_health; // players maximum health is stored here
+#endif
+
+#endif
--- /dev/null
+#ifdef SVQC
+.string chmap, gametype;
+void spawnfunc_target_changelevel_use()
+{
+ if(self.gametype != "")
+ MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
+
+ if (self.chmap == "")
+ localcmd("endmatch\n");
+ else
+ localcmd(strcat("changelevel ", self.chmap, "\n"));
+}
+
+void spawnfunc_target_changelevel()
+{
+ self.use = spawnfunc_target_changelevel_use;
+}
+#endif
--- /dev/null
+#include "include.qh"
+
+#include "changelevel.qc"
+#include "music.qc"
+#include "spawn.qc"
+#include "speaker.qc"
+#include "voicescript.qc"
--- /dev/null
+#ifndef TRIGGERS_TARGET_INCLUDE_H
+#define TRIGGERS_TARGET_INCLUDE_H
+
+// nothing yet
+
+#endif
--- /dev/null
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+ #include "../../../dpdefs/progsdefs.qh"
+ #include "../../../dpdefs/dpextensions.qh"
+ #include "../../constants.qh"
+ #include "../../../server/constants.qh"
+ #include "../../../server/defs.qh"
+#endif
+
+#ifdef SVQC
+
+// values:
+// volume
+// noise
+// targetname
+// lifetime
+// fade_time
+// fade_rate
+// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0)
+// when targetname is not set, THIS ONE is default
+void target_music_sendto(float to, float is)
+{
+ WriteByte(to, SVC_TEMPENTITY);
+ WriteByte(to, TE_CSQC_TARGET_MUSIC);
+ WriteShort(to, num_for_edict(self));
+ WriteByte(to, self.volume * 255.0 * is);
+ WriteByte(to, self.fade_time * 16.0);
+ WriteByte(to, self.fade_rate * 16.0);
+ WriteByte(to, self.lifetime);
+ WriteString(to, self.noise);
+}
+void target_music_reset()
+{
+ if(self.targetname == "")
+ target_music_sendto(MSG_ALL, 1);
+}
+void target_music_use()
+{
+ if(!activator)
+ return;
+ if(IS_REAL_CLIENT(activator))
+ {
+ msg_entity = activator;
+ target_music_sendto(MSG_ONE, 1);
+ }
+ entity head;
+ FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
+}
+void spawnfunc_target_music()
+{
+ self.use = target_music_use;
+ self.reset = target_music_reset;
+ if(!self.volume)
+ self.volume = 1;
+ if(self.targetname == "")
+ target_music_sendto(MSG_INIT, 1);
+ else
+ target_music_sendto(MSG_INIT, 0);
+}
+void TargetMusic_RestoreGame()
+{
+ for(self = world; (self = find(self, classname, "target_music")); )
+ {
+ if(self.targetname == "")
+ target_music_sendto(MSG_INIT, 1);
+ else
+ target_music_sendto(MSG_INIT, 0);
+ }
+}
+// values:
+// volume
+// noise
+// targetname
+// fade_time
+// spawnflags:
+// 1 = START_OFF
+// when triggered, it is disabled/enabled for everyone
+float trigger_music_SendEntity(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC);
+ sf &= ~0x80;
+ if(self.cnt)
+ sf |= 0x80;
+ WriteByte(MSG_ENTITY, sf);
+ if(sf & 4)
+ {
+ WriteCoord(MSG_ENTITY, self.origin.x);
+ WriteCoord(MSG_ENTITY, self.origin.y);
+ WriteCoord(MSG_ENTITY, self.origin.z);
+ }
+ if(sf & 1)
+ {
+ if(self.model != "null")
+ {
+ WriteShort(MSG_ENTITY, self.modelindex);
+ WriteCoord(MSG_ENTITY, self.mins.x);
+ WriteCoord(MSG_ENTITY, self.mins.y);
+ WriteCoord(MSG_ENTITY, self.mins.z);
+ WriteCoord(MSG_ENTITY, self.maxs.x);
+ WriteCoord(MSG_ENTITY, self.maxs.y);
+ WriteCoord(MSG_ENTITY, self.maxs.z);
+ }
+ else
+ {
+ WriteShort(MSG_ENTITY, 0);
+ WriteCoord(MSG_ENTITY, self.maxs.x);
+ WriteCoord(MSG_ENTITY, self.maxs.y);
+ WriteCoord(MSG_ENTITY, self.maxs.z);
+ }
+ WriteByte(MSG_ENTITY, self.volume * 255.0);
+ WriteByte(MSG_ENTITY, self.fade_time * 16.0);
+ WriteByte(MSG_ENTITY, self.fade_rate * 16.0);
+ WriteString(MSG_ENTITY, self.noise);
+ }
+ return 1;
+}
+void trigger_music_reset()
+{
+ self.cnt = !(self.spawnflags & 1);
+ self.SendFlags |= 0x80;
+}
+void trigger_music_use()
+{
+ self.cnt = !self.cnt;
+ self.SendFlags |= 0x80;
+}
+void spawnfunc_trigger_music()
+{
+ if(self.model != "")
+ setmodel(self, self.model);
+ if(!self.volume)
+ self.volume = 1;
+ if(!self.modelindex)
+ {
+ setorigin(self, self.origin + self.mins);
+ setsize(self, '0 0 0', self.maxs - self.mins);
+ }
+ trigger_music_reset();
+
+ self.use = trigger_music_use;
+ self.reset = trigger_music_reset;
+
+ Net_LinkEntity(self, false, 0, trigger_music_SendEntity);
+}
+#endif
--- /dev/null
+#ifndef TARGET_MUSIC_H
+#define TARGET_MUSIC_H
+
+.float lifetime;
+
+#endif
--- /dev/null
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+ #include "../../../dpdefs/progsdefs.qh"
+ #include "../../../dpdefs/dpextensions.qh"
+ #include "../../util.qh"
+ #include "../../../server/defs.qh"
+#endif
+
+#ifdef SVQC
+
+// spawner entity
+// "classname" "target_spawn"
+// "message" "fieldname value fieldname value ..."
+// "spawnflags"
+// 1 = call the spawn function
+// 2 = trigger on map load
+
+float target_spawn_initialized;
+.void() target_spawn_spawnfunc;
+float target_spawn_spawnfunc_field;
+.entity target_spawn_activator;
+.float target_spawn_id;
+float target_spawn_count;
+
+void target_spawn_helper_setmodel()
+{
+ setmodel(self, self.model);
+}
+
+void target_spawn_helper_setsize()
+{
+ setsize(self, self.mins, self.maxs);
+}
+
+void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act)
+{
+ float i, n, valuefieldpos;
+ string key, value, valuefield, valueoffset, valueoffsetrandom;
+ entity valueent;
+ vector data, data2;
+ entity oldself;
+ entity oldactivator;
+
+ n = tokenize_console(msg);
+
+ for(i = 0; i < n-1; i += 2)
+ {
+ key = argv(i);
+ value = argv(i+1);
+ if(key == "$")
+ {
+ data.x = -1;
+ data.y = FIELD_STRING;
+ }
+ else
+ {
+ data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key)));
+ if(data.y == 0) // undefined field, i.e., invalid type
+ {
+ print("target_spawn: invalid/unknown entity key ", key, " specified, ignored!\n");
+ continue;
+ }
+ }
+ if(substring(value, 0, 1) == "$")
+ {
+ value = substring(value, 1, strlen(value) - 1);
+ if(substring(value, 0, 1) == "$")
+ {
+ // deferred replacement
+ // do nothing
+ // useful for creating target_spawns with this!
+ }
+ else
+ {
+ // replace me!
+ valuefieldpos = strstrofs(value, "+", 0);
+ valueoffset = "";
+ if(valuefieldpos != -1)
+ {
+ valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
+ value = substring(value, 0, valuefieldpos);
+ }
+
+ valuefieldpos = strstrofs(valueoffset, "+", 0);
+ valueoffsetrandom = "";
+ if(valuefieldpos != -1)
+ {
+ valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1);
+ valueoffset = substring(valueoffset, 0, valuefieldpos);
+ }
+
+ valuefieldpos = strstrofs(value, ".", 0);
+ valuefield = "";
+ if(valuefieldpos != -1)
+ {
+ valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
+ value = substring(value, 0, valuefieldpos);
+ }
+
+ if(value == "self")
+ {
+ valueent = self;
+ value = "";
+ }
+ else if(value == "activator")
+ {
+ valueent = act;
+ value = "";
+ }
+ else if(value == "other")
+ {
+ valueent = other;
+ value = "";
+ }
+ else if(value == "pusher")
+ {
+ if(time < act.pushltime)
+ valueent = act.pusher;
+ else
+ valueent = world;
+ value = "";
+ }
+ else if(value == "target")
+ {
+ valueent = e;
+ value = "";
+ }
+ else if(value == "killtarget")
+ {
+ valueent = kt;
+ value = "";
+ }
+ else if(value == "target2")
+ {
+ valueent = t2;
+ value = "";
+ }
+ else if(value == "target3")
+ {
+ valueent = t3;
+ value = "";
+ }
+ else if(value == "target4")
+ {
+ valueent = t4;
+ value = "";
+ }
+ else if(value == "time")
+ {
+ valueent = world;
+ value = ftos(time);
+ }
+ else
+ {
+ print("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!\n");
+ continue;
+ }
+
+ if(valuefield == "")
+ {
+ if(value == "")
+ value = ftos(num_for_edict(valueent));
+ }
+ else
+ {
+ if(value != "")
+ {
+ print("target_spawn: try to get a field of a non-entity, ignored!\n");
+ continue;
+ }
+ data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield)));
+ if(data2_y == 0) // undefined field, i.e., invalid type
+ {
+ print("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!\n");
+ continue;
+ }
+ value = getentityfieldstring(data2_x, valueent);
+ }
+
+ if(valueoffset != "")
+ {
+ switch(data.y)
+ {
+ case FIELD_STRING:
+ value = strcat(value, valueoffset);
+ break;
+ case FIELD_FLOAT:
+ value = ftos(stof(value) + stof(valueoffset));
+ break;
+ case FIELD_VECTOR:
+ value = vtos(stov(value) + stov(valueoffset));
+ break;
+ default:
+ print("target_spawn: only string, float and vector fields can do calculations, calculation ignored!\n");
+ break;
+ }
+ }
+
+ if(valueoffsetrandom != "")
+ {
+ switch(data.y)
+ {
+ case FIELD_FLOAT:
+ value = ftos(stof(value) + random() * stof(valueoffsetrandom));
+ break;
+ case FIELD_VECTOR:
+ data2 = stov(valueoffsetrandom);
+ value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1');
+ break;
+ default:
+ print("target_spawn: only float and vector fields can do random calculations, calculation ignored!\n");
+ break;
+ }
+ }
+ }
+ }
+ if(key == "$")
+ {
+ if(substring(value, 0, 1) == "_")
+ value = strcat("target_spawn_helper", value);
+ putentityfieldstring(target_spawn_spawnfunc_field, e, value);
+
+ oldself = self;
+ oldactivator = activator;
+
+ self = e;
+ activator = act;
+
+ self.target_spawn_spawnfunc();
+
+ self = oldself;
+ activator = oldactivator;
+ }
+ else
+ {
+ if(data.y == FIELD_VECTOR)
+ value = strreplace("'", "", value); // why?!?
+ putentityfieldstring(data.x, e, value);
+ }
+ }
+}
+
+void target_spawn_useon(entity e)
+{
+ self.target_spawn_activator = activator;
+ target_spawn_edit_entity(
+ e,
+ self.message,
+ find(world, targetname, self.killtarget),
+ find(world, targetname, self.target2),
+ find(world, targetname, self.target3),
+ find(world, targetname, self.target4),
+ activator
+ );
+}
+
+float target_spawn_cancreate()
+{
+ float c;
+ entity e;
+
+ c = self.count;
+ if(c == 0) // no limit?
+ return 1;
+
+ ++c; // increase count to not include MYSELF
+ for(e = world; (e = findfloat(e, target_spawn_id, self.target_spawn_id)); --c)
+ ;
+
+ // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more
+ if(c == 0)
+ return 0;
+ return 1;
+}
+
+void target_spawn_use()
+{
+ entity e;
+
+ if(self.target == "")
+ {
+ // spawn new entity
+ if(!target_spawn_cancreate())
+ return;
+ e = spawn();
+ target_spawn_useon(e);
+ e.target_spawn_id = self.target_spawn_id;
+ }
+ else if(self.target == "*activator")
+ {
+ // edit entity
+ if(activator)
+ target_spawn_useon(activator);
+ }
+ else
+ {
+ // edit entity
+ for(e = world; (e = find(e, targetname, self.target)); )
+ target_spawn_useon(e);
+ }
+}
+
+void target_spawn_spawnfirst()
+{
+ activator = self.target_spawn_activator;
+ if(self.spawnflags & 2)
+ target_spawn_use();
+}
+
+void initialize_field_db()
+{
+ if(!target_spawn_initialized)
+ {
+ float n, i;
+ string fn;
+ vector prev, new;
+ float ft;
+
+ n = numentityfields();
+ for(i = 0; i < n; ++i)
+ {
+ fn = entityfieldname(i);
+ ft = entityfieldtype(i);
+ new = i * '1 0 0' + ft * '0 1 0' + '0 0 1';
+ prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn)));
+ if(prev.y == 0)
+ {
+ db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(new));
+ if(fn == "target_spawn_spawnfunc")
+ target_spawn_spawnfunc_field = i;
+ }
+ }
+
+ target_spawn_initialized = 1;
+ }
+}
+
+void spawnfunc_target_spawn()
+{
+ initialize_field_db();
+ self.use = target_spawn_use;
+ self.message = strzone(strreplace("'", "\"", self.message));
+ self.target_spawn_id = ++target_spawn_count;
+ InitializeEntity(self, target_spawn_spawnfirst, INITPRIO_LAST);
+}
+#endif
--- /dev/null
+#ifdef SVQC
+// TODO add a way to do looped sounds with sound(); then complete this entity
+void target_speaker_use_off();
+void target_speaker_use_activator()
+{
+ if (!IS_REAL_CLIENT(activator))
+ return;
+ string snd;
+ if(substring(self.noise, 0, 1) == "*")
+ {
+ var .string sample;
+ sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+ if(GetPlayerSoundSampleField_notFound)
+ snd = "misc/null.wav";
+ else if(activator.sample == "")
+ snd = "misc/null.wav";
+ else
+ {
+ tokenize_console(activator.sample);
+ float n;
+ n = stof(argv(1));
+ if(n > 0)
+ snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+ else
+ snd = strcat(argv(0), ".wav"); // randomization
+ }
+ }
+ else
+ snd = self.noise;
+ msg_entity = activator;
+ soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
+}
+void target_speaker_use_on()
+{
+ string snd;
+ if(substring(self.noise, 0, 1) == "*")
+ {
+ var .string sample;
+ sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
+ if(GetPlayerSoundSampleField_notFound)
+ snd = "misc/null.wav";
+ else if(activator.sample == "")
+ snd = "misc/null.wav";
+ else
+ {
+ tokenize_console(activator.sample);
+ float n;
+ n = stof(argv(1));
+ if(n > 0)
+ snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
+ else
+ snd = strcat(argv(0), ".wav"); // randomization
+ }
+ }
+ else
+ snd = self.noise;
+ sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
+ if(self.spawnflags & 3)
+ self.use = target_speaker_use_off;
+}
+void target_speaker_use_off()
+{
+ sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
+ self.use = target_speaker_use_on;
+}
+void target_speaker_reset()
+{
+ if(self.spawnflags & 1) // LOOPED_ON
+ {
+ if(self.use == target_speaker_use_on)
+ target_speaker_use_on();
+ }
+ else if(self.spawnflags & 2)
+ {
+ if(self.use == target_speaker_use_off)
+ target_speaker_use_off();
+ }
+}
+
+void spawnfunc_target_speaker()
+{
+ // TODO: "*" prefix to sound file name
+ // TODO: wait and random (just, HOW? random is not a field)
+ if(self.noise)
+ precache_sound (self.noise);
+
+ if(!self.atten && !(self.spawnflags & 4))
+ {
+ IFTARGETED
+ self.atten = ATTEN_NORM;
+ else
+ self.atten = ATTEN_STATIC;
+ }
+ else if(self.atten < 0)
+ self.atten = 0;
+
+ if(!self.volume)
+ self.volume = 1;
+
+ IFTARGETED
+ {
+ if(self.spawnflags & 8) // ACTIVATOR
+ self.use = target_speaker_use_activator;
+ else if(self.spawnflags & 1) // LOOPED_ON
+ {
+ target_speaker_use_on();
+ self.reset = target_speaker_reset;
+ }
+ else if(self.spawnflags & 2) // LOOPED_OFF
+ {
+ self.use = target_speaker_use_on;
+ self.reset = target_speaker_reset;
+ }
+ else
+ self.use = target_speaker_use_on;
+ }
+ else if(self.spawnflags & 1) // LOOPED_ON
+ {
+ ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+ remove(self);
+ }
+ else if(self.spawnflags & 2) // LOOPED_OFF
+ {
+ objerror("This sound entity can never be activated");
+ }
+ else
+ {
+ // Quake/Nexuiz fallback
+ ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
+ remove(self);
+ }
+}
+#endif
--- /dev/null
+#ifdef SVQC
+.entity voicescript; // attached voice script
+.float voicescript_index; // index of next voice, or -1 to use the randomized ones
+.float voicescript_nextthink; // time to play next voice
+.float voicescript_voiceend; // time when this voice ends
+
+void target_voicescript_clear(entity pl)
+{
+ pl.voicescript = world;
+}
+
+void target_voicescript_use()
+{
+ if(activator.voicescript != self)
+ {
+ activator.voicescript = self;
+ activator.voicescript_index = 0;
+ activator.voicescript_nextthink = time + self.delay;
+ }
+}
+
+void target_voicescript_next(entity pl)
+{
+ entity vs;
+ float i, n, dt;
+
+ vs = pl.voicescript;
+ if(!vs)
+ return;
+ if(vs.message == "")
+ return;
+ if (!IS_PLAYER(pl))
+ return;
+ if(gameover)
+ return;
+
+ if(time >= pl.voicescript_voiceend)
+ {
+ if(time >= pl.voicescript_nextthink)
+ {
+ // get the next voice...
+ n = tokenize_console(vs.message);
+
+ if(pl.voicescript_index < vs.cnt)
+ i = pl.voicescript_index * 2;
+ else if(n > vs.cnt * 2)
+ i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
+ else
+ i = -1;
+
+ if(i >= 0)
+ {
+ play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
+ dt = stof(argv(i + 1));
+ if(dt >= 0)
+ {
+ pl.voicescript_voiceend = time + dt;
+ pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
+ }
+ else
+ {
+ pl.voicescript_voiceend = time - dt;
+ pl.voicescript_nextthink = pl.voicescript_voiceend;
+ }
+
+ pl.voicescript_index += 1;
+ }
+ else
+ {
+ pl.voicescript = world; // stop trying then
+ }
+ }
+ }
+}
+
+void spawnfunc_target_voicescript()
+{
+ // netname: directory of the sound files
+ // message: list of "sound file" duration "sound file" duration, a *, and again a list
+ // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
+ // Here, a - in front of the duration means that no delay is to be
+ // added after this message
+ // wait: average time between messages
+ // delay: initial delay before the first message
+
+ float i, n;
+ self.use = target_voicescript_use;
+
+ n = tokenize_console(self.message);
+ self.cnt = n / 2;
+ for(i = 0; i+1 < n; i += 2)
+ {
+ if(argv(i) == "*")
+ {
+ self.cnt = i / 2;
+ ++i;
+ }
+ precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
+ }
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void counter_use()
+{
+ self.count -= 1;
+ if (self.count < 0)
+ return;
+
+ if (self.count == 0)
+ {
+ if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+ Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
+
+ self.enemy = activator;
+ multi_trigger ();
+ }
+ else
+ {
+ if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
+ if(self.count >= 4)
+ Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
+ else
+ Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
+ }
+}
+
+void counter_reset()
+{
+ self.count = self.cnt;
+ multi_reset();
+}
+
+/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
+Acts as an intermediary for an action that takes multiple inputs.
+
+If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
+
+After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
+*/
+void spawnfunc_trigger_counter()
+{
+ self.wait = -1;
+ if (!self.count)
+ self.count = 2;
+ self.cnt = self.count;
+
+ self.use = counter_use;
+ self.reset = counter_reset;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void delay_use()
+{
+ self.think = SUB_UseTargets;
+ self.nextthink = self.wait;
+}
+
+void delay_reset()
+{
+ self.think = func_null;
+ self.nextthink = 0;
+}
+
+void spawnfunc_trigger_delay()
+{
+ if(!self.wait)
+ self.wait = 1;
+
+ self.use = delay_use;
+ self.reset = delay_reset;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void trigger_disablerelay_use()
+{
+ entity e;
+
+ float a, b;
+ a = b = 0;
+
+ for(e = world; (e = find(e, targetname, self.target)); )
+ {
+ if(e.use == SUB_UseTargets)
+ {
+ e.use = SUB_DontUseTargets;
+ ++a;
+ }
+ else if(e.use == SUB_DontUseTargets)
+ {
+ e.use = SUB_UseTargets;
+ ++b;
+ }
+ }
+
+ if((!a) == (!b))
+ print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
+}
+
+void spawnfunc_trigger_disablerelay()
+{
+ self.use = trigger_disablerelay_use;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
+"Flip-flop" trigger gate... lets only every second trigger event through
+*/
+void flipflop_use()
+{
+ self.state = !self.state;
+ if(self.state)
+ SUB_UseTargets();
+}
+
+void spawnfunc_trigger_flipflop()
+{
+ if(self.spawnflags & 1)
+ self.state = 1;
+ self.use = flipflop_use;
+ self.reset = spawnfunc_trigger_flipflop; // perfect resetter
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void gamestart_use()
+{
+ activator = self;
+ SUB_UseTargets();
+ remove(self);
+}
+
+void spawnfunc_trigger_gamestart()
+{
+ self.use = gamestart_use;
+ self.reset2 = spawnfunc_trigger_gamestart;
+
+ if(self.wait)
+ {
+ self.think = self.use;
+ self.nextthink = game_starttime + self.wait;
+ }
+ else
+ InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
+}
+#endif
--- /dev/null
+#ifdef SVQC
+.entity trigger_gravity_check;
+void trigger_gravity_remove(entity own)
+{
+ if(own.trigger_gravity_check.owner == own)
+ {
+ UpdateCSQCProjectile(own);
+ own.gravity = own.trigger_gravity_check.gravity;
+ remove(own.trigger_gravity_check);
+ }
+ else
+ backtrace("Removing a trigger_gravity_check with no valid owner");
+ own.trigger_gravity_check = world;
+}
+void trigger_gravity_check_think()
+{
+ // This spawns when a player enters the gravity zone and checks if he left.
+ // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
+ // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
+ if(self.count <= 0)
+ {
+ if(self.owner.trigger_gravity_check == self)
+ trigger_gravity_remove(self.owner);
+ else
+ remove(self);
+ return;
+ }
+ else
+ {
+ self.count -= 1;
+ self.nextthink = time;
+ }
+}
+
+void trigger_gravity_use()
+{
+ self.state = !self.state;
+}
+
+void trigger_gravity_touch()
+{
+ float g;
+
+ if(self.state != true)
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ g = self.gravity;
+
+ if (!(self.spawnflags & 1))
+ {
+ if(other.trigger_gravity_check)
+ {
+ if(self == other.trigger_gravity_check.enemy)
+ {
+ // same?
+ other.trigger_gravity_check.count = 2; // gravity one more frame...
+ return;
+ }
+
+ // compare prio
+ if(self.cnt > other.trigger_gravity_check.enemy.cnt)
+ trigger_gravity_remove(other);
+ else
+ return;
+ }
+ other.trigger_gravity_check = spawn();
+ other.trigger_gravity_check.enemy = self;
+ other.trigger_gravity_check.owner = other;
+ other.trigger_gravity_check.gravity = other.gravity;
+ other.trigger_gravity_check.think = trigger_gravity_check_think;
+ other.trigger_gravity_check.nextthink = time;
+ other.trigger_gravity_check.count = 2;
+ if(other.gravity)
+ g *= other.gravity;
+ }
+
+ if (other.gravity != g)
+ {
+ other.gravity = g;
+ if(self.noise != "")
+ sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+ UpdateCSQCProjectile(self.owner);
+ }
+}
+
+void spawnfunc_trigger_gravity()
+{
+ if(self.gravity == 1)
+ return;
+
+ EXACTTRIGGER_INIT;
+ self.touch = trigger_gravity_touch;
+ if(self.noise != "")
+ precache_sound(self.noise);
+
+ self.state = true;
+ IFTARGETED
+ {
+ self.use = trigger_gravity_use;
+ if(self.spawnflags & 2)
+ self.state = false;
+ }
+}
+#endif
--- /dev/null
+#ifdef SVQC
+.float triggerhealtime;
+void trigger_heal_touch()
+{
+ if (self.active != ACTIVE_ACTIVE)
+ return;
+
+ // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
+ if (other.iscreature)
+ {
+ if (other.takedamage)
+ if (!other.deadflag)
+ if (other.triggerhealtime < time)
+ {
+ EXACTTRIGGER_TOUCH;
+ other.triggerhealtime = time + 1;
+
+ if (other.health < self.max_health)
+ {
+ other.health = min(other.health + self.health, self.max_health);
+ other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
+ sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+ }
+ }
+ }
+}
+
+void spawnfunc_trigger_heal()
+{
+ self.active = ACTIVE_ACTIVE;
+
+ EXACTTRIGGER_INIT;
+ self.touch = trigger_heal_touch;
+ if (!self.health)
+ self.health = 10;
+ if (!self.max_health)
+ self.max_health = 200; //Max health topoff for field
+ if(self.noise == "")
+ self.noise = "misc/mediumhealth.wav";
+ precache_sound(self.noise);
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void trigger_hurt_use()
+{
+ if(IS_PLAYER(activator))
+ self.enemy = activator;
+ else
+ self.enemy = world; // let's just destroy it, if taking over is too much work
+}
+
+.float triggerhurttime;
+void trigger_hurt_touch()
+{
+ if (self.active != ACTIVE_ACTIVE)
+ return;
+
+ if(self.team)
+ if(((self.spawnflags & 4) == 0) == (self.team != other.team))
+ return;
+
+ // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
+ if (other.iscreature)
+ {
+ if (other.takedamage)
+ if (other.triggerhurttime < time)
+ {
+ EXACTTRIGGER_TOUCH;
+ other.triggerhurttime = time + 1;
+
+ entity own;
+ own = self.enemy;
+ if (!IS_PLAYER(own))
+ {
+ own = self;
+ self.enemy = world; // I still hate you all
+ }
+
+ Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+ }
+ else if(other.damagedbytriggers)
+ {
+ if(other.takedamage)
+ {
+ EXACTTRIGGER_TOUCH;
+ Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
+ }
+ }
+
+ return;
+}
+
+/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
+Any object touching this will be hurt
+set dmg to damage amount
+defalt dmg = 5
+*/
+.entity trigger_hurt_next;
+entity trigger_hurt_last;
+entity trigger_hurt_first;
+void spawnfunc_trigger_hurt()
+{
+ EXACTTRIGGER_INIT;
+ self.active = ACTIVE_ACTIVE;
+ self.touch = trigger_hurt_touch;
+ self.use = trigger_hurt_use;
+ self.enemy = world; // I hate you all
+ if (!self.dmg)
+ self.dmg = 1000;
+ if (self.message == "")
+ self.message = "was in the wrong place";
+ if (self.message2 == "")
+ self.message2 = "was thrown into a world of hurt by";
+ // self.message = "someone like %s always gets wrongplaced";
+
+ if(!trigger_hurt_first)
+ trigger_hurt_first = self;
+ if(trigger_hurt_last)
+ trigger_hurt_last.trigger_hurt_next = self;
+ trigger_hurt_last = self;
+}
+
+float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
+{
+ entity th;
+
+ for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
+ if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
+ return true;
+
+ return false;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+// targeted (directional) mode
+void trigger_impulse_touch1()
+{
+ entity targ;
+ float pushdeltatime;
+ float str;
+
+ if (self.active != ACTIVE_ACTIVE)
+ return;
+
+ if (!isPushable(other))
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ targ = find(world, targetname, self.target);
+ if(!targ)
+ {
+ objerror("trigger_force without a (valid) .target!\n");
+ remove(self);
+ return;
+ }
+
+ str = min(self.radius, vlen(self.origin - other.origin));
+
+ if(self.falloff == 1)
+ str = (str / self.radius) * self.strength;
+ else if(self.falloff == 2)
+ str = (1 - (str / self.radius)) * self.strength;
+ else
+ str = self.strength;
+
+ pushdeltatime = time - other.lastpushtime;
+ if (pushdeltatime > 0.15) pushdeltatime = 0;
+ other.lastpushtime = time;
+ if(!pushdeltatime) return;
+
+ other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
+ other.flags &= ~FL_ONGROUND;
+ UpdateCSQCProjectile(other);
+}
+
+// Directionless (accelerator/decelerator) mode
+void trigger_impulse_touch2()
+{
+ float pushdeltatime;
+
+ if (self.active != ACTIVE_ACTIVE)
+ return;
+
+ if (!isPushable(other))
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ pushdeltatime = time - other.lastpushtime;
+ if (pushdeltatime > 0.15) pushdeltatime = 0;
+ other.lastpushtime = time;
+ if(!pushdeltatime) return;
+
+ // div0: ticrate independent, 1 = identity (not 20)
+ other.velocity = other.velocity * pow(self.strength, pushdeltatime);
+ UpdateCSQCProjectile(other);
+}
+
+// Spherical (gravity/repulsor) mode
+void trigger_impulse_touch3()
+{
+ float pushdeltatime;
+ float str;
+
+ if (self.active != ACTIVE_ACTIVE)
+ return;
+
+ if (!isPushable(other))
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ pushdeltatime = time - other.lastpushtime;
+ if (pushdeltatime > 0.15) pushdeltatime = 0;
+ other.lastpushtime = time;
+ if(!pushdeltatime) return;
+
+ setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
+
+ str = min(self.radius, vlen(self.origin - other.origin));
+
+ if(self.falloff == 1)
+ str = (1 - str / self.radius) * self.strength; // 1 in the inside
+ else if(self.falloff == 2)
+ str = (str / self.radius) * self.strength; // 0 in the inside
+ else
+ str = self.strength;
+
+ other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
+ UpdateCSQCProjectile(other);
+}
+
+/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
+-------- KEYS --------
+target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
+ If not, this trigger acts like a damper/accelerator field.
+
+strength : This is how mutch force to add in the direction of .target each second
+ when .target is set. If not, this is hoe mutch to slow down/accelerate
+ someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
+
+radius : If set, act as a spherical device rather then a liniar one.
+
+falloff : 0 = none, 1 = liniar, 2 = inverted liniar
+
+-------- NOTES --------
+Use a brush textured with common/origin in the trigger entity to determine the origin of the force
+in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
+*/
+
+void spawnfunc_trigger_impulse()
+{
+ self.active = ACTIVE_ACTIVE;
+
+ EXACTTRIGGER_INIT;
+ if(self.radius)
+ {
+ if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
+ setorigin(self, self.origin);
+ setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
+ self.touch = trigger_impulse_touch3;
+ }
+ else
+ {
+ if(self.target)
+ {
+ if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
+ self.touch = trigger_impulse_touch1;
+ }
+ else
+ {
+ if(!self.strength) self.strength = 0.9;
+ self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
+ self.touch = trigger_impulse_touch2;
+ }
+ }
+}
+#endif
--- /dev/null
+#ifndef TRIGGER_IMPULSE_H
+#define TRIGGER_IMPULSE_H
+
+// tZorks trigger impulse / gravity
+.float radius;
+.float falloff;
+.float strength;
+.float lastpushtime;
+
+#endif
--- /dev/null
+#include "include.qh"
+
+#include "counter.qc"
+#include "delay.qc"
+#include "disablerelay.qc"
+#include "flipflop.qc"
+#include "gamestart.qc"
+#include "gravity.qc"
+#include "heal.qc"
+#include "hurt.qc"
+#include "impulse.qc"
+#include "jumppads.qc"
+#include "magicear.qc"
+#include "monoflop.qc"
+#include "multi.qc"
+#include "multivibrator.qc"
+#include "relay.qc"
+#include "relay_activators.qc"
+#include "relay_if.qc"
+#include "relay_teamcheck.qc"
+#include "secret.qc"
+#include "swamp.qc"
--- /dev/null
+#ifndef TRIGGERS_TRIGGER_INCLUDE_H
+#define TRIGGERS_TRIGGER_INCLUDE_H
+
+#include "multi.qh"
+#include "jumppads.qh"
+#include "secret.qh"
+#include "swamp.qh"
+
+#endif
--- /dev/null
+// TODO: split target_push and put it in the target folder
+#ifdef SVQC
+#include "jumppads.qh"
+
+void trigger_push_use()
+{
+ if(teamplay)
+ {
+ self.team = activator.team;
+ self.SendFlags |= 2;
+ }
+}
+#endif
+
+/*
+ trigger_push_calculatevelocity
+
+ Arguments:
+ org - origin of the object which is to be pushed
+ tgt - target entity (can be either a point or a model entity; if it is
+ the latter, its midpoint is used)
+ ht - jump height, measured from the higher one of org and tgt's midpoint
+
+ Returns: velocity for the jump
+ the global trigger_push_calculatevelocity_flighttime is set to the total
+ jump time
+ */
+
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
+{
+ float grav, sdist, zdist, vs, vz, jumpheight;
+ vector sdir, torg;
+
+ torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
+
+ grav = PHYS_GRAVITY;
+ if(PHYS_ENTGRAVITY(other))
+ grav *= PHYS_ENTGRAVITY(other);
+
+ zdist = torg.z - org.z;
+ sdist = vlen(torg - org - zdist * '0 0 1');
+ sdir = normalize(torg - org - zdist * '0 0 1');
+
+ // how high do we need to push the player?
+ jumpheight = fabs(ht);
+ if(zdist > 0)
+ jumpheight = jumpheight + zdist;
+
+ /*
+ STOP.
+
+ You will not understand the following equations anyway...
+ But here is what I did to get them.
+
+ I used the functions
+
+ s(t) = t * vs
+ z(t) = t * vz - 1/2 grav t^2
+
+ and solved for:
+
+ s(ti) = sdist
+ z(ti) = zdist
+ max(z, ti) = jumpheight
+
+ From these three equations, you will find the three parameters vs, vz
+ and ti.
+ */
+
+ // push him so high...
+ vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
+
+ // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
+ if(ht < 0)
+ if(zdist < 0)
+ vz = -vz;
+
+ vector solution;
+ solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
+ // ALWAYS solvable because jumpheight >= zdist
+ if(!solution.z)
+ solution.y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0)
+ if(zdist == 0)
+ solution.x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
+
+ if(zdist < 0)
+ {
+ // down-jump
+ if(ht < 0)
+ {
+ // almost straight line type
+ // jump apex is before the jump
+ // we must take the larger one
+ trigger_push_calculatevelocity_flighttime = solution.y;
+ }
+ else
+ {
+ // regular jump
+ // jump apex is during the jump
+ // we must take the larger one too
+ trigger_push_calculatevelocity_flighttime = solution.y;
+ }
+ }
+ else
+ {
+ // up-jump
+ if(ht < 0)
+ {
+ // almost straight line type
+ // jump apex is after the jump
+ // we must take the smaller one
+ trigger_push_calculatevelocity_flighttime = solution.x;
+ }
+ else
+ {
+ // regular jump
+ // jump apex is during the jump
+ // we must take the larger one
+ trigger_push_calculatevelocity_flighttime = solution.y;
+ }
+ }
+ vs = sdist / trigger_push_calculatevelocity_flighttime;
+
+ // finally calculate the velocity
+ return sdir * vs + '0 0 1' * vz;
+}
+
+void trigger_push_touch()
+{
+ if (self.active == ACTIVE_NOT)
+ return;
+
+#ifdef SVQC
+ if (!isPushable(other))
+ return;
+#endif
+
+ if(self.team)
+ if(((self.spawnflags & 4) == 0) == (DIFF_TEAM(self, other)))
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ if(self.enemy)
+ {
+ other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
+ }
+ else if(self.target)
+ {
+ entity e;
+ RandomSelection_Init();
+ for(e = world; (e = find(e, targetname, self.target)); )
+ {
+ if(e.cnt)
+ RandomSelection_Add(e, 0, string_null, e.cnt, 1);
+ else
+ RandomSelection_Add(e, 0, string_null, 1, 1);
+ }
+ other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
+ }
+ else
+ {
+ other.velocity = self.movedir;
+ }
+
+ UNSET_ONGROUND(other);
+
+#ifdef SVQC
+ if (IS_PLAYER(other))
+ {
+ // reset tracking of oldvelocity for impact damage (sudden velocity changes)
+ other.oldvelocity = other.velocity;
+
+ if(self.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once
+ {
+ // flash when activated
+ pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
+ sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+ self.pushltime = time + 0.2;
+ }
+ if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
+ {
+ bool found = false;
+ for(int i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
+ if(other.(jumppadsused[i]) == self)
+ found = true;
+ if(!found)
+ {
+ other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = self;
+ other.jumppadcount = other.jumppadcount + 1;
+ }
+
+ if(IS_REAL_CLIENT(other))
+ {
+ if(self.message)
+ centerprint(other, self.message);
+ }
+ else
+ other.lastteleporttime = time;
+
+ if (other.deadflag == DEAD_NO)
+ animdecide_setaction(other, ANIMACTION_JUMP, true);
+ }
+ else
+ other.jumppadcount = true;
+
+ // reset tracking of who pushed you into a hazard (for kill credit)
+ other.pushltime = 0;
+ other.istypefrag = 0;
+ }
+
+ if(self.enemy.target)
+ {
+ entity oldself;
+ oldself = self;
+ activator = other;
+ self = self.enemy;
+ SUB_UseTargets();
+ self = oldself;
+ }
+
+ if (other.flags & FL_PROJECTILE)
+ {
+ other.angles = vectoangles (other.velocity);
+ switch(other.movetype)
+ {
+ case MOVETYPE_FLY:
+ other.movetype = MOVETYPE_TOSS;
+ other.gravity = 1;
+ break;
+ case MOVETYPE_BOUNCEMISSILE:
+ other.movetype = MOVETYPE_BOUNCE;
+ other.gravity = 1;
+ break;
+ }
+ UpdateCSQCProjectile(other);
+ }
+
+ if (self.spawnflags & PUSH_ONCE)
+ {
+ self.touch = func_null;
+ self.think = SUB_Remove;
+ self.nextthink = time;
+ }
+#endif
+}
+
+#ifdef SVQC
+void trigger_push_link();
+void trigger_push_updatelink();
+#endif
+void trigger_push_findtarget()
+{
+ entity t;
+ vector org;
+
+ // first calculate a typical start point for the jump
+ org = (self.absmin + self.absmax) * 0.5;
+ org.z = self.absmax.z - PL_MIN_z;
+
+ if (self.target)
+ {
+ float n = 0;
+ for(t = world; (t = find(t, targetname, self.target)); )
+ {
+ ++n;
+#ifdef SVQC
+ entity e = spawn();
+ setorigin(e, org);
+ setsize(e, PL_MIN, PL_MAX);
+ e.velocity = trigger_push_calculatevelocity(org, t, self.height);
+ tracetoss(e, e);
+ if(e.movetype == MOVETYPE_NONE)
+ waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+ remove(e);
+#endif
+ }
+
+ if(!n)
+ {
+ // no dest!
+#ifdef SVQC
+ objerror ("Jumppad with nonexistant target");
+#endif
+ return;
+ }
+ else if(n == 1)
+ {
+ // exactly one dest - bots love that
+ self.enemy = find(world, targetname, self.target);
+ }
+ else
+ {
+ // have to use random selection every single time
+ self.enemy = world;
+ }
+ }
+#ifdef SVQC
+ else
+ {
+ entity e = spawn();
+ setorigin(e, org);
+ setsize(e, PL_MIN, PL_MAX);
+ e.velocity = self.movedir;
+ tracetoss(e, e);
+ waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
+ remove(e);
+ }
+
+ trigger_push_link();
+ defer(0.1, trigger_push_updatelink);
+#endif
+}
+
+#ifdef SVQC
+float trigger_push_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
+ WriteByte(MSG_ENTITY, sf);
+
+ if(sf & 1)
+ {
+ WriteString(MSG_ENTITY, self.target);
+ WriteByte(MSG_ENTITY, self.team);
+ WriteInt24_t(MSG_ENTITY, self.spawnflags);
+ WriteByte(MSG_ENTITY, self.active);
+ WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+ WriteByte(MSG_ENTITY, self.height);
+ WriteByte(MSG_ENTITY, self.scale);
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.movedir_x);
+ WriteCoord(MSG_ENTITY, self.movedir_y);
+ WriteCoord(MSG_ENTITY, self.movedir_z);
+
+ WriteCoord(MSG_ENTITY, self.angles_x);
+ WriteCoord(MSG_ENTITY, self.angles_y);
+ WriteCoord(MSG_ENTITY, self.angles_z);
+ }
+
+ if(sf & 2)
+ {
+ WriteByte(MSG_ENTITY, self.team);
+ WriteByte(MSG_ENTITY, self.active);
+ }
+
+ return true;
+}
+
+void trigger_push_updatelink()
+{
+ self.SendFlags |= 1;
+}
+
+void trigger_push_link()
+{
+ Net_LinkEntity(self, false, 0, trigger_push_send);
+}
+#endif
+#ifdef SVQC
+/*
+ * ENTITY PARAMETERS:
+ *
+ * target: target of jump
+ * height: the absolute value is the height of the highest point of the jump
+ * trajectory above the higher one of the player and the target.
+ * the sign indicates whether the highest point is INSIDE (positive)
+ * or OUTSIDE (negative) of the jump trajectory. General rule: use
+ * positive values for targets mounted on the floor, and use negative
+ * values to target a point on the ceiling.
+ * movedir: if target is not set, this * speed * 10 is the velocity to be reached.
+ */
+void spawnfunc_trigger_push()
+{
+ SetMovedir ();
+
+ EXACTTRIGGER_INIT;
+
+ self.active = ACTIVE_ACTIVE;
+ self.use = trigger_push_use;
+ self.touch = trigger_push_touch;
+
+ // normal push setup
+ if (!self.speed)
+ self.speed = 1000;
+ self.movedir = self.movedir * self.speed * 10;
+
+ if (!self.noise)
+ self.noise = "misc/jumppad.wav";
+ precache_sound (self.noise);
+
+ // this must be called to spawn the teleport waypoints for bots
+ InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
+}
+
+
+float target_push_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
+
+ WriteByte(MSG_ENTITY, self.cnt);
+ WriteString(MSG_ENTITY, self.targetname);
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ return true;
+}
+
+void target_push_link()
+{
+ Net_LinkEntity(self, false, 0, target_push_send);
+ self.SendFlags |= 1; // update
+}
+
+void spawnfunc_target_push() { target_push_link(); }
+void spawnfunc_info_notnull() { target_push_link(); }
+void spawnfunc_target_position() { target_push_link(); }
+
+#endif
+
+#ifdef CSQC
+void ent_trigger_push()
+{
+ float sf = ReadByte();
+
+ if(sf & 1)
+ {
+ self.classname = "jumppad";
+ self.target = strzone(ReadString());
+ float mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
+ self.spawnflags = ReadInt24_t();
+ self.active = ReadByte();
+ self.warpzone_isboxy = ReadByte();
+ self.height = ReadByte();
+ self.scale = ReadByte();
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+ self.movedir_x = ReadCoord();
+ self.movedir_y = ReadCoord();
+ self.movedir_z = ReadCoord();
+ self.angles_x = ReadCoord();
+ self.angles_y = ReadCoord();
+ self.angles_z = ReadCoord();
+
+ self.solid = SOLID_TRIGGER;
+ self.draw = trigger_draw_generic;
+ self.trigger_touch = trigger_push_touch;
+ self.drawmask = MASK_ENGINE;
+ self.move_time = time;
+ trigger_push_findtarget();
+ }
+
+ if(sf & 2)
+ {
+ self.team = ReadByte();
+ self.active = ReadByte();
+ }
+}
+
+void ent_target_push()
+{
+ self.classname = "push_target";
+ self.cnt = ReadByte();
+ self.targetname = strzone(ReadString());
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.drawmask = MASK_ENGINE;
+}
+#endif
--- /dev/null
+#ifndef T_JUMPPADS_H
+#define T_JUMPPADS_H
+
+const float PUSH_ONCE = 1;
+const float PUSH_SILENT = 2;
+
+.float pushltime;
+.float istypefrag;
+.float height;
+
+const int NUM_JUMPPADSUSED = 3;
+.float jumppadcount;
+.entity jumppadsused[NUM_JUMPPADSUSED];
+
+float trigger_push_calculatevelocity_flighttime;
+
+#ifdef SVQC
+void() SUB_UseTargets;
+void trigger_push_use();
+#endif
+
+#ifdef CSQC
+void ent_trigger_push();
+
+void ent_target_push();
+#endif
+
+/*
+ trigger_push_calculatevelocity
+
+ Arguments:
+ org - origin of the object which is to be pushed
+ tgt - target entity (can be either a point or a model entity; if it is
+ the latter, its midpoint is used)
+ ht - jump height, measured from the higher one of org and tgt's midpoint
+
+ Returns: velocity for the jump
+ the global trigger_push_calculatevelocity_flighttime is set to the total
+ jump time
+ */
+
+vector trigger_push_calculatevelocity(vector org, entity tgt, float ht);
+
+void trigger_push_touch();
+
+.vector dest;
+void trigger_push_findtarget();
+
+/*
+ * ENTITY PARAMETERS:
+ *
+ * target: target of jump
+ * height: the absolute value is the height of the highest point of the jump
+ * trajectory above the higher one of the player and the target.
+ * the sign indicates whether the highest point is INSIDE (positive)
+ * or OUTSIDE (negative) of the jump trajectory. General rule: use
+ * positive values for targets mounted on the floor, and use negative
+ * values to target a point on the ceiling.
+ * movedir: if target is not set, this * speed * 10 is the velocity to be reached.
+ */
+#ifdef SVQC
+void spawnfunc_trigger_push();
+
+void spawnfunc_target_push();
+void spawnfunc_info_notnull();
+void spawnfunc_target_position();
+#endif
+#endif
--- /dev/null
+#ifdef SVQC
+float magicear_matched;
+float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
+string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
+{
+ float domatch, dotrigger, matchstart, l;
+ string s, msg;
+ entity oldself;
+ string savemessage;
+
+ magicear_matched = false;
+
+ dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
+ domatch = ((ear.spawnflags & 32) || dotrigger);
+
+ if (!domatch)
+ return msgin;
+
+ if (!msgin)
+ {
+ // we are in TUBA mode!
+ if (!(ear.spawnflags & 256))
+ return msgin;
+
+ if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir_x, !(ear.spawnflags & 512), ear.movedir_y, ear.movedir_z))
+ return msgin;
+
+ magicear_matched = true;
+
+ if(dotrigger)
+ {
+ oldself = self;
+ activator = source;
+ self = ear;
+ savemessage = self.message;
+ self.message = string_null;
+ SUB_UseTargets();
+ self.message = savemessage;
+ self = oldself;
+ }
+
+ if(ear.netname != "")
+ return ear.netname;
+
+ return msgin;
+ }
+
+ if(ear.spawnflags & 256) // ENOTUBA
+ return msgin;
+
+ if(privatesay)
+ {
+ if(ear.spawnflags & 4)
+ return msgin;
+ }
+ else
+ {
+ if(!teamsay)
+ if(ear.spawnflags & 1)
+ return msgin;
+ if(teamsay > 0)
+ if(ear.spawnflags & 2)
+ return msgin;
+ if(teamsay < 0)
+ if(ear.spawnflags & 8)
+ return msgin;
+ }
+
+ matchstart = -1;
+ l = strlen(ear.message);
+
+ if(ear.spawnflags & 128)
+ msg = msgin;
+ else
+ msg = strdecolorize(msgin);
+
+ if(substring(ear.message, 0, 1) == "*")
+ {
+ if(substring(ear.message, -1, 1) == "*")
+ {
+ // two wildcards
+ // as we need multi-replacement here...
+ s = substring(ear.message, 1, -2);
+ l -= 2;
+ if(strstrofs(msg, s, 0) >= 0)
+ matchstart = -2; // we use strreplace on s
+ }
+ else
+ {
+ // match at start
+ s = substring(ear.message, 1, -1);
+ l -= 1;
+ if(substring(msg, -l, l) == s)
+ matchstart = strlen(msg) - l;
+ }
+ }
+ else
+ {
+ if(substring(ear.message, -1, 1) == "*")
+ {
+ // match at end
+ s = substring(ear.message, 0, -2);
+ l -= 1;
+ if(substring(msg, 0, l) == s)
+ matchstart = 0;
+ }
+ else
+ {
+ // full match
+ s = ear.message;
+ if(msg == ear.message)
+ matchstart = 0;
+ }
+ }
+
+ if(matchstart == -1) // no match
+ return msgin;
+
+ magicear_matched = true;
+
+ if(dotrigger)
+ {
+ oldself = self;
+ activator = source;
+ self = ear;
+ savemessage = self.message;
+ self.message = string_null;
+ SUB_UseTargets();
+ self.message = savemessage;
+ self = oldself;
+ }
+
+ if(ear.spawnflags & 16)
+ {
+ return ear.netname;
+ }
+ else if(ear.netname != "")
+ {
+ if(matchstart < 0)
+ return strreplace(s, ear.netname, msg);
+ else
+ return strcat(
+ substring(msg, 0, matchstart),
+ ear.netname,
+ substring(msg, matchstart + l, -1)
+ );
+ }
+ else
+ return msgin;
+}
+
+entity magicears;
+string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
+{
+ entity ear;
+ string msgout;
+ for(ear = magicears; ear; ear = ear.enemy)
+ {
+ msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
+ if(!(ear.spawnflags & 64))
+ if(magicear_matched)
+ return msgout;
+ msgin = msgout;
+ }
+ return msgin;
+}
+
+void spawnfunc_trigger_magicear()
+{
+ self.enemy = magicears;
+ magicears = self;
+
+ // actually handled in "say" processing
+ // spawnflags:
+ // 1 = ignore say
+ // 2 = ignore teamsay
+ // 4 = ignore tell
+ // 8 = ignore tell to unknown player
+ // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
+ // 32 = perform the replacement even if outside the radius or dead
+ // 64 = continue replacing/triggering even if this one matched
+ // 128 = don't decolorize message before matching
+ // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
+ // 512 = tuba notes must be exact right pitch, no transposing
+ // message: either
+ // *pattern*
+ // or
+ // *pattern
+ // or
+ // pattern*
+ // or
+ // pattern
+ // netname:
+ // if set, replacement for the matched text
+ // radius:
+ // "hearing distance"
+ // target:
+ // what to trigger
+ // movedir:
+ // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
+
+ self.movedir_x -= 1; // map to tuba instrument numbers
+}
+#endif
--- /dev/null
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
+"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
+*/
+void monoflop_use()
+{
+ self.nextthink = time + self.wait;
+ self.enemy = activator;
+ if(self.state)
+ return;
+ self.state = 1;
+ SUB_UseTargets();
+}
+void monoflop_fixed_use()
+{
+ if(self.state)
+ return;
+ self.nextthink = time + self.wait;
+ self.state = 1;
+ self.enemy = activator;
+ SUB_UseTargets();
+}
+
+void monoflop_think()
+{
+ self.state = 0;
+ activator = self.enemy;
+ SUB_UseTargets();
+}
+
+void monoflop_reset()
+{
+ self.state = 0;
+ self.nextthink = 0;
+}
+
+void spawnfunc_trigger_monoflop()
+{
+ if(!self.wait)
+ self.wait = 1;
+ if(self.spawnflags & 1)
+ self.use = monoflop_fixed_use;
+ else
+ self.use = monoflop_use;
+ self.think = monoflop_think;
+ self.state = 0;
+ self.reset = monoflop_reset;
+}
+#endif
--- /dev/null
+// NOTE: also contains trigger_once at bottom
+
+#ifdef SVQC
+// the wait time has passed, so set back up for another activation
+void multi_wait()
+{
+ if (self.max_health)
+ {
+ self.health = self.max_health;
+ self.takedamage = DAMAGE_YES;
+ self.solid = SOLID_BBOX;
+ }
+}
+
+
+// the trigger was just touched/killed/used
+// self.enemy should be set to the activator so it can be held through a delay
+// so wait for the delay time before firing
+void multi_trigger()
+{
+ if (self.nextthink > time)
+ {
+ return; // allready been triggered
+ }
+
+ if (self.classname == "trigger_secret")
+ {
+ if (!IS_PLAYER(self.enemy))
+ return;
+ found_secrets = found_secrets + 1;
+ WriteByte (MSG_ALL, SVC_FOUNDSECRET);
+ }
+
+ if (self.noise)
+ sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
+
+// don't trigger again until reset
+ self.takedamage = DAMAGE_NO;
+
+ activator = self.enemy;
+ other = self.goalentity;
+ SUB_UseTargets();
+
+ if (self.wait > 0)
+ {
+ self.think = multi_wait;
+ self.nextthink = time + self.wait;
+ }
+ else if (self.wait == 0)
+ {
+ multi_wait(); // waiting finished
+ }
+ else
+ { // we can't just remove (self) here, because this is a touch function
+ // called wheil C code is looping through area links...
+ self.touch = func_null;
+ }
+}
+
+void multi_use()
+{
+ self.goalentity = other;
+ self.enemy = activator;
+ multi_trigger();
+}
+
+void multi_touch()
+{
+ if(!(self.spawnflags & 2))
+ if(!other.iscreature)
+ return;
+
+ if(self.team)
+ if(((self.spawnflags & 4) == 0) == (self.team != other.team))
+ return;
+
+// if the trigger has an angles field, check player's facing direction
+ if (self.movedir != '0 0 0')
+ {
+ makevectors (other.angles);
+ if (v_forward * self.movedir < 0)
+ return; // not facing the right way
+ }
+
+ EXACTTRIGGER_TOUCH;
+
+ self.enemy = other;
+ self.goalentity = other;
+ multi_trigger ();
+}
+
+void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
+{
+ if (!self.takedamage)
+ return;
+ if(self.spawnflags & DOOR_NOSPLASH)
+ if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
+ return;
+ self.health = self.health - damage;
+ if (self.health <= 0)
+ {
+ self.enemy = attacker;
+ self.goalentity = inflictor;
+ multi_trigger();
+ }
+}
+
+void multi_reset()
+{
+ if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
+ self.touch = multi_touch;
+ if (self.max_health)
+ {
+ self.health = self.max_health;
+ self.takedamage = DAMAGE_YES;
+ self.solid = SOLID_BBOX;
+ }
+ self.think = func_null;
+ self.nextthink = 0;
+ self.team = self.team_saved;
+}
+
+/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
+Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
+If "delay" is set, the trigger waits some time after activating before firing.
+"wait" : Seconds between triggerings. (.2 default)
+If notouch is set, the trigger is only fired by other entities, not by touching.
+NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
+sounds
+1) secret
+2) beep beep
+3) large switch
+4)
+set "message" to text string
+*/
+void spawnfunc_trigger_multiple()
+{
+ self.reset = multi_reset;
+ if (self.sounds == 1)
+ {
+ precache_sound ("misc/secret.wav");
+ self.noise = "misc/secret.wav";
+ }
+ else if (self.sounds == 2)
+ {
+ precache_sound ("misc/talk.wav");
+ self.noise = "misc/talk.wav";
+ }
+ else if (self.sounds == 3)
+ {
+ precache_sound ("misc/trigger1.wav");
+ self.noise = "misc/trigger1.wav";
+ }
+
+ if (!self.wait)
+ self.wait = 0.2;
+ else if(self.wait < -1)
+ self.wait = 0;
+ self.use = multi_use;
+
+ EXACTTRIGGER_INIT;
+
+ self.team_saved = self.team;
+
+ if (self.health)
+ {
+ if (self.spawnflags & SPAWNFLAG_NOTOUCH)
+ objerror ("health and notouch don't make sense\n");
+ self.max_health = self.health;
+ self.event_damage = multi_eventdamage;
+ self.takedamage = DAMAGE_YES;
+ self.solid = SOLID_BBOX;
+ setorigin (self, self.origin); // make sure it links into the world
+ }
+ else
+ {
+ if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
+ {
+ self.touch = multi_touch;
+ setorigin (self, self.origin); // make sure it links into the world
+ }
+ }
+}
+
+
+/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
+Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
+"targetname". If "health" is set, the trigger must be killed to activate.
+If notouch is set, the trigger is only fired by other entities, not by touching.
+if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
+if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
+sounds
+1) secret
+2) beep beep
+3) large switch
+4)
+set "message" to text string
+*/
+void spawnfunc_trigger_once()
+{
+ self.wait = -1;
+ spawnfunc_trigger_multiple();
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void multi_trigger();
+void multi_reset();
+#endif
--- /dev/null
+#ifdef SVQC
+void multivibrator_send()
+{
+ float newstate;
+ float cyclestart;
+
+ cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
+
+ newstate = (time < cyclestart + self.wait);
+
+ activator = self;
+ if(self.state != newstate)
+ SUB_UseTargets();
+ self.state = newstate;
+
+ if(self.state)
+ self.nextthink = cyclestart + self.wait + 0.01;
+ else
+ self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
+}
+
+void multivibrator_toggle()
+{
+ if(self.nextthink == 0)
+ {
+ multivibrator_send();
+ }
+ else
+ {
+ if(self.state)
+ {
+ SUB_UseTargets();
+ self.state = 0;
+ }
+ self.nextthink = 0;
+ }
+}
+
+void multivibrator_reset()
+{
+ if(!(self.spawnflags & 1))
+ self.nextthink = 0; // wait for a trigger event
+ else
+ self.nextthink = max(1, time);
+}
+
+/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
+"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
+-------- KEYS --------
+target: trigger all entities with this targetname when it goes off
+targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
+phase: offset of the timing
+wait: "on" cycle time (default: 1)
+respawntime: "off" cycle time (default: same as wait)
+-------- SPAWNFLAGS --------
+START_ON: assume it is already turned on (when targeted)
+*/
+void spawnfunc_trigger_multivibrator()
+{
+ if(!self.wait)
+ self.wait = 1;
+ if(!self.respawntime)
+ self.respawntime = self.wait;
+
+ self.state = 0;
+ self.use = multivibrator_toggle;
+ self.think = multivibrator_send;
+ self.nextthink = max(1, time);
+
+ IFTARGETED
+ multivibrator_reset();
+}
+#endif
--- /dev/null
+#ifdef SVQC
+/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
+This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
+*/
+void spawnfunc_trigger_relay()
+{
+ self.use = SUB_UseTargets;
+ self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void relay_activators_use()
+{
+ entity trg, os;
+
+ os = self;
+
+ for(trg = world; (trg = find(trg, targetname, os.target)); )
+ {
+ self = trg;
+ if (trg.setactive)
+ trg.setactive(os.cnt);
+ else
+ {
+ //bprint("Not using setactive\n");
+ if(os.cnt == ACTIVE_TOGGLE)
+ if(trg.active == ACTIVE_ACTIVE)
+ trg.active = ACTIVE_NOT;
+ else
+ trg.active = ACTIVE_ACTIVE;
+ else
+ trg.active = os.cnt;
+ }
+ }
+ self = os;
+}
+
+void spawnfunc_relay_activate()
+{
+ self.cnt = ACTIVE_ACTIVE;
+ self.use = relay_activators_use;
+}
+
+void spawnfunc_relay_deactivate()
+{
+ self.cnt = ACTIVE_NOT;
+ self.use = relay_activators_use;
+}
+
+void spawnfunc_relay_activatetoggle()
+{
+ self.cnt = ACTIVE_TOGGLE;
+ self.use = relay_activators_use;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void trigger_relay_if_use()
+{
+ float n;
+ n = self.count;
+
+ // TODO make this generic AND faster than nextent()ing through all, if somehow possible
+ n = (cvar_string(self.netname) == cvar_string(self.message));
+ if(self.spawnflags & 1)
+ n = !n;
+
+ if(n)
+ SUB_UseTargets();
+}
+
+void spawnfunc_trigger_relay_if()
+{
+ self.use = trigger_relay_if_use;
+}
+#endif
--- /dev/null
+#ifdef SVQC
+void trigger_relay_teamcheck_use()
+{
+ if(activator.team)
+ {
+ if(self.spawnflags & 2)
+ {
+ if(DIFF_TEAM(activator, self))
+ SUB_UseTargets();
+ }
+ else
+ {
+ if(SAME_TEAM(activator, self))
+ SUB_UseTargets();
+ }
+ }
+ else
+ {
+ if(self.spawnflags & 1)
+ SUB_UseTargets();
+ }
+}
+
+void trigger_relay_teamcheck_reset()
+{
+ self.team = self.team_saved;
+}
+
+void spawnfunc_trigger_relay_teamcheck()
+{
+ self.team_saved = self.team;
+ self.use = trigger_relay_teamcheck_use;
+ self.reset = trigger_relay_teamcheck_reset;
+}
+#endif
--- /dev/null
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+ #include "../../..//dpdefs/progsdefs.qh"
+ #include "../../util.qh"
+ #include "../../../server/defs.qh"
+ #include "secret.qh"
+#endif
+
+#ifdef SVQC
+
+void secrets_setstatus() {
+ self.stat_secrets_total = secrets_total;
+ self.stat_secrets_found = secrets_found;
+}
+
+/**
+ * A secret has been found (maybe :P)
+ */
+void trigger_secret_touch() {
+ // only a player can trigger this
+ if (!IS_PLAYER(other))
+ return;
+
+ // update secrets found counter
+ secrets_found += 1;
+ //print("Secret found: ", ftos(secret_counter.cnt), "/");
+ //print(ftos(secret_counter.count), "\n");
+
+ // centerprint message (multi_touch() doesn't always call centerprint())
+ centerprint(other, self.message);
+ self.message = "";
+
+ // handle normal trigger features
+ multi_touch();
+ remove(self);
+}
+
+/*QUAKED trigger_secret (.5 .5 .5) ?
+Variable sized secret trigger. Can be targeted at one or more entities.
+Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found.
+-------- KEYS --------
+sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1)
+noise: path to sound file, if you want to play something else
+target: trigger all entities with this targetname when triggered
+message: print this message to the player who activated the trigger instead of the standard 'You found a secret!'
+killtarget: remove all entities with this targetname when triggered
+-------- NOTES --------
+You should create a common/trigger textured brush covering the entrance to a secret room/area.
+Trigger secret can only be trigger by a player's touch and can not be a target itself.
+*/
+void spawnfunc_trigger_secret() {
+ // FIXME: should it be disabled in most modes?
+
+ // update secrets count
+ secrets_total += 1;
+
+ // add default message
+ if (self.message == "")
+ self.message = "You found a secret!";
+
+ // set default sound
+ if (self.noise == "")
+ if (!self.sounds)
+ self.sounds = 1; // misc/secret.wav
+
+ // this entity can't be a target itself!!!!
+ self.targetname = "";
+
+ // you can't just shoot a room to find it, can you?
+ self.health = 0;
+
+ // a secret can not be delayed
+ self.delay = 0;
+
+ // convert this trigger to trigger_once
+ self.classname = "trigger_once";
+ spawnfunc_trigger_once();
+
+ // take over the touch() function, so we can mark secret as found
+ self.touch = trigger_secret_touch;
+ // ignore triggering;
+ self.use = func_null;
+}
+#endif
--- /dev/null
+#ifndef SECRET_H
+#define SECRET_H
+#ifdef SVQC
+
+/**
+ * Total number of secrets on the map.
+ */
+float secrets_total;
+
+/**
+ * Total numbe of secrets found on the map.
+ */
+float secrets_found;
+
+
+.float stat_secrets_total;
+.float stat_secrets_found;
+
+/**
+ * update secrets status.
+ */
+void secrets_setstatus();
+#endif
+#endif
--- /dev/null
+#if defined(CSQC)
+#elif defined(MENUQC)
+#elif defined(SVQC)
+ #include "../../../dpdefs/progsdefs.qh"
+ #include "../../../warpzonelib/util_server.qh"
+ #include "../../weapons/weapons.qh"
+ #include "../../../server/defs.qh"
+ #include "../../deathtypes.qh"
+#endif
+
+/*
+* t_swamp.c
+* Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+
+* Author tZork (Jakob MG)
+* jakob@games43.se
+* 2005 11 29
+*/
+
+.float swamp_interval; //Hurt players in swamp with this interval
+.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.entity swampslug;
+
+#ifdef SVQC
+void spawnfunc_trigger_swamp(void);
+#endif
+void swamp_touch(void);
+void swampslug_think();
+
+
+/*
+* Uses a entity calld swampslug to handle players in the swamp
+* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp
+* attaches a new "swampslug" to the player. As long as the plyer is inside
+* the swamp the swamp gives the slug new health. But the slug slowly kills itself
+* so when the player goes outside the swamp, it dies and releases the player from the
+* swamps curses (dmg/slowdown)
+*
+* I do it this way becuz there is no "untouch" event.
+*/
+void swampslug_think(void)
+{
+ //Slowly kill the slug
+ self.health = self.health - 1;
+
+ //Slug dead? then remove curses.
+ if(self.health <= 0)
+ {
+ self.owner.in_swamp = 0;
+ remove(self);
+ //centerprint(self.owner,"Killing slug...\n");
+ return;
+ }
+
+ // Slug still alive, so we are still in the swamp
+ // Or we have exited it very recently.
+ // Do the damage and renew the timer.
+#ifdef SVQC
+ Damage (self.owner, self, self, self.dmg, DEATH_SWAMP, other.origin, '0 0 0');
+#endif
+
+ self.nextthink = time + self.swamp_interval;
+}
+
+void swamp_touch(void)
+{
+ // If whatever thats touching the swamp is not a player
+ // or if its a dead player, just dont care abt it.
+ if(!IS_PLAYER(other) || PHYS_DEAD(other))
+ return;
+
+ EXACTTRIGGER_TOUCH;
+
+ // Chech if player alredy got a swampslug.
+ if(other.in_swamp != 1)
+ {
+ // If not attach one.
+ //centerprint(other,"Entering swamp!\n");
+ other.swampslug = spawn();
+ other.swampslug.health = 2;
+ other.swampslug.think = swampslug_think;
+ other.swampslug.nextthink = time;
+ other.swampslug.owner = other;
+ other.swampslug.dmg = self.dmg;
+ other.swampslug.swamp_interval = self.swamp_interval;
+ other.swamp_slowdown = self.swamp_slowdown;
+ other.in_swamp = 1;
+ return;
+ }
+
+ //other.in_swamp = 1;
+
+ //Revitalize players swampslug
+ other.swampslug.health = 2;
+}
+
+#ifdef SVQC
+float swamp_send(entity to, float sf)
+{
+ WriteByte(MSG_ENTITY, ENT_CLIENT_LADDER);
+
+ WriteByte(MSG_ENTITY, self.warpzone_isboxy);
+ WriteByte(MSG_ENTITY, self.scale);
+ WriteByte(MSG_ENTITY, self.dmg); // can probably get away with using a single byte here
+ WriteByte(MSG_ENTITY, self.swamp_slowdown);
+ WriteByte(MSG_ENTITY, self.swamp_interval);
+ WriteCoord(MSG_ENTITY, self.origin_x);
+ WriteCoord(MSG_ENTITY, self.origin_y);
+ WriteCoord(MSG_ENTITY, self.origin_z);
+
+ WriteCoord(MSG_ENTITY, self.mins_x);
+ WriteCoord(MSG_ENTITY, self.mins_y);
+ WriteCoord(MSG_ENTITY, self.mins_z);
+ WriteCoord(MSG_ENTITY, self.maxs_x);
+ WriteCoord(MSG_ENTITY, self.maxs_y);
+ WriteCoord(MSG_ENTITY, self.maxs_z);
+
+ WriteCoord(MSG_ENTITY, self.movedir_x);
+ WriteCoord(MSG_ENTITY, self.movedir_y);
+ WriteCoord(MSG_ENTITY, self.movedir_z);
+
+ WriteAngle(MSG_ENTITY, self.angles_x);
+ WriteAngle(MSG_ENTITY, self.angles_y);
+ WriteAngle(MSG_ENTITY, self.angles_z);
+
+ return true;
+}
+
+void swamp_link()
+{
+ Net_LinkEntity(self, false, 0, func_ladder_send);
+}
+
+/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ?
+Players gettin into the swamp will
+get slowd down and damaged
+*/
+void spawnfunc_trigger_swamp(void)
+{
+ // Init stuff
+ EXACTTRIGGER_INIT;
+ self.touch = swamp_touch;
+
+ // Setup default keys, if missing
+ if(self.dmg <= 0)
+ self.dmg = 5;
+ if(self.swamp_interval <= 0)
+ self.swamp_interval = 1;
+ if(self.swamp_slowdown <= 0)
+ self.swamp_slowdown = 0.5;
+
+ swamp_link();
+}
+
+#elif defined(CSQC)
+
+void ent_swamp()
+{
+ self.warpzone_isboxy = ReadByte();
+ self.scale = ReadByte();
+ self.dmg = ReadByte();
+ self.swamp_slowdown = ReadByte();
+ self.swamp_interval = ReadByte();
+
+ self.origin_x = ReadCoord();
+ self.origin_y = ReadCoord();
+ self.origin_z = ReadCoord();
+ setorigin(self, self.origin);
+
+ self.mins_x = ReadCoord();
+ self.mins_y = ReadCoord();
+ self.mins_z = ReadCoord();
+ self.maxs_x = ReadCoord();
+ self.maxs_y = ReadCoord();
+ self.maxs_z = ReadCoord();
+ setsize(self, self.mins, self.maxs);
+
+ self.movedir_x = ReadCoord();
+ self.movedir_y = ReadCoord();
+ self.movedir_z = ReadCoord();
+
+ self.angles_x = ReadAngle();
+ self.angles_y = ReadAngle();
+ self.angles_z = ReadAngle();
+
+ self.classname = "trigger_swamp";
+ self.solid = SOLID_TRIGGER;
+ self.draw = trigger_draw_generic;
+ self.trigger_touch = swamp_touch;
+ self.drawmask = MASK_NORMAL;
+ self.move_time = time;
+}
+#endif
--- /dev/null
+#ifndef TRIGGER_SWAMP_H
+#define TRIGGER_SWAMP_H
+
+.float swamp_interval; //Hurt players in swamp with this interval
+.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
+.entity swampslug;
+
+.float in_swamp; // bool
+.entity swampslug; // Uses this to release from swamp ("untouch" fix)
+
+#ifdef CSQC
+void ent_swamp();
+#endif
+
+#endif
--- /dev/null
+void SUB_DontUseTargets() { }
+
+void() SUB_UseTargets;
+
+void DelayThink()
+{
+ activator = self.enemy;
+ SUB_UseTargets ();
+ remove(self);
+}
+
+void FixSize(entity e)
+{
+ e.mins_x = rint(e.mins_x);
+ e.mins_y = rint(e.mins_y);
+ e.mins_z = rint(e.mins_z);
+
+ e.maxs_x = rint(e.maxs_x);
+ e.maxs_y = rint(e.maxs_y);
+ e.maxs_z = rint(e.maxs_z);
+}
+
+/*
+==============================
+SUB_UseTargets
+
+the global "activator" should be set to the entity that initiated the firing.
+
+If self.delay is set, a DelayedUse entity will be created that will actually
+do the SUB_UseTargets after that many seconds have passed.
+
+Centerprints any self.message to the activator.
+
+Removes all entities with a targetname that match self.killtarget,
+and removes them, so some events can remove other triggers.
+
+Search for (string)targetname in all entities that
+match (string)self.target and call their .use function
+
+==============================
+*/
+void SUB_UseTargets()
+{
+ entity t, stemp, otemp, act;
+ string s;
+ float i;
+
+//
+// check for a delay
+//
+ if (self.delay)
+ {
+ // create a temp object to fire at a later time
+ t = spawn();
+ t.classname = "DelayedUse";
+ t.nextthink = time + self.delay;
+ t.think = DelayThink;
+ t.enemy = activator;
+ t.message = self.message;
+ t.killtarget = self.killtarget;
+ t.target = self.target;
+ t.target2 = self.target2;
+ t.target3 = self.target3;
+ t.target4 = self.target4;
+ return;
+ }
+
+
+//
+// print the message
+//
+#ifdef SVQC
+ if(self)
+ if(IS_PLAYER(activator) && self.message != "")
+ if(IS_REAL_CLIENT(activator))
+ {
+ centerprint(activator, self.message);
+ if (self.noise == "")
+ play2(activator, "misc/talk.wav");
+ }
+
+//
+// kill the killtagets
+//
+ s = self.killtarget;
+ if (s != "")
+ {
+ for(t = world; (t = find(t, targetname, s)); )
+ remove(t);
+ }
+#endif
+
+//
+// fire targets
+//
+ act = activator;
+ stemp = self;
+ otemp = other;
+
+ if(stemp.target_random)
+ RandomSelection_Init();
+
+ for(i = 0; i < 4; ++i)
+ {
+ switch(i)
+ {
+ default:
+ case 0: s = stemp.target; break;
+ case 1: s = stemp.target2; break;
+ case 2: s = stemp.target3; break;
+ case 3: s = stemp.target4; break;
+ }
+ if (s != "")
+ {
+ for(t = world; (t = find(t, targetname, s)); )
+ if(t.use)
+ {
+ if(stemp.target_random)
+ {
+ RandomSelection_Add(t, 0, string_null, 1, 0);
+ }
+ else
+ {
+ self = t;
+ other = stemp;
+ activator = act;
+ self.use();
+ }
+ }
+ }
+ }
+
+ if(stemp.target_random && RandomSelection_chosen_ent)
+ {
+ self = RandomSelection_chosen_ent;
+ other = stemp;
+ activator = act;
+ self.use();
+ }
+
+ activator = act;
+ self = stemp;
+ other = otemp;
+}
+
+#ifdef CSQC
+void trigger_touch_generic(void() touchfunc)
+{
+ entity e;
+ for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
+ if(e.isplayermodel)
+ {
+ vector emin = e.absmin, emax = e.absmax;
+ if(self.solid == SOLID_BSP)
+ {
+ emin -= '1 1 1';
+ emax += '1 1 1';
+ }
+ if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
+ if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
+ {
+ other = e;
+ touchfunc();
+ }
+ }
+}
+void trigger_draw_generic()
+{
+ float dt = time - self.move_time;
+ self.move_time = time;
+ if(dt <= 0) { return; }
+
+ if(self.trigger_touch) { trigger_touch_generic(self.trigger_touch); }
+}
+#endif
--- /dev/null
+#ifndef TRIGGERS_H
+#define TRIGGERS_H
+
+const float SF_TRIGGER_INIT = 1;
+const float SF_TRIGGER_UPDATE = 2;
+const float SF_TRIGGER_RESET = 4;
+
+const float SPAWNFLAG_NOMESSAGE = 1;
+const float SPAWNFLAG_NOTOUCH = 1;
+
+.void() trigger_touch;
+
+.float height;
+
+.float nottargeted;
+#define IFTARGETED if(!self.nottargeted && self.targetname != "")
+
+.string bgmscript;
+.float bgmscriptattack;
+.float bgmscriptdecay;
+.float bgmscriptsustain;
+.float bgmscriptrelease;
+
+.float lip;
+
+// used elsewhere (will fix)
+#ifdef SVQC
+void spawnfunc_trigger_once();
+string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin);
+
+void target_voicescript_next(entity pl);
+void target_voicescript_clear(entity pl);
+#endif
+
+.float volume, atten;
+
+.vector dest;
+
+#ifdef CSQC
+float WarpZoneLib_ExactTrigger_Touch();
+#define EXACTTRIGGER_TOUCH if(WarpZoneLib_ExactTrigger_Touch()) return
+
+.float active;
+.string target;
+.string targetname;
+
+const int ACTIVE_NOT = 0;
+const int ACTIVE_ACTIVE = 1;
+const int ACTIVE_IDLE = 2;
+const int ACTIVE_BUSY = 2;
+const int ACTIVE_TOGGLE = 3;
+#endif
+
+#endif
#endif
#else
#ifdef SVQC
+#include "../triggers/trigger/jumppads.qh"
+
void spawnfunc_weapon_porto(void) { weapon_defaultspawnfunc(WEP_PORTO); }
void W_Porto_Success(void)
#include "role_onslaught.qc"
#include "role_keyhunt.qc"
#include "roles.qc"
+#include "../../../common/triggers/trigger/jumppads.qh"
void havocbot_ai()
{
#include "autocvars.qh"
#include "defs.qh"
#include "../common/deathtypes.qh"
+ #include "../common/triggers/subs.qh"
#include "mutators/mutators_include.qh"
#include "../csqcmodellib/sv_model.qh"
#endif
#include "portals.qh"
#include "teamplay.qh"
#include "playerdemo.qh"
-#include "secret.qh"
#include "bot/bot.qh"
#include "bot/navigation.qh"
#include "../common/net_notice.qh"
#include "../common/physics.qh"
+#include "../common/triggers/subs.qh"
+#include "../common/triggers/triggers.qh"
+#include "../common/triggers/trigger/secret.qh"
+
#include "../common/monsters/sv_monsters.qh"
#include "../warpzonelib/server.qh"
#include "cl_player.qh"
-#include "g_triggers.qh"
#include "g_violence.qh"
#include "miscfunctions.qh"
const int ASSAULT_VALUE_INACTIVE = 1000;
-const int DOOR_NOSPLASH = 256; // generic anti-splashdamage spawnflag
#endif
float sv_clones;
float sv_foginterval;
-entity activator;
-
float player_count;
float currentbots;
float bots_would_leave;
//.string map;
//.float worldtype;
-.float delay;
-.float wait;
-.float lip;
-//.float light_lev;
-.float speed;
-//.float style;
-//.float skill;
-.float sounds;
-.string platmovetype;
-.float platmovetype_start, platmovetype_end;
-
-.string killtarget;
-
-.vector pos1, pos2;
-.vector mangle;
.float pain_finished; //Added by Supajoe
.float pain_frame; //"
.float invincible_finished;
.float superweapons_finished;
-.vector finaldest, finalangle; //plat.qc stuff
-.void() think1;
-.float state;
-.float t_length, t_width;
-
-.vector destvec; // for rain
-.vector destvec2; // for train
-.float cnt; // for rain
+.float cnt; // used in too many places
.float count;
//.float cnt2;
.float fade_time;
.float fade_rate;
-// player animation state
-.float animstate_startframe;
-.float animstate_numframes;
-.float animstate_framerate;
-.float animstate_starttime;
-.float animstate_endtime;
-.float animstate_override;
-.float animstate_looping;
-
// weapon animation vectors:
.vector anim_fire1;
.vector anim_fire2;
void weapon_defaultspawnfunc(float wpn);
-.vector dest1, dest2;
-
float gameover;
float intermission_running;
float intermission_exittime;
float alreadychangedlevel;
-// Keys player is holding
-.float itemkeys;
-// message delay for func_door locked by keys and key locks
-// this field is used on player entities
-.float key_door_messagetime;
-
-
.float version;
-//swamp
-.float in_swamp; // bool
-.entity swampslug; // Uses this to release from swamp ("untouch" fix)
-
// footstep interval
.float nextstep;
.float version_nagtime;
-const int NUM_JUMPPADSUSED = 3;
-.float jumppadcount;
-.entity jumppadsused[NUM_JUMPPADSUSED];
-
string gamemode_name;
float startitem_failed;
// may be useful to all weapons
.float bulletcounter;
-void target_voicescript_next(entity pl);
-void target_voicescript_clear(entity pl);
-
-.string target2;
-.string target3;
-.string target4;
-.string curvetarget;
-.float target_random;
-.float trigger_reverse;
-
// Nexball
.entity ballcarried; // Also used for keepaway
.float metertime;
+++ /dev/null
-#include "weapons/common.qh"
-
-.entity sprite;
-
-.float dmg;
-.float dmg_edge;
-.float dmg_radius;
-.float dmg_force;
-.float debrismovetype;
-.float debrissolid;
-.vector debrisvelocity;
-.vector debrisvelocityjitter;
-.vector debrisavelocityjitter;
-.float debristime;
-.float debristimejitter;
-.float debrisfadetime;
-.float debrisdamageforcescale;
-.float debrisskin;
-
-.string mdl_dead; // or "" to hide when broken
-.string debris; // space separated list of debris models
-// other fields:
-// mdl = particle effect name
-// count = particle effect multiplier
-// targetname = target to trigger to unbreak the model
-// target = targets to trigger when broken
-// health = amount of damage it can take
-// spawnflags:
-// 1 = start disabled (needs to be triggered to activate)
-// 2 = indicate damage
-// notes:
-// for mdl_dead to work, origin must be set (using a common/origin brush).
-// Otherwise mdl_dead will be displayed at the map origin, and nobody would
-// want that!
-
-void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force);
-
-//
-// func_breakable
-// - basically func_assault_destructible for general gameplay use
-//
-void LaunchDebris (string debrisname, vector force)
-{
- entity dbr = spawn();
- setorigin(dbr, self.absmin
- + '1 0 0' * random() * (self.absmax.x - self.absmin.x)
- + '0 1 0' * random() * (self.absmax.y - self.absmin.y)
- + '0 0 1' * random() * (self.absmax.z - self.absmin.z));
- setmodel (dbr, debrisname );
- dbr.skin = self.debrisskin;
- dbr.colormap = self.colormap; // inherit team colors
- dbr.owner = self; // do not be affected by our own explosion
- dbr.movetype = self.debrismovetype;
- dbr.solid = self.debrissolid;
- if(dbr.solid != SOLID_BSP) // SOLID_BSP has exact collision, MAYBE this works? TODO check this out
- setsize(dbr, '0 0 0', '0 0 0'); // needed for performance, until engine can deal better with it
- dbr.velocity_x = self.debrisvelocity.x + self.debrisvelocityjitter.x * crandom();
- dbr.velocity_y = self.debrisvelocity.y + self.debrisvelocityjitter.y * crandom();
- dbr.velocity_z = self.debrisvelocity.z + self.debrisvelocityjitter.z * crandom();
- self.velocity = self.velocity + force * self.debrisdamageforcescale;
- dbr.avelocity_x = random()*self.debrisavelocityjitter.x;
- dbr.avelocity_y = random()*self.debrisavelocityjitter.y;
- dbr.avelocity_z = random()*self.debrisavelocityjitter.z;
- dbr.damageforcescale = self.debrisdamageforcescale;
- if(dbr.damageforcescale)
- dbr.takedamage = DAMAGE_YES;
- SUB_SetFade(dbr, time + self.debristime + crandom() * self.debristimejitter, self.debrisfadetime);
-}
-
-void func_breakable_colormod()
-{
- float h;
- if (!(self.spawnflags & 2))
- return;
- h = self.health / self.max_health;
- if(h < 0.25)
- self.colormod = '1 0 0';
- else if(h <= 0.75)
- self.colormod = '1 0 0' + '0 1 0' * (2 * h - 0.5);
- else
- self.colormod = '1 1 1';
-
- CSQCMODEL_AUTOUPDATE();
-}
-
-void func_breakable_look_destroyed()
-{
- float floorZ;
-
- if(self.solid == SOLID_BSP) // in case a misc_follow moved me, save the current origin first
- self.dropped_origin = self.origin;
-
- if(self.mdl_dead == "")
- self.model = "";
- else {
- if (self.origin == '0 0 0') { // probably no origin brush, so don't spawn in the middle of the map..
- floorZ = self.absmin.z;
- setorigin(self,((self.absmax+self.absmin)*.5));
- self.origin_z = floorZ;
- }
- setmodel(self, self.mdl_dead);
- }
-
- self.solid = SOLID_NOT;
-}
-
-void func_breakable_look_restore()
-{
- setmodel(self, self.mdl);
- if(self.mdl_dead != "") // only do this if we use mdl_dead, to behave better with misc_follow
- setorigin(self, self.dropped_origin);
- self.solid = SOLID_BSP;
-}
-
-void func_breakable_behave_destroyed()
-{
- self.health = self.max_health;
- self.takedamage = DAMAGE_NO;
- self.bot_attack = false;
- self.event_damage = func_null;
- self.state = 1;
- func_breakable_colormod();
-}
-
-void func_breakable_behave_restore()
-{
- self.health = self.max_health;
- if(self.sprite)
- {
- WaypointSprite_UpdateMaxHealth(self.sprite, self.max_health);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
- }
- self.takedamage = DAMAGE_AIM;
- self.bot_attack = true;
- self.event_damage = func_breakable_damage;
- self.state = 0;
- self.nextthink = 0; // cancel auto respawn
- func_breakable_colormod();
-}
-
-void func_breakable_destroyed()
-{
- func_breakable_look_destroyed();
- func_breakable_behave_destroyed();
-
- CSQCMODEL_AUTOUPDATE();
-}
-
-void func_breakable_restore()
-{
- func_breakable_look_restore();
- func_breakable_behave_restore();
-
- CSQCMODEL_AUTOUPDATE();
-}
-
-vector debrisforce; // global, set before calling this
-void func_breakable_destroy() {
- float n, i;
- string oldmsg;
-
- activator = self.owner;
- self.owner = world; // set by W_PrepareExplosionByDamage
-
- // now throw around the debris
- n = tokenize_console(self.debris);
- for(i = 0; i < n; ++i)
- LaunchDebris(argv(i), debrisforce);
-
- func_breakable_destroyed();
-
- if(self.noise)
- sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
- if(self.dmg)
- RadiusDamage(self, activator, self.dmg, self.dmg_edge, self.dmg_radius, self, world, self.dmg_force, DEATH_HURTTRIGGER, world);
-
- if(self.cnt)
- pointparticles(self.cnt, self.absmin * 0.5 + self.absmax * 0.5, '0 0 0', self.count);
-
- if(self.respawntime)
- {
- self.think = func_breakable_restore;
- self.nextthink = time + self.respawntime + crandom() * self.respawntimejitter;
- }
-
- oldmsg = self.message;
- self.message = "";
- SUB_UseTargets();
- self.message = oldmsg;
-}
-
-void func_breakable_damage(entity inflictor, entity attacker, float damage, int deathtype, vector hitloc, vector force)
-{
- if(self.state == 1)
- return;
- if(self.spawnflags & DOOR_NOSPLASH)
- if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
- return;
- if(self.team)
- if(attacker.team == self.team)
- return;
- self.health = self.health - damage;
- if(self.sprite)
- {
- WaypointSprite_Ping(self.sprite);
- WaypointSprite_UpdateHealth(self.sprite, self.health);
- }
- func_breakable_colormod();
-
- if(self.health <= 0)
- {
- debrisforce = force;
- W_PrepareExplosionByDamage(attacker, func_breakable_destroy);
- }
-}
-
-void func_breakable_reset()
-{
- self.team = self.team_saved;
- func_breakable_look_restore();
- if(self.spawnflags & 1)
- func_breakable_behave_destroyed();
- else
- func_breakable_behave_restore();
-
- CSQCMODEL_AUTOUPDATE();
-}
-
-// destructible walls that can be used to trigger target_objective_decrease
-void spawnfunc_func_breakable() {
- float n, i;
- if(!self.health)
- self.health = 100;
- self.max_health = self.health;
-
- // yes, I know, MOVETYPE_NONE is not available here, not that one would want it here anyway
- if(!self.debrismovetype) self.debrismovetype = MOVETYPE_BOUNCE;
- if(!self.debrissolid) self.debrissolid = SOLID_NOT;
- if(self.debrisvelocity == '0 0 0') self.debrisvelocity = '0 0 140';
- if(self.debrisvelocityjitter == '0 0 0') self.debrisvelocityjitter = '70 70 70';
- if(self.debrisavelocityjitter == '0 0 0') self.debrisavelocityjitter = '600 600 600';
- if(!self.debristime) self.debristime = 3.5;
- if(!self.debristimejitter) self.debristime = 2.5;
-
- if(self.mdl != "")
- self.cnt = particleeffectnum(self.mdl);
- if(self.count == 0)
- self.count = 1;
-
- if(self.message == "")
- self.message = "got too close to an explosion";
- if(self.message2 == "")
- self.message2 = "was pushed into an explosion by";
- if(!self.dmg_radius)
- self.dmg_radius = 150;
- if(!self.dmg_force)
- self.dmg_force = 200;
-
- self.mdl = self.model;
- SetBrushEntityModel();
-
- self.use = func_breakable_restore;
-
- // precache all the models
- if (self.mdl_dead)
- precache_model(self.mdl_dead);
- n = tokenize_console(self.debris);
- for(i = 0; i < n; ++i)
- precache_model(argv(i));
- if(self.noise)
- precache_sound(self.noise);
-
- self.team_saved = self.team;
- self.dropped_origin = self.origin;
-
- self.reset = func_breakable_reset;
- func_breakable_reset();
-
- CSQCMODEL_AUTOINIT();
-}
-
-// for use in maps with a "model" key set
-void spawnfunc_misc_breakablemodel() {
- spawnfunc_func_breakable();
-}
#include "command/common.qh"
#include "g_hook.qh"
#include "round_handler.qh"
+ #include "weapons/common.qh"
#endif
/*============================================
#include "../dpdefs/progsdefs.qh"
#include "../dpdefs/dpextensions.qh"
#include "../common/constants.qh"
+ #include "../common/triggers/subs.qh"
#include "autocvars.qh"
#include "constants.qh"
#include "defs.qh"
#include "g_subs.qh"
-void SUB_NullThink(void) { }
-
void spawnfunc_info_null (void)
{
remove(self);
//print(ftos(time), " -> ", ftos(e.frame), "\n");
}
-/*
-==================
-SUB_Remove
-
-Remove self
-==================
-*/
-void SUB_Remove (void)
-{
- remove (self);
-}
-
-/*
-==================
-SUB_Friction
-
-Applies some friction to self
-==================
-*/
-void SUB_Friction (void)
-{
- self.nextthink = time;
- if(self.flags & FL_ONGROUND)
- self.velocity = self.velocity * (1 - frametime * self.friction);
-}
-
-/*
-==================
-SUB_VanishOrRemove
-
-Makes client invisible or removes non-client
-==================
-*/
-void SUB_VanishOrRemove (entity ent)
-{
- if (IS_CLIENT(ent))
- {
- // vanish
- ent.alpha = -1;
- ent.effects = 0;
- ent.glow_size = 0;
- ent.pflags = 0;
- }
- else
- {
- // remove
- remove (ent);
- }
-}
-
-void SUB_SetFade_Think (void)
-{
- if(self.alpha == 0)
- self.alpha = 1;
- self.think = SUB_SetFade_Think;
- self.nextthink = time;
- self.alpha -= frametime * self.fade_rate;
- if (self.alpha < 0.01)
- SUB_VanishOrRemove(self);
- else
- self.nextthink = time;
-}
-
-/*
-==================
-SUB_SetFade
-
-Fade 'ent' out when time >= 'when'
-==================
-*/
-void SUB_SetFade (entity ent, float when, float fadetime)
-{
- ent.fade_rate = 1/fadetime;
- ent.think = SUB_SetFade_Think;
- ent.nextthink = when;
-}
-
-/*
-=============
-SUB_CalcMove
-
-calculate self.velocity and self.nextthink to reach dest from
-self.origin traveling at speed
-===============
-*/
-void SUB_CalcMoveDone (void)
-{
- // After moving, set origin to exact final destination
-
- setorigin (self, self.finaldest);
- self.velocity = '0 0 0';
- self.nextthink = -1;
- if (self.think1)
- self.think1 ();
-}
-
-void SUB_CalcMove_controller_think (void)
-{
- entity oldself;
- float traveltime;
- float phasepos;
- float nexttick;
- vector delta;
- vector delta2;
- vector veloc;
- vector angloc;
- vector nextpos;
- delta = self.destvec;
- delta2 = self.destvec2;
- if(time < self.animstate_endtime) {
- nexttick = time + sys_frametime;
-
- traveltime = self.animstate_endtime - self.animstate_starttime;
- phasepos = (nexttick - self.animstate_starttime) / traveltime; // range: [0, 1]
- phasepos = cubic_speedfunc(self.platmovetype_start, self.platmovetype_end, phasepos);
- nextpos = self.origin + (delta * phasepos) + (delta2 * phasepos * phasepos);
- // derivative: delta + 2 * delta2 * phasepos (e.g. for angle positioning)
-
- if(self.owner.platmovetype_turn)
- {
- vector destangle;
- destangle = delta + 2 * delta2 * phasepos;
- destangle = vectoangles(destangle);
- destangle.x = -destangle.x; // flip up / down orientation
-
- // take the shortest distance for the angles
- self.owner.angles_x -= 360 * floor((self.owner.angles.x - destangle.x) / 360 + 0.5);
- self.owner.angles_y -= 360 * floor((self.owner.angles.y - destangle.y) / 360 + 0.5);
- self.owner.angles_z -= 360 * floor((self.owner.angles.z - destangle.z) / 360 + 0.5);
- angloc = destangle - self.owner.angles;
- angloc = angloc * (1 / sys_frametime); // so it arrives for the next frame
- self.owner.avelocity = angloc;
- }
- if(nexttick < self.animstate_endtime)
- veloc = nextpos - self.owner.origin;
- else
- veloc = self.finaldest - self.owner.origin;
- veloc = veloc * (1 / sys_frametime); // so it arrives for the next frame
-
- self.owner.velocity = veloc;
- self.nextthink = nexttick;
- } else {
- // derivative: delta + 2 * delta2 (e.g. for angle positioning)
- oldself = self;
- self.owner.think = self.think1;
- self = self.owner;
- remove(oldself);
- self.think();
- }
-}
-
-void SUB_CalcMove_controller_setbezier (entity controller, vector org, vector control, vector dest)
-{
- // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
- // 2 * control * t - 2 * control * t * t + dest * t * t
- // 2 * control * t + (dest - 2 * control) * t * t
-
- controller.origin = org; // starting point
- control -= org;
- dest -= org;
-
- controller.destvec = 2 * control; // control point
- controller.destvec2 = dest - 2 * control; // quadratic part required to reach end point
- // also: initial d/dphasepos origin = 2 * control, final speed = 2 * (dest - control)
-}
-
-void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest)
-{
- // 0 * (1-t) * (1-t) + 2 * control * t * (1-t) + dest * t * t
- // 2 * control * t - 2 * control * t * t + dest * t * t
- // 2 * control * t + (dest - 2 * control) * t * t
-
- controller.origin = org; // starting point
- dest -= org;
-
- controller.destvec = dest; // end point
- controller.destvec2 = '0 0 0';
-}
-
-void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func)
-{
- float traveltime;
- entity controller;
-
- if (!tspeed)
- objerror ("No speed is defined!");
-
- self.think1 = func;
- self.finaldest = tdest;
- self.think = SUB_CalcMoveDone;
-
- switch(tspeedtype)
- {
- default:
- case TSPEED_START:
- traveltime = 2 * vlen(tcontrol - self.origin) / tspeed;
- break;
- case TSPEED_END:
- traveltime = 2 * vlen(tcontrol - tdest) / tspeed;
- break;
- case TSPEED_LINEAR:
- traveltime = vlen(tdest - self.origin) / tspeed;
- break;
- case TSPEED_TIME:
- traveltime = tspeed;
- break;
- }
-
- if (traveltime < 0.1) // useless anim
- {
- self.velocity = '0 0 0';
- self.nextthink = self.ltime + 0.1;
- return;
- }
-
- controller = spawn();
- controller.classname = "SUB_CalcMove_controller";
- controller.owner = self;
- controller.platmovetype = self.platmovetype;
- controller.platmovetype_start = self.platmovetype_start;
- controller.platmovetype_end = self.platmovetype_end;
- SUB_CalcMove_controller_setbezier(controller, self.origin, tcontrol, tdest);
- controller.finaldest = (tdest + '0 0 0.125'); // where do we want to end? Offset to overshoot a bit.
- controller.animstate_starttime = time;
- controller.animstate_endtime = time + traveltime;
- controller.think = SUB_CalcMove_controller_think;
- controller.think1 = self.think;
-
- // the thinking is now done by the controller
- self.think = SUB_NullThink; // for PushMove
- self.nextthink = self.ltime + traveltime;
-
- // invoke controller
- self = controller;
- self.think();
- self = self.owner;
-}
-
-void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func)
-{
- vector delta;
- float traveltime;
-
- if (!tspeed)
- objerror ("No speed is defined!");
-
- self.think1 = func;
- self.finaldest = tdest;
- self.think = SUB_CalcMoveDone;
-
- if (tdest == self.origin)
- {
- self.velocity = '0 0 0';
- self.nextthink = self.ltime + 0.1;
- return;
- }
-
- delta = tdest - self.origin;
-
- switch(tspeedtype)
- {
- default:
- case TSPEED_START:
- case TSPEED_END:
- case TSPEED_LINEAR:
- traveltime = vlen (delta) / tspeed;
- break;
- case TSPEED_TIME:
- traveltime = tspeed;
- break;
- }
-
- // Very short animations don't really show off the effect
- // of controlled animation, so let's just use linear movement.
- // Alternatively entities can choose to specify non-controlled movement.
- // The only currently implemented alternative movement is linear (value 1)
- if (traveltime < 0.15 || (self.platmovetype_start == 1 && self.platmovetype_end == 1)) // is this correct?
- {
- self.velocity = delta * (1/traveltime); // QuakeC doesn't allow vector/float division
- self.nextthink = self.ltime + traveltime;
- return;
- }
-
- // now just run like a bezier curve...
- SUB_CalcMove_Bezier((self.origin + tdest) * 0.5, tdest, tspeedtype, tspeed, func);
-}
-
-void SUB_CalcMoveEnt (entity ent, vector tdest, float tspeedtype, float tspeed, void() func)
-{
- entity oldself;
-
- oldself = self;
- self = ent;
-
- SUB_CalcMove (tdest, tspeedtype, tspeed, func);
-
- self = oldself;
-}
-
-/*
-=============
-SUB_CalcAngleMove
-
-calculate self.avelocity and self.nextthink to reach destangle from
-self.angles rotating
-
-The calling function should make sure self.think is valid
-===============
-*/
-void SUB_CalcAngleMoveDone (void)
-{
- // After rotating, set angle to exact final angle
- self.angles = self.finalangle;
- self.avelocity = '0 0 0';
- self.nextthink = -1;
- if (self.think1)
- self.think1 ();
-}
-
-// FIXME: I fixed this function only for rotation around the main axes
-void SUB_CalcAngleMove (vector destangle, float tspeedtype, float tspeed, void() func)
-{
- vector delta;
- float traveltime;
-
- if (!tspeed)
- objerror ("No speed is defined!");
-
- // take the shortest distance for the angles
- self.angles_x -= 360 * floor((self.angles.x - destangle.x) / 360 + 0.5);
- self.angles_y -= 360 * floor((self.angles.y - destangle.y) / 360 + 0.5);
- self.angles_z -= 360 * floor((self.angles.z - destangle.z) / 360 + 0.5);
- delta = destangle - self.angles;
-
- switch(tspeedtype)
- {
- default:
- case TSPEED_START:
- case TSPEED_END:
- case TSPEED_LINEAR:
- traveltime = vlen (delta) / tspeed;
- break;
- case TSPEED_TIME:
- traveltime = tspeed;
- break;
- }
-
- self.think1 = func;
- self.finalangle = destangle;
- self.think = SUB_CalcAngleMoveDone;
-
- if (traveltime < 0.1)
- {
- self.avelocity = '0 0 0';
- self.nextthink = self.ltime + 0.1;
- return;
- }
-
- self.avelocity = delta * (1 / traveltime);
- self.nextthink = self.ltime + traveltime;
-}
-
-void SUB_CalcAngleMoveEnt (entity ent, vector destangle, float tspeedtype, float tspeed, void() func)
-{
- entity oldself;
-
- oldself = self;
- self = ent;
-
- SUB_CalcAngleMove (destangle, tspeedtype, tspeed, func);
-
- self = oldself;
-}
-
/*
==================
main
void SUB_CalcMove_controller_setlinear (entity controller, vector org, vector dest);
-float TSPEED_TIME = -1;
-float TSPEED_LINEAR = 0;
-float TSPEED_START = 1;
-float TSPEED_END = 2;
-// TODO average too?
-
void SUB_CalcMove_Bezier (vector tcontrol, vector tdest, float tspeedtype, float tspeed, void() func);
void SUB_CalcMove (vector tdest, float tspeedtype, float tspeed, void() func);
+++ /dev/null
-#include "g_triggers.qh"
-#include "t_jumppads.qh"
-
-void SUB_DontUseTargets()
-{
-}
-
-
-void DelayThink()
-{
- activator = self.enemy;
- SUB_UseTargets ();
- remove(self);
-}
-
-/*
-==============================
-SUB_UseTargets
-
-the global "activator" should be set to the entity that initiated the firing.
-
-If self.delay is set, a DelayedUse entity will be created that will actually
-do the SUB_UseTargets after that many seconds have passed.
-
-Centerprints any self.message to the activator.
-
-Removes all entities with a targetname that match self.killtarget,
-and removes them, so some events can remove other triggers.
-
-Search for (string)targetname in all entities that
-match (string)self.target and call their .use function
-
-==============================
-*/
-void SUB_UseTargets()
-{
- entity t, stemp, otemp, act;
- string s;
- float i;
-
-//
-// check for a delay
-//
- if (self.delay)
- {
- // create a temp object to fire at a later time
- t = spawn();
- t.classname = "DelayedUse";
- t.nextthink = time + self.delay;
- t.think = DelayThink;
- t.enemy = activator;
- t.message = self.message;
- t.killtarget = self.killtarget;
- t.target = self.target;
- t.target2 = self.target2;
- t.target3 = self.target3;
- t.target4 = self.target4;
- return;
- }
-
-
-//
-// print the message
-//
- if(self)
- if(IS_PLAYER(activator) && self.message != "")
- if(IS_REAL_CLIENT(activator))
- {
- centerprint(activator, self.message);
- if (self.noise == "")
- play2(activator, "misc/talk.wav");
- }
-
-//
-// kill the killtagets
-//
- s = self.killtarget;
- if (s != "")
- {
- for(t = world; (t = find(t, targetname, s)); )
- remove(t);
- }
-
-//
-// fire targets
-//
- act = activator;
- stemp = self;
- otemp = other;
-
- if(stemp.target_random)
- RandomSelection_Init();
-
- for(i = 0; i < 4; ++i)
- {
- switch(i)
- {
- default:
- case 0: s = stemp.target; break;
- case 1: s = stemp.target2; break;
- case 2: s = stemp.target3; break;
- case 3: s = stemp.target4; break;
- }
- if (s != "")
- {
- for(t = world; (t = find(t, targetname, s)); )
- if(t.use)
- {
- if(stemp.target_random)
- {
- RandomSelection_Add(t, 0, string_null, 1, 0);
- }
- else
- {
- self = t;
- other = stemp;
- activator = act;
- self.use();
- }
- }
- }
- }
-
- if(stemp.target_random && RandomSelection_chosen_ent)
- {
- self = RandomSelection_chosen_ent;
- other = stemp;
- activator = act;
- self.use();
- }
-
- activator = act;
- self = stemp;
- other = otemp;
-}
-
-
-//=============================================================================
-
-// the wait time has passed, so set back up for another activation
-void multi_wait()
-{
- if (self.max_health)
- {
- self.health = self.max_health;
- self.takedamage = DAMAGE_YES;
- self.solid = SOLID_BBOX;
- }
-}
-
-
-// the trigger was just touched/killed/used
-// self.enemy should be set to the activator so it can be held through a delay
-// so wait for the delay time before firing
-void multi_trigger()
-{
- if (self.nextthink > time)
- {
- return; // allready been triggered
- }
-
- if (self.classname == "trigger_secret")
- {
- if (!IS_PLAYER(self.enemy))
- return;
- found_secrets = found_secrets + 1;
- WriteByte (MSG_ALL, SVC_FOUNDSECRET);
- }
-
- if (self.noise)
- sound (self.enemy, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
-// don't trigger again until reset
- self.takedamage = DAMAGE_NO;
-
- activator = self.enemy;
- other = self.goalentity;
- SUB_UseTargets();
-
- if (self.wait > 0)
- {
- self.think = multi_wait;
- self.nextthink = time + self.wait;
- }
- else if (self.wait == 0)
- {
- multi_wait(); // waiting finished
- }
- else
- { // we can't just remove (self) here, because this is a touch function
- // called wheil C code is looping through area links...
- self.touch = func_null;
- }
-}
-
-void multi_use()
-{
- self.goalentity = other;
- self.enemy = activator;
- multi_trigger();
-}
-
-void multi_touch()
-{
- if(!(self.spawnflags & 2))
- if(!other.iscreature)
- return;
-
- if(self.team)
- if(((self.spawnflags & 4) == 0) == (self.team != other.team))
- return;
-
-// if the trigger has an angles field, check player's facing direction
- if (self.movedir != '0 0 0')
- {
- makevectors (other.angles);
- if (v_forward * self.movedir < 0)
- return; // not facing the right way
- }
-
- EXACTTRIGGER_TOUCH;
-
- self.enemy = other;
- self.goalentity = other;
- multi_trigger ();
-}
-
-void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
- if (!self.takedamage)
- return;
- if(self.spawnflags & DOOR_NOSPLASH)
- if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
- return;
- self.health = self.health - damage;
- if (self.health <= 0)
- {
- self.enemy = attacker;
- self.goalentity = inflictor;
- multi_trigger();
- }
-}
-
-void multi_reset()
-{
- if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
- self.touch = multi_touch;
- if (self.max_health)
- {
- self.health = self.max_health;
- self.takedamage = DAMAGE_YES;
- self.solid = SOLID_BBOX;
- }
- self.think = func_null;
- self.nextthink = 0;
- self.team = self.team_saved;
-}
-
-/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
-Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
-If "delay" is set, the trigger waits some time after activating before firing.
-"wait" : Seconds between triggerings. (.2 default)
-If notouch is set, the trigger is only fired by other entities, not by touching.
-NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
-sounds
-1) secret
-2) beep beep
-3) large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_multiple()
-{
- self.reset = multi_reset;
- if (self.sounds == 1)
- {
- precache_sound ("misc/secret.wav");
- self.noise = "misc/secret.wav";
- }
- else if (self.sounds == 2)
- {
- precache_sound ("misc/talk.wav");
- self.noise = "misc/talk.wav";
- }
- else if (self.sounds == 3)
- {
- precache_sound ("misc/trigger1.wav");
- self.noise = "misc/trigger1.wav";
- }
-
- if (!self.wait)
- self.wait = 0.2;
- else if(self.wait < -1)
- self.wait = 0;
- self.use = multi_use;
-
- EXACTTRIGGER_INIT;
-
- self.team_saved = self.team;
-
- if (self.health)
- {
- if (self.spawnflags & SPAWNFLAG_NOTOUCH)
- objerror ("health and notouch don't make sense\n");
- self.max_health = self.health;
- self.event_damage = multi_eventdamage;
- self.takedamage = DAMAGE_YES;
- self.solid = SOLID_BBOX;
- setorigin (self, self.origin); // make sure it links into the world
- }
- else
- {
- if ( !(self.spawnflags & SPAWNFLAG_NOTOUCH) )
- {
- self.touch = multi_touch;
- setorigin (self, self.origin); // make sure it links into the world
- }
- }
-}
-
-
-/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
-Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
-"targetname". If "health" is set, the trigger must be killed to activate.
-If notouch is set, the trigger is only fired by other entities, not by touching.
-if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
-if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
-sounds
-1) secret
-2) beep beep
-3) large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_once()
-{
- self.wait = -1;
- spawnfunc_trigger_multiple();
-}
-
-//=============================================================================
-
-/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
-This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
-*/
-void spawnfunc_trigger_relay()
-{
- self.use = SUB_UseTargets;
- self.reset = spawnfunc_trigger_relay; // this spawnfunc resets fully
-}
-
-void delay_use()
-{
- self.think = SUB_UseTargets;
- self.nextthink = self.wait;
-}
-
-void delay_reset()
-{
- self.think = func_null;
- self.nextthink = 0;
-}
-
-void spawnfunc_trigger_delay()
-{
- if(!self.wait)
- self.wait = 1;
-
- self.use = delay_use;
- self.reset = delay_reset;
-}
-
-//=============================================================================
-
-
-void counter_use()
-{
- self.count -= 1;
- if (self.count < 0)
- return;
-
- if (self.count == 0)
- {
- if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
- Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COMPLETED);
-
- self.enemy = activator;
- multi_trigger ();
- }
- else
- {
- if(IS_PLAYER(activator) && (self.spawnflags & SPAWNFLAG_NOMESSAGE) == 0)
- if(self.count >= 4)
- Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER);
- else
- Send_Notification(NOTIF_ONE, activator, MSG_CENTER, CENTER_SEQUENCE_COUNTER_FEWMORE, self.count);
- }
-}
-
-void counter_reset()
-{
- self.count = self.cnt;
- multi_reset();
-}
-
-/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
-Acts as an intermediary for an action that takes multiple inputs.
-
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
-
-After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
-*/
-void spawnfunc_trigger_counter()
-{
- self.wait = -1;
- if (!self.count)
- self.count = 2;
- self.cnt = self.count;
-
- self.use = counter_use;
- self.reset = counter_reset;
-}
-
-void trigger_hurt_use()
-{
- if(IS_PLAYER(activator))
- self.enemy = activator;
- else
- self.enemy = world; // let's just destroy it, if taking over is too much work
-}
-
-void trigger_hurt_touch()
-{
- if (self.active != ACTIVE_ACTIVE)
- return;
-
- if(self.team)
- if(((self.spawnflags & 4) == 0) == (self.team != other.team))
- return;
-
- // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
- if (other.iscreature)
- {
- if (other.takedamage)
- if (other.triggerhurttime < time)
- {
- EXACTTRIGGER_TOUCH;
- other.triggerhurttime = time + 1;
-
- entity own;
- own = self.enemy;
- if (!IS_PLAYER(own))
- {
- own = self;
- self.enemy = world; // I still hate you all
- }
-
- Damage (other, self, own, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
- }
- else if(other.damagedbytriggers)
- {
- if(other.takedamage)
- {
- EXACTTRIGGER_TOUCH;
- Damage(other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
- }
-
- return;
-}
-
-/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
-Any object touching this will be hurt
-set dmg to damage amount
-defalt dmg = 5
-*/
-void spawnfunc_trigger_hurt()
-{
- EXACTTRIGGER_INIT;
- self.active = ACTIVE_ACTIVE;
- self.touch = trigger_hurt_touch;
- self.use = trigger_hurt_use;
- self.enemy = world; // I hate you all
- if (!self.dmg)
- self.dmg = 1000;
- if (self.message == "")
- self.message = "was in the wrong place";
- if (self.message2 == "")
- self.message2 = "was thrown into a world of hurt by";
- // self.message = "someone like %s always gets wrongplaced";
-
- if(!trigger_hurt_first)
- trigger_hurt_first = self;
- if(trigger_hurt_last)
- trigger_hurt_last.trigger_hurt_next = self;
- trigger_hurt_last = self;
-}
-
-float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end)
-{
- entity th;
-
- for(th = trigger_hurt_first; th; th = th.trigger_hurt_next)
- if(tracebox_hits_box(start, mi, ma, end, th.absmin, th.absmax))
- return true;
-
- return false;
-}
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
-//
-//////////////////////////////////////////////////////////////
-
-void trigger_heal_touch()
-{
- if (self.active != ACTIVE_ACTIVE)
- return;
-
- // only do the EXACTTRIGGER_TOUCH checks when really needed (saves some cpu)
- if (other.iscreature)
- {
- if (other.takedamage)
- if (!other.deadflag)
- if (other.triggerhealtime < time)
- {
- EXACTTRIGGER_TOUCH;
- other.triggerhealtime = time + 1;
-
- if (other.health < self.max_health)
- {
- other.health = min(other.health + self.health, self.max_health);
- other.pauserothealth_finished = max(other.pauserothealth_finished, time + autocvar_g_balance_pause_health_rot);
- sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
- }
- }
- }
-}
-
-void spawnfunc_trigger_heal()
-{
- self.active = ACTIVE_ACTIVE;
-
- EXACTTRIGGER_INIT;
- self.touch = trigger_heal_touch;
- if (!self.health)
- self.health = 10;
- if (!self.max_health)
- self.max_health = 200; //Max health topoff for field
- if(self.noise == "")
- self.noise = "misc/mediumhealth.wav";
- precache_sound(self.noise);
-}
-
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//End trigger_heal
-//
-//////////////////////////////////////////////////////////////
-
-void trigger_gravity_remove(entity own)
-{
- if(own.trigger_gravity_check.owner == own)
- {
- UpdateCSQCProjectile(own);
- own.gravity = own.trigger_gravity_check.gravity;
- remove(own.trigger_gravity_check);
- }
- else
- backtrace("Removing a trigger_gravity_check with no valid owner");
- own.trigger_gravity_check = world;
-}
-void trigger_gravity_check_think()
-{
- // This spawns when a player enters the gravity zone and checks if he left.
- // Each frame, self.count is set to 2 by trigger_gravity_touch() and decreased by 1 here.
- // It the player has left the gravity trigger, this will be allowed to reach 0 and indicate that.
- if(self.count <= 0)
- {
- if(self.owner.trigger_gravity_check == self)
- trigger_gravity_remove(self.owner);
- else
- remove(self);
- return;
- }
- else
- {
- self.count -= 1;
- self.nextthink = time;
- }
-}
-
-void trigger_gravity_use()
-{
- self.state = !self.state;
-}
-
-void trigger_gravity_touch()
-{
- float g;
-
- if(self.state != true)
- return;
-
- EXACTTRIGGER_TOUCH;
-
- g = self.gravity;
-
- if (!(self.spawnflags & 1))
- {
- if(other.trigger_gravity_check)
- {
- if(self == other.trigger_gravity_check.enemy)
- {
- // same?
- other.trigger_gravity_check.count = 2; // gravity one more frame...
- return;
- }
-
- // compare prio
- if(self.cnt > other.trigger_gravity_check.enemy.cnt)
- trigger_gravity_remove(other);
- else
- return;
- }
- other.trigger_gravity_check = spawn();
- other.trigger_gravity_check.enemy = self;
- other.trigger_gravity_check.owner = other;
- other.trigger_gravity_check.gravity = other.gravity;
- other.trigger_gravity_check.think = trigger_gravity_check_think;
- other.trigger_gravity_check.nextthink = time;
- other.trigger_gravity_check.count = 2;
- if(other.gravity)
- g *= other.gravity;
- }
-
- if (other.gravity != g)
- {
- other.gravity = g;
- if(self.noise != "")
- sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
- UpdateCSQCProjectile(self.owner);
- }
-}
-
-void spawnfunc_trigger_gravity()
-{
- if(self.gravity == 1)
- return;
-
- EXACTTRIGGER_INIT;
- self.touch = trigger_gravity_touch;
- if(self.noise != "")
- precache_sound(self.noise);
-
- self.state = true;
- IFTARGETED
- {
- self.use = trigger_gravity_use;
- if(self.spawnflags & 2)
- self.state = false;
- }
-}
-
-//=============================================================================
-
-// TODO add a way to do looped sounds with sound(); then complete this entity
-void target_speaker_use_activator()
-{
- if (!IS_REAL_CLIENT(activator))
- return;
- string snd;
- if(substring(self.noise, 0, 1) == "*")
- {
- var .string sample;
- sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
- if(GetPlayerSoundSampleField_notFound)
- snd = "misc/null.wav";
- else if(activator.sample == "")
- snd = "misc/null.wav";
- else
- {
- tokenize_console(activator.sample);
- float n;
- n = stof(argv(1));
- if(n > 0)
- snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
- else
- snd = strcat(argv(0), ".wav"); // randomization
- }
- }
- else
- snd = self.noise;
- msg_entity = activator;
- soundto(MSG_ONE, self, CH_TRIGGER, snd, VOL_BASE * self.volume, self.atten);
-}
-void target_speaker_use_on()
-{
- string snd;
- if(substring(self.noise, 0, 1) == "*")
- {
- var .string sample;
- sample = GetVoiceMessageSampleField(substring(self.noise, 1, -1));
- if(GetPlayerSoundSampleField_notFound)
- snd = "misc/null.wav";
- else if(activator.sample == "")
- snd = "misc/null.wav";
- else
- {
- tokenize_console(activator.sample);
- float n;
- n = stof(argv(1));
- if(n > 0)
- snd = strcat(argv(0), ftos(floor(random() * n + 1)), ".wav"); // randomization
- else
- snd = strcat(argv(0), ".wav"); // randomization
- }
- }
- else
- snd = self.noise;
- sound(self, CH_TRIGGER_SINGLE, snd, VOL_BASE * self.volume, self.atten);
- if(self.spawnflags & 3)
- self.use = target_speaker_use_off;
-}
-void target_speaker_use_off()
-{
- sound(self, CH_TRIGGER_SINGLE, "misc/null.wav", VOL_BASE * self.volume, self.atten);
- self.use = target_speaker_use_on;
-}
-void target_speaker_reset()
-{
- if(self.spawnflags & 1) // LOOPED_ON
- {
- if(self.use == target_speaker_use_on)
- target_speaker_use_on();
- }
- else if(self.spawnflags & 2)
- {
- if(self.use == target_speaker_use_off)
- target_speaker_use_off();
- }
-}
-
-void spawnfunc_target_speaker()
-{
- // TODO: "*" prefix to sound file name
- // TODO: wait and random (just, HOW? random is not a field)
- if(self.noise)
- precache_sound (self.noise);
-
- if(!self.atten && !(self.spawnflags & 4))
- {
- IFTARGETED
- self.atten = ATTEN_NORM;
- else
- self.atten = ATTEN_STATIC;
- }
- else if(self.atten < 0)
- self.atten = 0;
-
- if(!self.volume)
- self.volume = 1;
-
- IFTARGETED
- {
- if(self.spawnflags & 8) // ACTIVATOR
- self.use = target_speaker_use_activator;
- else if(self.spawnflags & 1) // LOOPED_ON
- {
- target_speaker_use_on();
- self.reset = target_speaker_reset;
- }
- else if(self.spawnflags & 2) // LOOPED_OFF
- {
- self.use = target_speaker_use_on;
- self.reset = target_speaker_reset;
- }
- else
- self.use = target_speaker_use_on;
- }
- else if(self.spawnflags & 1) // LOOPED_ON
- {
- ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
- remove(self);
- }
- else if(self.spawnflags & 2) // LOOPED_OFF
- {
- objerror("This sound entity can never be activated");
- }
- else
- {
- // Quake/Nexuiz fallback
- ambientsound (self.origin, self.noise, VOL_BASE * self.volume, self.atten);
- remove(self);
- }
-}
-
-
-void spawnfunc_func_stardust() {
- self.effects = EF_STARDUST;
-}
-
-float pointparticles_SendEntity(entity to, float fl)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_POINTPARTICLES);
-
- // optional features to save space
- fl = fl & 0x0F;
- if(self.spawnflags & 2)
- fl |= 0x10; // absolute count on toggle-on
- if(self.movedir != '0 0 0' || self.velocity != '0 0 0')
- fl |= 0x20; // 4 bytes - saves CPU
- if(self.waterlevel || self.count != 1)
- fl |= 0x40; // 4 bytes - obscure features almost never used
- if(self.mins != '0 0 0' || self.maxs != '0 0 0')
- fl |= 0x80; // 14 bytes - saves lots of space
-
- WriteByte(MSG_ENTITY, fl);
- if(fl & 2)
- {
- if(self.state)
- WriteCoord(MSG_ENTITY, self.impulse);
- else
- WriteCoord(MSG_ENTITY, 0); // off
- }
- if(fl & 4)
- {
- WriteCoord(MSG_ENTITY, self.origin.x);
- WriteCoord(MSG_ENTITY, self.origin.y);
- WriteCoord(MSG_ENTITY, self.origin.z);
- }
- if(fl & 1)
- {
- if(self.model != "null")
- {
- WriteShort(MSG_ENTITY, self.modelindex);
- if(fl & 0x80)
- {
- WriteCoord(MSG_ENTITY, self.mins.x);
- WriteCoord(MSG_ENTITY, self.mins.y);
- WriteCoord(MSG_ENTITY, self.mins.z);
- WriteCoord(MSG_ENTITY, self.maxs.x);
- WriteCoord(MSG_ENTITY, self.maxs.y);
- WriteCoord(MSG_ENTITY, self.maxs.z);
- }
- }
- else
- {
- WriteShort(MSG_ENTITY, 0);
- if(fl & 0x80)
- {
- WriteCoord(MSG_ENTITY, self.maxs.x);
- WriteCoord(MSG_ENTITY, self.maxs.y);
- WriteCoord(MSG_ENTITY, self.maxs.z);
- }
- }
- WriteShort(MSG_ENTITY, self.cnt);
- if(fl & 0x20)
- {
- WriteShort(MSG_ENTITY, compressShortVector(self.velocity));
- WriteShort(MSG_ENTITY, compressShortVector(self.movedir));
- }
- if(fl & 0x40)
- {
- WriteShort(MSG_ENTITY, self.waterlevel * 16.0);
- WriteByte(MSG_ENTITY, self.count * 16.0);
- }
- WriteString(MSG_ENTITY, self.noise);
- if(self.noise != "")
- {
- WriteByte(MSG_ENTITY, floor(self.atten * 64));
- WriteByte(MSG_ENTITY, floor(self.volume * 255));
- }
- WriteString(MSG_ENTITY, self.bgmscript);
- if(self.bgmscript != "")
- {
- WriteByte(MSG_ENTITY, floor(self.bgmscriptattack * 64));
- WriteByte(MSG_ENTITY, floor(self.bgmscriptdecay * 64));
- WriteByte(MSG_ENTITY, floor(self.bgmscriptsustain * 255));
- WriteByte(MSG_ENTITY, floor(self.bgmscriptrelease * 64));
- }
- }
- return 1;
-}
-
-void pointparticles_use()
-{
- self.state = !self.state;
- self.SendFlags |= 2;
-}
-
-void pointparticles_think()
-{
- if(self.origin != self.oldorigin)
- {
- self.SendFlags |= 4;
- self.oldorigin = self.origin;
- }
- self.nextthink = time;
-}
-
-void pointparticles_reset()
-{
- if(self.spawnflags & 1)
- self.state = 1;
- else
- self.state = 0;
-}
-
-void spawnfunc_func_pointparticles()
-{
- if(self.model != "")
- setmodel(self, self.model);
- if(self.noise != "")
- precache_sound (self.noise);
-
- if(!self.bgmscriptsustain)
- self.bgmscriptsustain = 1;
- else if(self.bgmscriptsustain < 0)
- self.bgmscriptsustain = 0;
-
- if(!self.atten)
- self.atten = ATTEN_NORM;
- else if(self.atten < 0)
- self.atten = 0;
- if(!self.volume)
- self.volume = 1;
- if(!self.count)
- self.count = 1;
- if(!self.impulse)
- self.impulse = 1;
-
- if(!self.modelindex)
- {
- setorigin(self, self.origin + self.mins);
- setsize(self, '0 0 0', self.maxs - self.mins);
- }
- if(!self.cnt)
- self.cnt = particleeffectnum(self.mdl);
-
- Net_LinkEntity(self, (self.spawnflags & 4), 0, pointparticles_SendEntity);
-
- IFTARGETED
- {
- self.use = pointparticles_use;
- self.reset = pointparticles_reset;
- self.reset();
- }
- else
- self.state = 1;
- self.think = pointparticles_think;
- self.nextthink = time;
-}
-
-void spawnfunc_func_sparks()
-{
- // self.cnt is the amount of sparks that one burst will spawn
- if(self.cnt < 1) {
- self.cnt = 25.0; // nice default value
- }
-
- // self.wait is the probability that a sparkthink will spawn a spark shower
- // range: 0 - 1, but 0 makes little sense, so...
- if(self.wait < 0.05) {
- self.wait = 0.25; // nice default value
- }
-
- self.count = self.cnt;
- self.mins = '0 0 0';
- self.maxs = '0 0 0';
- self.velocity = '0 0 -1';
- self.mdl = "TE_SPARK";
- self.impulse = 10 * self.wait; // by default 2.5/sec
- self.wait = 0;
- self.cnt = 0; // use mdl
-
- spawnfunc_func_pointparticles();
-}
-
-float rainsnow_SendEntity(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_RAINSNOW);
- WriteByte(MSG_ENTITY, self.state);
- WriteCoord(MSG_ENTITY, self.origin.x + self.mins.x);
- WriteCoord(MSG_ENTITY, self.origin.y + self.mins.y);
- WriteCoord(MSG_ENTITY, self.origin.z + self.mins.z);
- WriteCoord(MSG_ENTITY, self.maxs.x - self.mins.x);
- WriteCoord(MSG_ENTITY, self.maxs.y - self.mins.y);
- WriteCoord(MSG_ENTITY, self.maxs.z - self.mins.z);
- WriteShort(MSG_ENTITY, compressShortVector(self.dest));
- WriteShort(MSG_ENTITY, self.count);
- WriteByte(MSG_ENTITY, self.cnt);
- return 1;
-}
-
-/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
-This is an invisible area like a trigger, which rain falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_rain()
-{
- self.dest = self.velocity;
- self.velocity = '0 0 0';
- if (!self.dest)
- self.dest = '0 0 -700';
- self.angles = '0 0 0';
- self.movetype = MOVETYPE_NONE;
- self.solid = SOLID_NOT;
- SetBrushEntityModel();
- if (!self.cnt)
- self.cnt = 12;
- if (!self.count)
- self.count = 2000;
- self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
- if (self.count < 1)
- self.count = 1;
- if(self.count > 65535)
- self.count = 65535;
-
- self.state = 1; // 1 is rain, 0 is snow
- self.Version = 1;
-
- Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
-}
-
-
-/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
-This is an invisible area like a trigger, which snow falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_snow()
-{
- self.dest = self.velocity;
- self.velocity = '0 0 0';
- if (!self.dest)
- self.dest = '0 0 -300';
- self.angles = '0 0 0';
- self.movetype = MOVETYPE_NONE;
- self.solid = SOLID_NOT;
- SetBrushEntityModel();
- if (!self.cnt)
- self.cnt = 12;
- if (!self.count)
- self.count = 2000;
- self.count = 0.01 * self.count * (self.size.x / 1024) * (self.size.y / 1024);
- if (self.count < 1)
- self.count = 1;
- if(self.count > 65535)
- self.count = 65535;
-
- self.state = 0; // 1 is rain, 0 is snow
- self.Version = 1;
-
- Net_LinkEntity(self, false, 0, rainsnow_SendEntity);
-}
-
-void misc_laser_aim()
-{
- vector a;
- if(self.enemy)
- {
- if(self.spawnflags & 2)
- {
- if(self.enemy.origin != self.mangle)
- {
- self.mangle = self.enemy.origin;
- self.SendFlags |= 2;
- }
- }
- else
- {
- a = vectoangles(self.enemy.origin - self.origin);
- a.x = -a.x;
- if(a != self.mangle)
- {
- self.mangle = a;
- self.SendFlags |= 2;
- }
- }
- }
- else
- {
- if(self.angles != self.mangle)
- {
- self.mangle = self.angles;
- self.SendFlags |= 2;
- }
- }
- if(self.origin != self.oldorigin)
- {
- self.SendFlags |= 1;
- self.oldorigin = self.origin;
- }
-}
-
-void misc_laser_init()
-{
- if(self.target != "")
- self.enemy = find(world, targetname, self.target);
-}
-
-void misc_laser_think()
-{
- vector o;
- entity oldself;
- entity hitent;
- vector hitloc;
-
- self.nextthink = time;
-
- if(!self.state)
- return;
-
- misc_laser_aim();
-
- if(self.enemy)
- {
- o = self.enemy.origin;
- if (!(self.spawnflags & 2))
- o = self.origin + normalize(o - self.origin) * 32768;
- }
- else
- {
- makevectors(self.mangle);
- o = self.origin + v_forward * 32768;
- }
-
- if(self.dmg || self.enemy.target != "")
- {
- traceline(self.origin, o, MOVE_NORMAL, self);
- }
- hitent = trace_ent;
- hitloc = trace_endpos;
-
- if(self.enemy.target != "") // DETECTOR laser
- {
- if(trace_ent.iscreature)
- {
- self.pusher = hitent;
- if(!self.count)
- {
- self.count = 1;
-
- oldself = self;
- self = self.enemy;
- activator = self.pusher;
- SUB_UseTargets();
- self = oldself;
- }
- }
- else
- {
- if(self.count)
- {
- self.count = 0;
-
- oldself = self;
- self = self.enemy;
- activator = self.pusher;
- SUB_UseTargets();
- self = oldself;
- }
- }
- }
-
- if(self.dmg)
- {
- if(self.team)
- if(((self.spawnflags & 8) == 0) == (self.team != hitent.team))
- return;
- if(hitent.takedamage)
- Damage(hitent, self, self, ((self.dmg < 0) ? 100000 : (self.dmg * frametime)), DEATH_HURTTRIGGER, hitloc, '0 0 0');
- }
-}
-
-float laser_SendEntity(entity to, float fl)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_LASER);
- fl = fl - (fl & 0xF0); // use that bit to indicate finite length laser
- if(self.spawnflags & 2)
- fl |= 0x80;
- if(self.alpha)
- fl |= 0x40;
- if(self.scale != 1 || self.modelscale != 1)
- fl |= 0x20;
- if(self.spawnflags & 4)
- fl |= 0x10;
- WriteByte(MSG_ENTITY, fl);
- if(fl & 1)
- {
- WriteCoord(MSG_ENTITY, self.origin.x);
- WriteCoord(MSG_ENTITY, self.origin.y);
- WriteCoord(MSG_ENTITY, self.origin.z);
- }
- if(fl & 8)
- {
- WriteByte(MSG_ENTITY, self.colormod.x * 255.0);
- WriteByte(MSG_ENTITY, self.colormod.y * 255.0);
- WriteByte(MSG_ENTITY, self.colormod.z * 255.0);
- if(fl & 0x40)
- WriteByte(MSG_ENTITY, self.alpha * 255.0);
- if(fl & 0x20)
- {
- WriteByte(MSG_ENTITY, bound(0, self.scale * 16.0, 255));
- WriteByte(MSG_ENTITY, bound(0, self.modelscale * 16.0, 255));
- }
- if((fl & 0x80) || !(fl & 0x10)) // effect doesn't need sending if the laser is infinite and has collision testing turned off
- WriteShort(MSG_ENTITY, self.cnt + 1);
- }
- if(fl & 2)
- {
- if(fl & 0x80)
- {
- WriteCoord(MSG_ENTITY, self.enemy.origin.x);
- WriteCoord(MSG_ENTITY, self.enemy.origin.y);
- WriteCoord(MSG_ENTITY, self.enemy.origin.z);
- }
- else
- {
- WriteAngle(MSG_ENTITY, self.mangle.x);
- WriteAngle(MSG_ENTITY, self.mangle.y);
- }
- }
- if(fl & 4)
- WriteByte(MSG_ENTITY, self.state);
- return 1;
-}
-
-/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
-Any object touching the beam will be hurt
-Keys:
-"target"
- spawnfunc_target_position where the laser ends
-"mdl"
- name of beam end effect to use
-"colormod"
- color of the beam (default: red)
-"dmg"
- damage per second (-1 for a laser that kills immediately)
-*/
-void laser_use()
-{
- self.state = !self.state;
- self.SendFlags |= 4;
- misc_laser_aim();
-}
-
-void laser_reset()
-{
- if(self.spawnflags & 1)
- self.state = 1;
- else
- self.state = 0;
-}
-
-void spawnfunc_misc_laser()
-{
- if(self.mdl)
- {
- if(self.mdl == "none")
- self.cnt = -1;
- else
- {
- self.cnt = particleeffectnum(self.mdl);
- if(self.cnt < 0)
- if(self.dmg)
- self.cnt = particleeffectnum("laser_deadly");
- }
- }
- else if(!self.cnt)
- {
- if(self.dmg)
- self.cnt = particleeffectnum("laser_deadly");
- else
- self.cnt = -1;
- }
- if(self.cnt < 0)
- self.cnt = -1;
-
- if(self.colormod == '0 0 0')
- if(!self.alpha)
- self.colormod = '1 0 0';
- if(self.message == "")
- self.message = "saw the light";
- if (self.message2 == "")
- self.message2 = "was pushed into a laser by";
- if(!self.scale)
- self.scale = 1;
- if(!self.modelscale)
- self.modelscale = 1;
- else if(self.modelscale < 0)
- self.modelscale = 0;
- self.think = misc_laser_think;
- self.nextthink = time;
- InitializeEntity(self, misc_laser_init, INITPRIO_FINDTARGET);
-
- self.mangle = self.angles;
-
- Net_LinkEntity(self, false, 0, laser_SendEntity);
-
- IFTARGETED
- {
- self.reset = laser_reset;
- laser_reset();
- self.use = laser_use;
- }
- else
- self.state = 1;
-}
-
-// tZorks trigger impulse / gravity
-
-// targeted (directional) mode
-void trigger_impulse_touch1()
-{
- entity targ;
- float pushdeltatime;
- float str;
-
- if (self.active != ACTIVE_ACTIVE)
- return;
-
- if (!isPushable(other))
- return;
-
- EXACTTRIGGER_TOUCH;
-
- targ = find(world, targetname, self.target);
- if(!targ)
- {
- objerror("trigger_force without a (valid) .target!\n");
- remove(self);
- return;
- }
-
- str = min(self.radius, vlen(self.origin - other.origin));
-
- if(self.falloff == 1)
- str = (str / self.radius) * self.strength;
- else if(self.falloff == 2)
- str = (1 - (str / self.radius)) * self.strength;
- else
- str = self.strength;
-
- pushdeltatime = time - other.lastpushtime;
- if (pushdeltatime > 0.15) pushdeltatime = 0;
- other.lastpushtime = time;
- if(!pushdeltatime) return;
-
- other.velocity = other.velocity + normalize(targ.origin - self.origin) * str * pushdeltatime;
- other.flags &= ~FL_ONGROUND;
- UpdateCSQCProjectile(other);
-}
-
-// Directionless (accelerator/decelerator) mode
-void trigger_impulse_touch2()
-{
- float pushdeltatime;
-
- if (self.active != ACTIVE_ACTIVE)
- return;
-
- if (!isPushable(other))
- return;
-
- EXACTTRIGGER_TOUCH;
-
- pushdeltatime = time - other.lastpushtime;
- if (pushdeltatime > 0.15) pushdeltatime = 0;
- other.lastpushtime = time;
- if(!pushdeltatime) return;
-
- // div0: ticrate independent, 1 = identity (not 20)
- other.velocity = other.velocity * pow(self.strength, pushdeltatime);
- UpdateCSQCProjectile(other);
-}
-
-// Spherical (gravity/repulsor) mode
-void trigger_impulse_touch3()
-{
- float pushdeltatime;
- float str;
-
- if (self.active != ACTIVE_ACTIVE)
- return;
-
- if (!isPushable(other))
- return;
-
- EXACTTRIGGER_TOUCH;
-
- pushdeltatime = time - other.lastpushtime;
- if (pushdeltatime > 0.15) pushdeltatime = 0;
- other.lastpushtime = time;
- if(!pushdeltatime) return;
-
- setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
-
- str = min(self.radius, vlen(self.origin - other.origin));
-
- if(self.falloff == 1)
- str = (1 - str / self.radius) * self.strength; // 1 in the inside
- else if(self.falloff == 2)
- str = (str / self.radius) * self.strength; // 0 in the inside
- else
- str = self.strength;
-
- other.velocity = other.velocity + normalize(other.origin - self.origin) * str * pushdeltatime;
- UpdateCSQCProjectile(other);
-}
-
-/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
--------- KEYS --------
-target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
- If not, this trigger acts like a damper/accelerator field.
-
-strength : This is how mutch force to add in the direction of .target each second
- when .target is set. If not, this is hoe mutch to slow down/accelerate
- someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
-
-radius : If set, act as a spherical device rather then a liniar one.
-
-falloff : 0 = none, 1 = liniar, 2 = inverted liniar
-
--------- NOTES --------
-Use a brush textured with common/origin in the trigger entity to determine the origin of the force
-in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
-*/
-
-void spawnfunc_trigger_impulse()
-{
- self.active = ACTIVE_ACTIVE;
-
- EXACTTRIGGER_INIT;
- if(self.radius)
- {
- if(!self.strength) self.strength = 2000 * autocvar_g_triggerimpulse_radial_multiplier;
- setorigin(self, self.origin);
- setsize(self, '-1 -1 -1' * self.radius,'1 1 1' * self.radius);
- self.touch = trigger_impulse_touch3;
- }
- else
- {
- if(self.target)
- {
- if(!self.strength) self.strength = 950 * autocvar_g_triggerimpulse_directional_multiplier;
- self.touch = trigger_impulse_touch1;
- }
- else
- {
- if(!self.strength) self.strength = 0.9;
- self.strength = pow(self.strength, autocvar_g_triggerimpulse_accel_power) * autocvar_g_triggerimpulse_accel_multiplier;
- self.touch = trigger_impulse_touch2;
- }
- }
-}
-
-/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
-"Flip-flop" trigger gate... lets only every second trigger event through
-*/
-void flipflop_use()
-{
- self.state = !self.state;
- if(self.state)
- SUB_UseTargets();
-}
-
-void spawnfunc_trigger_flipflop()
-{
- if(self.spawnflags & 1)
- self.state = 1;
- self.use = flipflop_use;
- self.reset = spawnfunc_trigger_flipflop; // perfect resetter
-}
-
-/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
-"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
-*/
-void monoflop_use()
-{
- self.nextthink = time + self.wait;
- self.enemy = activator;
- if(self.state)
- return;
- self.state = 1;
- SUB_UseTargets();
-}
-void monoflop_fixed_use()
-{
- if(self.state)
- return;
- self.nextthink = time + self.wait;
- self.state = 1;
- self.enemy = activator;
- SUB_UseTargets();
-}
-
-void monoflop_think()
-{
- self.state = 0;
- activator = self.enemy;
- SUB_UseTargets();
-}
-
-void monoflop_reset()
-{
- self.state = 0;
- self.nextthink = 0;
-}
-
-void spawnfunc_trigger_monoflop()
-{
- if(!self.wait)
- self.wait = 1;
- if(self.spawnflags & 1)
- self.use = monoflop_fixed_use;
- else
- self.use = monoflop_use;
- self.think = monoflop_think;
- self.state = 0;
- self.reset = monoflop_reset;
-}
-
-void multivibrator_send()
-{
- float newstate;
- float cyclestart;
-
- cyclestart = floor((time + self.phase) / (self.wait + self.respawntime)) * (self.wait + self.respawntime) - self.phase;
-
- newstate = (time < cyclestart + self.wait);
-
- activator = self;
- if(self.state != newstate)
- SUB_UseTargets();
- self.state = newstate;
-
- if(self.state)
- self.nextthink = cyclestart + self.wait + 0.01;
- else
- self.nextthink = cyclestart + self.wait + self.respawntime + 0.01;
-}
-
-void multivibrator_toggle()
-{
- if(self.nextthink == 0)
- {
- multivibrator_send();
- }
- else
- {
- if(self.state)
- {
- SUB_UseTargets();
- self.state = 0;
- }
- self.nextthink = 0;
- }
-}
-
-void multivibrator_reset()
-{
- if(!(self.spawnflags & 1))
- self.nextthink = 0; // wait for a trigger event
- else
- self.nextthink = max(1, time);
-}
-
-/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
-"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
--------- KEYS --------
-target: trigger all entities with this targetname when it goes off
-targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
-phase: offset of the timing
-wait: "on" cycle time (default: 1)
-respawntime: "off" cycle time (default: same as wait)
--------- SPAWNFLAGS --------
-START_ON: assume it is already turned on (when targeted)
-*/
-void spawnfunc_trigger_multivibrator()
-{
- if(!self.wait)
- self.wait = 1;
- if(!self.respawntime)
- self.respawntime = self.wait;
-
- self.state = 0;
- self.use = multivibrator_toggle;
- self.think = multivibrator_send;
- self.nextthink = max(1, time);
-
- IFTARGETED
- multivibrator_reset();
-}
-
-
-void follow_init()
-{
- entity src, dst;
- src = world;
- dst = world;
- if(self.killtarget != "")
- src = find(world, targetname, self.killtarget);
- if(self.target != "")
- dst = find(world, targetname, self.target);
-
- if(!src && !dst)
- {
- objerror("follow: could not find target/killtarget");
- return;
- }
-
- if(self.jointtype)
- {
- // already done :P entity must stay
- self.aiment = src;
- self.enemy = dst;
- }
- else if(!src || !dst)
- {
- objerror("follow: could not find target/killtarget");
- return;
- }
- else if(self.spawnflags & 1)
- {
- // attach
- if(self.spawnflags & 2)
- {
- setattachment(dst, src, self.message);
- }
- else
- {
- attach_sameorigin(dst, src, self.message);
- }
-
- dst.solid = SOLID_NOT; // solid doesn't work with attachment
- remove(self);
- }
- else
- {
- if(self.spawnflags & 2)
- {
- dst.movetype = MOVETYPE_FOLLOW;
- dst.aiment = src;
- // dst.punchangle = '0 0 0'; // keep unchanged
- dst.view_ofs = dst.origin;
- dst.v_angle = dst.angles;
- }
- else
- {
- follow_sameorigin(dst, src);
- }
-
- remove(self);
- }
-}
-
-void spawnfunc_misc_follow()
-{
- InitializeEntity(self, follow_init, INITPRIO_FINDTARGET);
-}
-
-
-
-void gamestart_use() {
- activator = self;
- SUB_UseTargets();
- remove(self);
-}
-
-void spawnfunc_trigger_gamestart() {
- self.use = gamestart_use;
- self.reset2 = spawnfunc_trigger_gamestart;
-
- if(self.wait)
- {
- self.think = self.use;
- self.nextthink = game_starttime + self.wait;
- }
- else
- InitializeEntity(self, gamestart_use, INITPRIO_FINDTARGET);
-}
-
-
-
-
-void target_voicescript_clear(entity pl)
-{
- pl.voicescript = world;
-}
-
-void target_voicescript_use()
-{
- if(activator.voicescript != self)
- {
- activator.voicescript = self;
- activator.voicescript_index = 0;
- activator.voicescript_nextthink = time + self.delay;
- }
-}
-
-void target_voicescript_next(entity pl)
-{
- entity vs;
- float i, n, dt;
-
- vs = pl.voicescript;
- if(!vs)
- return;
- if(vs.message == "")
- return;
- if (!IS_PLAYER(pl))
- return;
- if(gameover)
- return;
-
- if(time >= pl.voicescript_voiceend)
- {
- if(time >= pl.voicescript_nextthink)
- {
- // get the next voice...
- n = tokenize_console(vs.message);
-
- if(pl.voicescript_index < vs.cnt)
- i = pl.voicescript_index * 2;
- else if(n > vs.cnt * 2)
- i = ((pl.voicescript_index - vs.cnt) % ((n - vs.cnt * 2 - 1) / 2)) * 2 + vs.cnt * 2 + 1;
- else
- i = -1;
-
- if(i >= 0)
- {
- play2(pl, strcat(vs.netname, "/", argv(i), ".wav"));
- dt = stof(argv(i + 1));
- if(dt >= 0)
- {
- pl.voicescript_voiceend = time + dt;
- pl.voicescript_nextthink = pl.voicescript_voiceend + vs.wait * (0.5 + random());
- }
- else
- {
- pl.voicescript_voiceend = time - dt;
- pl.voicescript_nextthink = pl.voicescript_voiceend;
- }
-
- pl.voicescript_index += 1;
- }
- else
- {
- pl.voicescript = world; // stop trying then
- }
- }
- }
-}
-
-void spawnfunc_target_voicescript()
-{
- // netname: directory of the sound files
- // message: list of "sound file" duration "sound file" duration, a *, and again a list
- // foo1 4.1 foo2 4.0 foo3 -3.1 * fool1 1.1 fool2 7.1 fool3 9.1 fool4 3.7
- // Here, a - in front of the duration means that no delay is to be
- // added after this message
- // wait: average time between messages
- // delay: initial delay before the first message
-
- float i, n;
- self.use = target_voicescript_use;
-
- n = tokenize_console(self.message);
- self.cnt = n / 2;
- for(i = 0; i+1 < n; i += 2)
- {
- if(argv(i) == "*")
- {
- self.cnt = i / 2;
- ++i;
- }
- precache_sound(strcat(self.netname, "/", argv(i), ".wav"));
- }
-}
-
-
-
-void trigger_relay_teamcheck_use()
-{
- if(activator.team)
- {
- if(self.spawnflags & 2)
- {
- if(activator.team != self.team)
- SUB_UseTargets();
- }
- else
- {
- if(activator.team == self.team)
- SUB_UseTargets();
- }
- }
- else
- {
- if(self.spawnflags & 1)
- SUB_UseTargets();
- }
-}
-
-void trigger_relay_teamcheck_reset()
-{
- self.team = self.team_saved;
-}
-
-void spawnfunc_trigger_relay_teamcheck()
-{
- self.team_saved = self.team;
- self.use = trigger_relay_teamcheck_use;
- self.reset = trigger_relay_teamcheck_reset;
-}
-
-
-
-void trigger_disablerelay_use()
-{
- entity e;
-
- float a, b;
- a = b = 0;
-
- for(e = world; (e = find(e, targetname, self.target)); )
- {
- if(e.use == SUB_UseTargets)
- {
- e.use = SUB_DontUseTargets;
- ++a;
- }
- else if(e.use == SUB_DontUseTargets)
- {
- e.use = SUB_UseTargets;
- ++b;
- }
- }
-
- if((!a) == (!b))
- print("Invalid use of trigger_disablerelay: ", ftos(a), " relays were on, ", ftos(b), " relays were off!\n");
-}
-
-void spawnfunc_trigger_disablerelay()
-{
- self.use = trigger_disablerelay_use;
-}
-
-string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin)
-{
- float domatch, dotrigger, matchstart, l;
- string s, msg;
- entity oldself;
- string savemessage;
-
- magicear_matched = false;
-
- dotrigger = ((IS_PLAYER(source)) && (source.deadflag == DEAD_NO) && ((ear.radius == 0) || (vlen(source.origin - ear.origin) <= ear.radius)));
- domatch = ((ear.spawnflags & 32) || dotrigger);
-
- if (!domatch)
- return msgin;
-
- if (!msgin)
- {
- // we are in TUBA mode!
- if (!(ear.spawnflags & 256))
- return msgin;
-
- if(!W_Tuba_HasPlayed(source, ear.message, ear.movedir.x, !(ear.spawnflags & 512), ear.movedir.y, ear.movedir.z))
- return msgin;
-
- magicear_matched = true;
-
- if(dotrigger)
- {
- oldself = self;
- activator = source;
- self = ear;
- savemessage = self.message;
- self.message = string_null;
- SUB_UseTargets();
- self.message = savemessage;
- self = oldself;
- }
-
- if(ear.netname != "")
- return ear.netname;
-
- return msgin;
- }
-
- if(ear.spawnflags & 256) // ENOTUBA
- return msgin;
-
- if(privatesay)
- {
- if(ear.spawnflags & 4)
- return msgin;
- }
- else
- {
- if(!teamsay)
- if(ear.spawnflags & 1)
- return msgin;
- if(teamsay > 0)
- if(ear.spawnflags & 2)
- return msgin;
- if(teamsay < 0)
- if(ear.spawnflags & 8)
- return msgin;
- }
-
- matchstart = -1;
- l = strlen(ear.message);
-
- if(ear.spawnflags & 128)
- msg = msgin;
- else
- msg = strdecolorize(msgin);
-
- if(substring(ear.message, 0, 1) == "*")
- {
- if(substring(ear.message, -1, 1) == "*")
- {
- // two wildcards
- // as we need multi-replacement here...
- s = substring(ear.message, 1, -2);
- l -= 2;
- if(strstrofs(msg, s, 0) >= 0)
- matchstart = -2; // we use strreplace on s
- }
- else
- {
- // match at start
- s = substring(ear.message, 1, -1);
- l -= 1;
- if(substring(msg, -l, l) == s)
- matchstart = strlen(msg) - l;
- }
- }
- else
- {
- if(substring(ear.message, -1, 1) == "*")
- {
- // match at end
- s = substring(ear.message, 0, -2);
- l -= 1;
- if(substring(msg, 0, l) == s)
- matchstart = 0;
- }
- else
- {
- // full match
- s = ear.message;
- if(msg == ear.message)
- matchstart = 0;
- }
- }
-
- if(matchstart == -1) // no match
- return msgin;
-
- magicear_matched = true;
-
- if(dotrigger)
- {
- oldself = self;
- activator = source;
- self = ear;
- savemessage = self.message;
- self.message = string_null;
- SUB_UseTargets();
- self.message = savemessage;
- self = oldself;
- }
-
- if(ear.spawnflags & 16)
- {
- return ear.netname;
- }
- else if(ear.netname != "")
- {
- if(matchstart < 0)
- return strreplace(s, ear.netname, msg);
- else
- return strcat(
- substring(msg, 0, matchstart),
- ear.netname,
- substring(msg, matchstart + l, -1)
- );
- }
- else
- return msgin;
-}
-
-string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin)
-{
- entity ear;
- string msgout;
- for(ear = magicears; ear; ear = ear.enemy)
- {
- msgout = trigger_magicear_processmessage(ear, source, teamsay, privatesay, msgin);
- if(!(ear.spawnflags & 64))
- if(magicear_matched)
- return msgout;
- msgin = msgout;
- }
- return msgin;
-}
-
-void spawnfunc_trigger_magicear()
-{
- self.enemy = magicears;
- magicears = self;
-
- // actually handled in "say" processing
- // spawnflags:
- // 1 = ignore say
- // 2 = ignore teamsay
- // 4 = ignore tell
- // 8 = ignore tell to unknown player
- // 16 = let netname replace the whole message (otherwise, netname is a word replacement if set)
- // 32 = perform the replacement even if outside the radius or dead
- // 64 = continue replacing/triggering even if this one matched
- // 128 = don't decolorize message before matching
- // 256 = message is a tuba note sequence (pitch.duration pitch.duration ...)
- // 512 = tuba notes must be exact right pitch, no transposing
- // message: either
- // *pattern*
- // or
- // *pattern
- // or
- // pattern*
- // or
- // pattern
- // netname:
- // if set, replacement for the matched text
- // radius:
- // "hearing distance"
- // target:
- // what to trigger
- // movedir:
- // for spawnflags 256, defines 'instrument+1 mintempo maxtempo' (zero component doesn't matter)
-
- self.movedir_x -= 1; // map to tuba instrument numbers
-}
-
-void relay_activators_use()
-{
- entity trg, os;
-
- os = self;
-
- for(trg = world; (trg = find(trg, targetname, os.target)); )
- {
- self = trg;
- if (trg.setactive)
- trg.setactive(os.cnt);
- else
- {
- //bprint("Not using setactive\n");
- if(os.cnt == ACTIVE_TOGGLE)
- if(trg.active == ACTIVE_ACTIVE)
- trg.active = ACTIVE_NOT;
- else
- trg.active = ACTIVE_ACTIVE;
- else
- trg.active = os.cnt;
- }
- }
- self = os;
-}
-
-void spawnfunc_relay_activate()
-{
- self.cnt = ACTIVE_ACTIVE;
- self.use = relay_activators_use;
-}
-
-void spawnfunc_relay_deactivate()
-{
- self.cnt = ACTIVE_NOT;
- self.use = relay_activators_use;
-}
-
-void spawnfunc_relay_activatetoggle()
-{
- self.cnt = ACTIVE_TOGGLE;
- self.use = relay_activators_use;
-}
-
-void spawnfunc_target_changelevel_use()
-{
- if(self.gametype != "")
- MapInfo_SwitchGameType(MapInfo_Type_FromString(self.gametype));
-
- if (self.chmap == "")
- localcmd("endmatch\n");
- else
- localcmd(strcat("changelevel ", self.chmap, "\n"));
-}
-
-void spawnfunc_target_changelevel()
-{
- self.use = spawnfunc_target_changelevel_use;
-}
+++ /dev/null
-#ifndef G_TRIGGERS_H
-#define G_TRIGGERS_H
-
-void SUB_DontUseTargets();
-
-
-void() SUB_UseTargets;
-
-void DelayThink();
-
-/*
-==============================
-SUB_UseTargets
-
-the global "activator" should be set to the entity that initiated the firing.
-
-If self.delay is set, a DelayedUse entity will be created that will actually
-do the SUB_UseTargets after that many seconds have passed.
-
-Centerprints any self.message to the activator.
-
-Removes all entities with a targetname that match self.killtarget,
-and removes them, so some events can remove other triggers.
-
-Search for (string)targetname in all entities that
-match (string)self.target and call their .use function
-
-==============================
-*/
-void SUB_UseTargets();
-
-
-//=============================================================================
-
-const float SPAWNFLAG_NOMESSAGE = 1;
-const float SPAWNFLAG_NOTOUCH = 1;
-
-// the wait time has passed, so set back up for another activation
-void multi_wait();
-
-
-// the trigger was just touched/killed/used
-// self.enemy should be set to the activator so it can be held through a delay
-// so wait for the delay time before firing
-void multi_trigger();
-
-void multi_use();
-
-void multi_touch();
-
-void multi_eventdamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force);
-
-void multi_reset();
-
-/*QUAKED spawnfunc_trigger_multiple (.5 .5 .5) ? notouch
-Variable sized repeatable trigger. Must be targeted at one or more entities. If "health" is set, the trigger must be killed to activate each time.
-If "delay" is set, the trigger waits some time after activating before firing.
-"wait" : Seconds between triggerings. (.2 default)
-If notouch is set, the trigger is only fired by other entities, not by touching.
-NOTOUCH has been obsoleted by spawnfunc_trigger_relay!
-sounds
-1) secret
-2) beep beep
-3) large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_multiple();
-
-
-/*QUAKED spawnfunc_trigger_once (.5 .5 .5) ? notouch
-Variable sized trigger. Triggers once, then removes itself. You must set the key "target" to the name of another object in the level that has a matching
-"targetname". If "health" is set, the trigger must be killed to activate.
-If notouch is set, the trigger is only fired by other entities, not by touching.
-if "killtarget" is set, any objects that have a matching "target" will be removed when the trigger is fired.
-if "angle" is set, the trigger will only fire when someone is facing the direction of the angle. Use "360" for an angle of 0.
-sounds
-1) secret
-2) beep beep
-3) large switch
-4)
-set "message" to text string
-*/
-void spawnfunc_trigger_once();
-
-//=============================================================================
-
-/*QUAKED spawnfunc_trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8)
-This fixed size trigger cannot be touched, it can only be fired by other events. It can contain killtargets, targets, delays, and messages.
-*/
-void spawnfunc_trigger_relay();
-
-void delay_use();
-
-void delay_reset();
-
-void spawnfunc_trigger_delay();
-
-//=============================================================================
-
-
-void counter_use();
-
-void counter_reset();
-
-/*QUAKED spawnfunc_trigger_counter (.5 .5 .5) ? nomessage
-Acts as an intermediary for an action that takes multiple inputs.
-
-If nomessage is not set, t will print "1 more.. " etc when triggered and "sequence complete" when finished.
-
-After the counter has been triggered "count" times (default 2), it will fire all of it's targets and remove itself.
-*/
-void spawnfunc_trigger_counter();
-
-void trigger_hurt_use();
-
-.float triggerhurttime;
-void trigger_hurt_touch();
-
-/*QUAKED spawnfunc_trigger_hurt (.5 .5 .5) ?
-Any object touching this will be hurt
-set dmg to damage amount
-defalt dmg = 5
-*/
-.entity trigger_hurt_next;
-entity trigger_hurt_last;
-entity trigger_hurt_first;
-void spawnfunc_trigger_hurt();
-
-float tracebox_hits_trigger_hurt(vector start, vector mi, vector ma, vector end);
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e
-//
-//////////////////////////////////////////////////////////////
-
-.float triggerhealtime;
-void trigger_heal_touch();
-
-void spawnfunc_trigger_heal();
-
-
-//////////////////////////////////////////////////////////////
-//
-//
-//
-//End trigger_heal
-//
-//////////////////////////////////////////////////////////////
-
-.entity trigger_gravity_check;
-void trigger_gravity_remove(entity own);
-void trigger_gravity_check_think();
-
-void trigger_gravity_use();
-
-void trigger_gravity_touch();
-
-void spawnfunc_trigger_gravity();
-
-//=============================================================================
-
-// TODO add a way to do looped sounds with sound(); then complete this entity
-.float volume, atten;
-void target_speaker_use_off();
-void target_speaker_use_activator();
-void target_speaker_use_on();
-void target_speaker_use_off();
-void target_speaker_reset();
-
-void spawnfunc_target_speaker();
-
-
-void spawnfunc_func_stardust();
-
-.string bgmscript;
-.float bgmscriptattack;
-.float bgmscriptdecay;
-.float bgmscriptsustain;
-.float bgmscriptrelease;
-float pointparticles_SendEntity(entity to, float fl);
-
-void pointparticles_use();
-
-void pointparticles_think();
-
-void pointparticles_reset();
-
-void spawnfunc_func_pointparticles();
-
-void spawnfunc_func_sparks();
-
-float rainsnow_SendEntity(entity to, float sf);
-
-/*QUAKED spawnfunc_func_rain (0 .5 .8) ?
-This is an invisible area like a trigger, which rain falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -700', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_rain();
-
-
-/*QUAKED spawnfunc_func_snow (0 .5 .8) ?
-This is an invisible area like a trigger, which snow falls inside of.
-
-Keys:
-"velocity"
- falling direction (should be something like '0 0 -300', use the X and Y velocity for wind)
-"cnt"
- sets color of rain (default 12 - white)
-"count"
- adjusts density, this many particles fall every second for a 1024x1024 area, default is 2000
-*/
-void spawnfunc_func_snow();
-
-.float modelscale;
-void misc_laser_aim();
-
-void misc_laser_init();
-
-.entity pusher;
-void misc_laser_think();
-
-float laser_SendEntity(entity to, float fl);
-
-/*QUAKED spawnfunc_misc_laser (.5 .5 .5) ? START_ON DEST_IS_FIXED
-Any object touching the beam will be hurt
-Keys:
-"target"
- spawnfunc_target_position where the laser ends
-"mdl"
- name of beam end effect to use
-"colormod"
- color of the beam (default: red)
-"dmg"
- damage per second (-1 for a laser that kills immediately)
-*/
-void laser_use();
-
-void laser_reset();
-
-void spawnfunc_misc_laser();
-
-// tZorks trigger impulse / gravity
-.float radius;
-.float falloff;
-.float strength;
-.float lastpushtime;
-
-// targeted (directional) mode
-void trigger_impulse_touch1();
-
-// Directionless (accelerator/decelerator) mode
-void trigger_impulse_touch2();
-
-// Spherical (gravity/repulsor) mode
-void trigger_impulse_touch3();
-
-/*QUAKED spawnfunc_trigger_impulse (.5 .5 .5) ?
--------- KEYS --------
-target : If this is set, this points to the spawnfunc_target_position to which the player will get pushed.
- If not, this trigger acts like a damper/accelerator field.
-
-strength : This is how mutch force to add in the direction of .target each second
- when .target is set. If not, this is hoe mutch to slow down/accelerate
- someting cought inside this trigger. (1=no change, 0,5 half speed rougthly each tic, 2 = doubble)
-
-radius : If set, act as a spherical device rather then a liniar one.
-
-falloff : 0 = none, 1 = liniar, 2 = inverted liniar
-
--------- NOTES --------
-Use a brush textured with common/origin in the trigger entity to determine the origin of the force
-in directional and sperical mode. For damper/accelerator mode this is not nessesary (and has no effect).
-*/
-
-void spawnfunc_trigger_impulse();
-
-/*QUAKED spawnfunc_trigger_flipflop (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ENABLED
-"Flip-flop" trigger gate... lets only every second trigger event through
-*/
-void flipflop_use();
-
-void spawnfunc_trigger_flipflop();
-
-/*QUAKED spawnfunc_trigger_monoflop (.5 .5 .5) (-8 -8 -8) (8 8 8)
-"Mono-flop" trigger gate... turns one trigger event into one "on" and one "off" event, separated by a delay of "wait"
-*/
-void monoflop_use();
-void monoflop_fixed_use();
-
-void monoflop_think();
-
-void monoflop_reset();
-
-void spawnfunc_trigger_monoflop();
-
-void multivibrator_send();
-
-void multivibrator_toggle();
-
-void multivibrator_reset();
-
-/*QUAKED trigger_multivibrator (.5 .5 .5) (-8 -8 -8) (8 8 8) START_ON
-"Multivibrator" trigger gate... repeatedly sends trigger events. When triggered, turns on or off.
--------- KEYS --------
-target: trigger all entities with this targetname when it goes off
-targetname: name that identifies this entity so it can be triggered; when off, it always uses the OFF state
-phase: offset of the timing
-wait: "on" cycle time (default: 1)
-respawntime: "off" cycle time (default: same as wait)
--------- SPAWNFLAGS --------
-START_ON: assume it is already turned on (when targeted)
-*/
-void spawnfunc_trigger_multivibrator();
-
-
-void follow_init();
-
-void spawnfunc_misc_follow();
-
-
-
-void gamestart_use();
-
-void spawnfunc_trigger_gamestart();
-
-
-
-
-.entity voicescript; // attached voice script
-.float voicescript_index; // index of next voice, or -1 to use the randomized ones
-.float voicescript_nextthink; // time to play next voice
-.float voicescript_voiceend; // time when this voice ends
-
-void target_voicescript_clear(entity pl);
-
-void target_voicescript_use();
-
-void target_voicescript_next(entity pl);
-
-void spawnfunc_target_voicescript();
-
-
-
-void trigger_relay_teamcheck_use();
-
-void trigger_relay_teamcheck_reset();
-
-void spawnfunc_trigger_relay_teamcheck();
-
-
-
-void trigger_disablerelay_use();
-
-void spawnfunc_trigger_disablerelay();
-
-float magicear_matched;
-float W_Tuba_HasPlayed(entity pl, string melody, float instrument, float ignorepitch, float mintempo, float maxtempo);
-string trigger_magicear_processmessage(entity ear, entity source, float teamsay, entity privatesay, string msgin);
-
-entity magicears;
-string trigger_magicear_processmessage_forallears(entity source, float teamsay, entity privatesay, string msgin);
-
-void spawnfunc_trigger_magicear();
-
-void relay_activators_use();
-
-void spawnfunc_relay_activate();
-
-void spawnfunc_relay_deactivate();
-
-void spawnfunc_relay_activatetoggle();
-
-.string chmap, gametype;
-void spawnfunc_target_changelevel_use();
-
-void spawnfunc_target_changelevel();
-#endif
#include "ipban.qh"
#include "race.qh"
#include "antilag.qh"
- #include "secret.qh"
#endif
const float LATENCY_THINKRATE = 10;
#include "../warpzonelib/util_server.qh"
#include "../common/util.qh"
#include "../common/monsters/monsters.qh"
+ #include "../common/triggers/subs.qh"
#include "defs.qh"
#include "../common/notifications.qh"
#include "item_key.qh"
EXACTTRIGGER_INIT;
self.touch = trigger_keylock_touch;
-};
-
-
+}
/**
* list of key names.
*/
+#ifdef SVQC
string item_keys_names[ITEM_KEY_MAX];
/**
*/
string item_keys_keylist(float keylist);
#endif
+
+#endif
#include "defs.qh"
#include "../common/notifications.qh"
#include "../common/deathtypes.qh"
+ #include "../common/triggers/subs.qh"
#include "mutators/mutators_include.qh"
#include "tturrets/include/turrets_early.qh"
#include "../common/mapinfo.qh"
return v;
}
-
-
-float sound_allowed(float _dest, entity e)
+float sound_allowed(float destin, entity e)
{
// sounds from world may always pass
for (;;)
break;
}
// sounds to self may always pass
- if (_dest == MSG_ONE)
+ if (destin == MSG_ONE)
if (e == msg_entity)
return true;
// sounds by players can be removed
}
#undef sound
-void sound(entity e, float chan, string samp, float vol, float _atten)
+void sound(entity e, float chan, string samp, float vol, float attenu)
{
if (!sound_allowed(MSG_BROADCAST, e))
return;
- sound7(e, chan, samp, vol, _atten, 0, 0);
+ sound7(e, chan, samp, vol, attenu, 0, 0);
}
-void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float _atten)
+void soundtoat(float _dest, entity e, vector o, float chan, string samp, float vol, float attenu)
{
float entno, idx;
float sflags;
sflags = 0;
- _atten = floor(_atten * 64);
+ attenu = floor(attenu * 64);
vol = floor(vol * 255);
if (vol != 255)
sflags |= SND_VOLUME;
- if (_atten != 64)
+ if (attenu != 64)
sflags |= SND_ATTENUATION;
if (entno >= 8192 || chan < 0 || chan > 7)
sflags |= SND_LARGEENTITY;
if (sflags & SND_VOLUME)
WriteByte(_dest, vol);
if (sflags & SND_ATTENUATION)
- WriteByte(_dest, _atten);
+ WriteByte(_dest, attenu);
if (sflags & SND_LARGEENTITY)
{
WriteShort(_dest, entno);
e.uncustomizeentityforclient_set = !!uncustomizer;
}
-
void Net_LinkEntity(entity e, float docull, float dt, float(entity, float) sendfunc)
{
vector mi, ma;
void WarpZone_traceline_antilag (entity source, vector v1, vector v2, float nomonst, entity forent, float lag);
-#define IFTARGETED if(!self.nottargeted && self.targetname != "")
-
#define ITEM_TOUCH_NEEDKILL() (((trace_dpstartcontents | trace_dphitcontents) & DPCONTENTS_NODROP) || (trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY))
#define ITEM_DAMAGE_NEEDKILL(dt) (((dt) == DEATH_HURTTRIGGER) || ((dt) == DEATH_SLIME) || ((dt) == DEATH_LAVA) || ((dt) == DEATH_SWAMP))
+#include "../../common/triggers/subs.qh"
+
// random functions
void assault_objective_use()
{
+#include "../../common/triggers/subs.qh"
+
void dom_EventLog(string mode, float team_before, entity actor) // use an alias for easy changing and quick editing later
{
if(autocvar_sv_eventlog)
+#include "../../common/triggers/subs.qh"
+
float autocvar_g_onslaught_spawn_at_controlpoints;
float autocvar_g_onslaught_spawn_at_generator;
float autocvar_g_onslaught_cp_proxydecap;
+#include "../../common/triggers/target/music.qh"
+
float buffs_BuffModel_Customize()
{
entity player, myowner;
+#include "../../common/triggers/target/music.qh"
+
.entity nade_spawnloc;
void nade_timer_think()
#include "../playerdemo.qh"
#include "../round_handler.qh"
#include "../item_key.qh"
- #include "../secret.qh"
#include "../pathlib/pathlib.qh"
#include "../tturrets/include/turrets.qh"
#include "../vehicles/vehicles.qh"
// ctf.qc
// domination.qc
ent_cs.qc
-func_breakable.qc
g_casings.qc
g_damage.qc
g_hook.qc
g_models.qc
g_subs.qc
g_tetris.qc
-g_triggers.qc
g_violence.qc
g_world.qc
ipban.qc
// runematch.qc
scores.qc
scores_rules.qc
-secret.qc
spawnpoints.qc
steerlib.qc
sv_main.qc
-target_music.qc
-target_spawn.qc
teamplay.qc
t_halflife.qc
t_items.qc
-t_jumppads.qc
-t_plats.qc
t_quake3.qc
t_quake.qc
-t_swamp.qc
t_teleporters.qc
waypointsprites.qc
../common/physics.qc
../common/playerstats.qc
../common/test.qc
+../common/triggers/include.qc
../common/urllib.qc
../common/util.qc
../common/weapons/config.qc
#include "race.qh"
+#include "../common/triggers/subs.qh"
void W_Porto_Fail(float failhard);
+++ /dev/null
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "../common/util.qh"
- #include "defs.qh"
- #include "secret.qh"
-#endif
-
-void secrets_setstatus() {
- self.stat_secrets_total = secrets_total;
- self.stat_secrets_found = secrets_found;
-}
-
-/**
- * A secret has been found (maybe :P)
- */
-void trigger_secret_touch() {
- // only a player can trigger this
- if (!IS_PLAYER(other))
- return;
-
- // update secrets found counter
- secrets_found += 1;
- //print("Secret found: ", ftos(secret_counter.cnt), "/");
- //print(ftos(secret_counter.count), "\n");
-
- // centerprint message (multi_touch() doesn't always call centerprint())
- centerprint(other, self.message);
- self.message = "";
-
- // handle normal trigger features
- multi_touch();
- remove(self);
-}
-
-/*QUAKED trigger_secret (.5 .5 .5) ?
-Variable sized secret trigger. Can be targeted at one or more entities.
-Basically, it's a trigger_once (with restrictions, see notes) that additionally updates the number of secrets found.
--------- KEYS --------
-sounds: 1 to play misc/secret.wav, 2 to play misc/talk.wav, 3 to play misc/trigger1.wav (default: 1)
-noise: path to sound file, if you want to play something else
-target: trigger all entities with this targetname when triggered
-message: print this message to the player who activated the trigger instead of the standard 'You found a secret!'
-killtarget: remove all entities with this targetname when triggered
--------- NOTES --------
-You should create a common/trigger textured brush covering the entrance to a secret room/area.
-Trigger secret can only be trigger by a player's touch and can not be a target itself.
-*/
-void spawnfunc_trigger_secret() {
- // FIXME: should it be disabled in most modes?
-
- // update secrets count
- secrets_total += 1;
-
- // add default message
- if (self.message == "")
- self.message = "You found a secret!";
-
- // set default sound
- if (self.noise == "")
- if (!self.sounds)
- self.sounds = 1; // misc/secret.wav
-
- // this entity can't be a target itself!!!!
- self.targetname = "";
-
- // you can't just shoot a room to find it, can you?
- self.health = 0;
-
- // a secret can not be delayed
- self.delay = 0;
-
- // convert this trigger to trigger_once
- self.classname = "trigger_once";
- spawnfunc_trigger_once();
-
- // take over the touch() function, so we can mark secret as found
- self.touch = trigger_secret_touch;
- // ignore triggering;
- self.use = func_null;
-}
-
+++ /dev/null
-#ifndef SECRET_H
-#define SECRET_H
-
-/**
- * Total number of secrets on the map.
- */
-float secrets_total;
-
-/**
- * Total numbe of secrets found on the map.
- */
-float secrets_found;
-
-
-.float stat_secrets_total;
-.float stat_secrets_found;
-
-/**
- * update secrets status.
- */
-void secrets_setstatus();
-#endif
#include "../warpzonelib/util_server.qh"
#include "../common/constants.qh"
#include "../common/teams.qh"
+ #include "../common/triggers/subs.qh"
#include "../common/util.qh"
#include "autocvars.qh"
#include "constants.qh"
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "defs.qh"
- #include "vehicles/vehicles_def.qh"
-#endif
-#include "../warpzonelib/util_server.qh"
-
-#include "miscfunctions.qh"
-#ifdef CSQC
-#define EXACTTRIGGER_TOUCH if(WarpZoneLib_ExactTrigger_Touch()) return
-#endif
-
-.float ladder_time;
-.entity ladder_entity;
-
-#ifdef SVQC
.float roomtype;
.float radius;
.float pitch;
void spawnfunc_env_sound() {}
void spawnfunc_light_spot() {}
void spawnfunc_func_healthcharger() {}
-#endif
-
-void func_ladder_touch()
-{
-#ifdef SVQC
- if (!other.iscreature)
- return;
- if (other.vehicle_flags & VHF_ISVEHICLE)
- return;
-#endif
-#ifdef CSQC
- if(other.classname != "csqcmodel")
- return;
-#endif
-
- EXACTTRIGGER_TOUCH;
-
- other.ladder_time = time + 0.1;
- other.ladder_entity = self;
-}
-
-#ifdef SVQC
-float func_ladder_send(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_LADDER);
-
- WriteString(MSG_ENTITY, self.classname);
- WriteByte(MSG_ENTITY, self.warpzone_isboxy);
- WriteByte(MSG_ENTITY, self.skin);
- WriteByte(MSG_ENTITY, self.speed);
- WriteByte(MSG_ENTITY, self.scale);
- WriteCoord(MSG_ENTITY, self.origin_x);
- WriteCoord(MSG_ENTITY, self.origin_y);
- WriteCoord(MSG_ENTITY, self.origin_z);
-
- WriteCoord(MSG_ENTITY, self.mins_x);
- WriteCoord(MSG_ENTITY, self.mins_y);
- WriteCoord(MSG_ENTITY, self.mins_z);
- WriteCoord(MSG_ENTITY, self.maxs_x);
- WriteCoord(MSG_ENTITY, self.maxs_y);
- WriteCoord(MSG_ENTITY, self.maxs_z);
-
- WriteCoord(MSG_ENTITY, self.movedir_x);
- WriteCoord(MSG_ENTITY, self.movedir_y);
- WriteCoord(MSG_ENTITY, self.movedir_z);
-
- WriteCoord(MSG_ENTITY, self.angles_x);
- WriteCoord(MSG_ENTITY, self.angles_y);
- WriteCoord(MSG_ENTITY, self.angles_z);
-
- return true;
-}
-
-void func_ladder_link()
-{
- Net_LinkEntity(self, false, 0, func_ladder_send);
-}
-
-void spawnfunc_func_ladder()
-{
- EXACTTRIGGER_INIT;
- self.touch = func_ladder_touch;
-
- func_ladder_link();
-}
-
-void spawnfunc_func_water()
-{
- EXACTTRIGGER_INIT;
- self.touch = func_ladder_touch;
-
- func_ladder_link();
-}
-
-#elif defined(CSQC)
-.float speed;
-
-void func_ladder_draw()
-{
- float dt = time - self.move_time;
- self.move_time = time;
- if(dt <= 0) { return; }
-
- trigger_touch_generic(func_ladder_touch);
-}
-
-void ent_func_ladder()
-{
- self.classname = strzone(ReadString());
- self.warpzone_isboxy = ReadByte();
- self.skin = ReadByte();
- self.speed = ReadByte();
- self.scale = ReadByte();
- self.origin_x = ReadCoord();
- self.origin_y = ReadCoord();
- self.origin_z = ReadCoord();
- setorigin(self, self.origin);
- self.mins_x = ReadCoord();
- self.mins_y = ReadCoord();
- self.mins_z = ReadCoord();
- self.maxs_x = ReadCoord();
- self.maxs_y = ReadCoord();
- self.maxs_z = ReadCoord();
- setsize(self, self.mins, self.maxs);
- self.movedir_x = ReadCoord();
- self.movedir_y = ReadCoord();
- self.movedir_z = ReadCoord();
- self.angles_x = ReadCoord();
- self.angles_y = ReadCoord();
- self.angles_z = ReadCoord();
-
- self.solid = SOLID_TRIGGER;
- self.draw = func_ladder_draw;
- self.drawmask = MASK_NORMAL;
- self.move_time = time;
-}
-#endif
#include "../common/constants.qh"
#include "../common/util.qh"
#include "../common/monsters/monsters.qh"
+ #include "../common/triggers/subs.qh"
#include "../common/weapons/weapons.qh"
#include "weapons/weaponsystem.qh"
#include "t_items.qh"
+++ /dev/null
-#include "t_jumppads.qh"
-
-#ifdef SVQC
-
-void trigger_push_use()
-{
- if(teamplay)
- {
- self.team = activator.team;
- self.SendFlags |= 2;
- }
-}
-#endif
-
-/*
- trigger_push_calculatevelocity
-
- Arguments:
- org - origin of the object which is to be pushed
- tgt - target entity (can be either a point or a model entity; if it is
- the latter, its midpoint is used)
- ht - jump height, measured from the higher one of org and tgt's midpoint
-
- Returns: velocity for the jump
- the global trigger_push_calculatevelocity_flighttime is set to the total
- jump time
- */
-
-vector trigger_push_calculatevelocity(vector org, entity tgt, float ht)
-{
- float grav, sdist, zdist, vs, vz, jumpheight;
- vector sdir, torg;
-
- torg = tgt.origin + (tgt.mins + tgt.maxs) * 0.5;
-
- grav = PHYS_GRAVITY;
- if(PHYS_ENTGRAVITY(other))
- grav *= PHYS_ENTGRAVITY(other);
-
- zdist = torg.z - org.z;
- sdist = vlen(torg - org - zdist * '0 0 1');
- sdir = normalize(torg - org - zdist * '0 0 1');
-
- // how high do we need to push the player?
- jumpheight = fabs(ht);
- if(zdist > 0)
- jumpheight = jumpheight + zdist;
-
- /*
- STOP.
-
- You will not understand the following equations anyway...
- But here is what I did to get them.
-
- I used the functions
-
- s(t) = t * vs
- z(t) = t * vz - 1/2 grav t^2
-
- and solved for:
-
- s(ti) = sdist
- z(ti) = zdist
- max(z, ti) = jumpheight
-
- From these three equations, you will find the three parameters vs, vz
- and ti.
- */
-
- // push him so high...
- vz = sqrt(fabs(2 * grav * jumpheight)); // NOTE: sqrt(positive)!
-
- // we start with downwards velocity only if it's a downjump and the jump apex should be outside the jump!
- if(ht < 0)
- if(zdist < 0)
- vz = -vz;
-
- vector solution;
- solution = solve_quadratic(0.5 * grav, -vz, zdist); // equation "z(ti) = zdist"
- // ALWAYS solvable because jumpheight >= zdist
- if(!solution.z)
- solution.y = solution.x; // just in case it is not solvable due to roundoff errors, assume two equal solutions at their center (this is mainly for the usual case with ht == 0)
- if(zdist == 0)
- solution.x = solution.y; // solution_x is 0 in this case, so don't use it, but rather use solution_y (which will be sqrt(0.5 * jumpheight / grav), actually)
-
- if(zdist < 0)
- {
- // down-jump
- if(ht < 0)
- {
- // almost straight line type
- // jump apex is before the jump
- // we must take the larger one
- trigger_push_calculatevelocity_flighttime = solution.y;
- }
- else
- {
- // regular jump
- // jump apex is during the jump
- // we must take the larger one too
- trigger_push_calculatevelocity_flighttime = solution.y;
- }
- }
- else
- {
- // up-jump
- if(ht < 0)
- {
- // almost straight line type
- // jump apex is after the jump
- // we must take the smaller one
- trigger_push_calculatevelocity_flighttime = solution.x;
- }
- else
- {
- // regular jump
- // jump apex is during the jump
- // we must take the larger one
- trigger_push_calculatevelocity_flighttime = solution.y;
- }
- }
- vs = sdist / trigger_push_calculatevelocity_flighttime;
-
- // finally calculate the velocity
- return sdir * vs + '0 0 1' * vz;
-}
-
-void trigger_push_touch()
-{
- if (self.active == ACTIVE_NOT)
- return;
-
-#ifdef SVQC
- if (!isPushable(other))
- return;
-#endif
-
- if(self.team)
- if(((self.spawnflags & 4) == 0) == (DIFF_TEAM(self, other)))
- return;
-
- EXACTTRIGGER_TOUCH;
-
- if(self.enemy)
- {
- other.velocity = trigger_push_calculatevelocity(other.origin, self.enemy, self.height);
- }
- else if(self.target)
- {
- entity e;
- RandomSelection_Init();
- for(e = world; (e = find(e, targetname, self.target)); )
- {
- if(e.cnt)
- RandomSelection_Add(e, 0, string_null, e.cnt, 1);
- else
- RandomSelection_Add(e, 0, string_null, 1, 1);
- }
- other.velocity = trigger_push_calculatevelocity(other.origin, RandomSelection_chosen_ent, self.height);
- }
- else
- {
- other.velocity = self.movedir;
- }
-
- UNSET_ONGROUND(other);
-
-#ifdef SVQC
- if (IS_PLAYER(other))
- {
- // reset tracking of oldvelocity for impact damage (sudden velocity changes)
- other.oldvelocity = other.velocity;
-
- if(self.pushltime < time) // prevent "snorring" sound when a player hits the jumppad more than once
- {
- // flash when activated
- pointparticles(particleeffectnum("jumppad_activate"), other.origin, other.velocity, 1);
- sound (other, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
- self.pushltime = time + 0.2;
- }
- if(IS_REAL_CLIENT(other) || IS_BOT_CLIENT(other))
- {
- bool found = false;
- for(int i = 0; i < other.jumppadcount && i < NUM_JUMPPADSUSED; ++i)
- if(other.(jumppadsused[i]) == self)
- found = true;
- if(!found)
- {
- other.(jumppadsused[other.jumppadcount % NUM_JUMPPADSUSED]) = self;
- other.jumppadcount = other.jumppadcount + 1;
- }
-
- if(IS_REAL_CLIENT(other))
- {
- if(self.message)
- centerprint(other, self.message);
- }
- else
- other.lastteleporttime = time;
-
- if (other.deadflag == DEAD_NO)
- animdecide_setaction(other, ANIMACTION_JUMP, true);
- }
- else
- other.jumppadcount = true;
-
- // reset tracking of who pushed you into a hazard (for kill credit)
- other.pushltime = 0;
- other.istypefrag = 0;
- }
-
- if(self.enemy.target)
- {
- entity oldself;
- oldself = self;
- activator = other;
- self = self.enemy;
- SUB_UseTargets();
- self = oldself;
- }
-
- if (other.flags & FL_PROJECTILE)
- {
- other.angles = vectoangles (other.velocity);
- switch(other.movetype)
- {
- case MOVETYPE_FLY:
- other.movetype = MOVETYPE_TOSS;
- other.gravity = 1;
- break;
- case MOVETYPE_BOUNCEMISSILE:
- other.movetype = MOVETYPE_BOUNCE;
- other.gravity = 1;
- break;
- }
- UpdateCSQCProjectile(other);
- }
-
- if (self.spawnflags & PUSH_ONCE)
- {
- self.touch = func_null;
- self.think = SUB_Remove;
- self.nextthink = time;
- }
-#endif
-}
-
-.vector dest;
-#ifdef SVQC
-void trigger_push_link();
-void trigger_push_updatelink();
-#endif
-void trigger_push_findtarget()
-{
- entity t;
- vector org;
-
- // first calculate a typical start point for the jump
- org = (self.absmin + self.absmax) * 0.5;
- org.z = self.absmax.z - PL_MIN_z;
-
- if (self.target)
- {
- float n = 0;
- for(t = world; (t = find(t, targetname, self.target)); )
- {
- ++n;
-#ifdef SVQC
- entity e = spawn();
- setorigin(e, org);
- setsize(e, PL_MIN, PL_MAX);
- e.velocity = trigger_push_calculatevelocity(org, t, self.height);
- tracetoss(e, e);
- if(e.movetype == MOVETYPE_NONE)
- waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
- remove(e);
-#endif
- }
-
- if(!n)
- {
- // no dest!
-#ifdef SVQC
- objerror ("Jumppad with nonexistant target");
-#endif
- return;
- }
- else if(n == 1)
- {
- // exactly one dest - bots love that
- self.enemy = find(world, targetname, self.target);
- }
- else
- {
- // have to use random selection every single time
- self.enemy = world;
- }
- }
-#ifdef SVQC
- else
- {
- entity e = spawn();
- setorigin(e, org);
- setsize(e, PL_MIN, PL_MAX);
- e.velocity = self.movedir;
- tracetoss(e, e);
- waypoint_spawnforteleporter(self, trace_endpos, vlen(trace_endpos - org) / vlen(e.velocity));
- remove(e);
- }
-
- trigger_push_link();
- defer(0.1, trigger_push_updatelink);
-#endif
-}
-
-#ifdef SVQC
-float trigger_push_send(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_PUSH);
- WriteByte(MSG_ENTITY, sf);
-
- if(sf & 1)
- {
- WriteString(MSG_ENTITY, self.target);
- WriteByte(MSG_ENTITY, self.team);
- WriteInt24_t(MSG_ENTITY, self.spawnflags);
- WriteByte(MSG_ENTITY, self.active);
- WriteByte(MSG_ENTITY, self.warpzone_isboxy);
- WriteByte(MSG_ENTITY, self.height);
- WriteByte(MSG_ENTITY, self.scale);
- WriteCoord(MSG_ENTITY, self.origin_x);
- WriteCoord(MSG_ENTITY, self.origin_y);
- WriteCoord(MSG_ENTITY, self.origin_z);
-
- WriteCoord(MSG_ENTITY, self.mins_x);
- WriteCoord(MSG_ENTITY, self.mins_y);
- WriteCoord(MSG_ENTITY, self.mins_z);
- WriteCoord(MSG_ENTITY, self.maxs_x);
- WriteCoord(MSG_ENTITY, self.maxs_y);
- WriteCoord(MSG_ENTITY, self.maxs_z);
-
- WriteCoord(MSG_ENTITY, self.movedir_x);
- WriteCoord(MSG_ENTITY, self.movedir_y);
- WriteCoord(MSG_ENTITY, self.movedir_z);
-
- WriteCoord(MSG_ENTITY, self.angles_x);
- WriteCoord(MSG_ENTITY, self.angles_y);
- WriteCoord(MSG_ENTITY, self.angles_z);
- }
-
- if(sf & 2)
- {
- WriteByte(MSG_ENTITY, self.team);
- WriteByte(MSG_ENTITY, self.active);
- }
-
- return true;
-}
-
-void trigger_push_updatelink()
-{
- self.SendFlags |= 1;
-}
-
-void trigger_push_link()
-{
- Net_LinkEntity(self, false, 0, trigger_push_send);
-}
-#endif
-#ifdef SVQC
-/*
- * ENTITY PARAMETERS:
- *
- * target: target of jump
- * height: the absolute value is the height of the highest point of the jump
- * trajectory above the higher one of the player and the target.
- * the sign indicates whether the highest point is INSIDE (positive)
- * or OUTSIDE (negative) of the jump trajectory. General rule: use
- * positive values for targets mounted on the floor, and use negative
- * values to target a point on the ceiling.
- * movedir: if target is not set, this * speed * 10 is the velocity to be reached.
- */
-void spawnfunc_trigger_push()
-{
- SetMovedir ();
-
- EXACTTRIGGER_INIT;
-
- self.active = ACTIVE_ACTIVE;
- self.use = trigger_push_use;
- self.touch = trigger_push_touch;
-
- // normal push setup
- if (!self.speed)
- self.speed = 1000;
- self.movedir = self.movedir * self.speed * 10;
-
- if (!self.noise)
- self.noise = "misc/jumppad.wav";
- precache_sound (self.noise);
-
- // this must be called to spawn the teleport waypoints for bots
- InitializeEntity(self, trigger_push_findtarget, INITPRIO_FINDTARGET);
-}
-
-
-float target_push_send(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_TARGET_PUSH);
-
- WriteByte(MSG_ENTITY, self.cnt);
- WriteString(MSG_ENTITY, self.targetname);
- WriteCoord(MSG_ENTITY, self.origin_x);
- WriteCoord(MSG_ENTITY, self.origin_y);
- WriteCoord(MSG_ENTITY, self.origin_z);
-
- return true;
-}
-
-void target_push_link()
-{
- Net_LinkEntity(self, false, 0, target_push_send);
- self.SendFlags |= 1; // update
-}
-
-void spawnfunc_target_push() { target_push_link(); }
-void spawnfunc_info_notnull() { target_push_link(); }
-void spawnfunc_target_position() { target_push_link(); }
-
-#endif
-
-#ifdef CSQC
-void trigger_push_draw()
-{
- float dt = time - self.move_time;
- self.move_time = time;
- if(dt <= 0) { return; }
-
- trigger_touch_generic(trigger_push_touch);
-}
-
-void ent_trigger_push()
-{
- float sf = ReadByte();
-
- if(sf & 1)
- {
- self.classname = "jumppad";
- self.target = strzone(ReadString());
- float mytm = ReadByte(); if(mytm) { self.team = mytm - 1; }
- self.spawnflags = ReadInt24_t();
- self.active = ReadByte();
- self.warpzone_isboxy = ReadByte();
- self.height = ReadByte();
- self.scale = ReadByte();
- self.origin_x = ReadCoord();
- self.origin_y = ReadCoord();
- self.origin_z = ReadCoord();
- setorigin(self, self.origin);
- self.mins_x = ReadCoord();
- self.mins_y = ReadCoord();
- self.mins_z = ReadCoord();
- self.maxs_x = ReadCoord();
- self.maxs_y = ReadCoord();
- self.maxs_z = ReadCoord();
- setsize(self, self.mins, self.maxs);
- self.movedir_x = ReadCoord();
- self.movedir_y = ReadCoord();
- self.movedir_z = ReadCoord();
- self.angles_x = ReadCoord();
- self.angles_y = ReadCoord();
- self.angles_z = ReadCoord();
-
- self.solid = SOLID_TRIGGER;
- //self.draw = trigger_push_draw;
- self.drawmask = MASK_ENGINE;
- self.move_time = time;
- //self.touch = trigger_push_touch;
- trigger_push_findtarget();
- }
-
- if(sf & 2)
- {
- self.team = ReadByte();
- self.active = ReadByte();
- }
-}
-
-void ent_target_push()
-{
- self.classname = "push_target";
- self.cnt = ReadByte();
- self.targetname = strzone(ReadString());
- self.origin_x = ReadCoord();
- self.origin_y = ReadCoord();
- self.origin_z = ReadCoord();
- setorigin(self, self.origin);
-
- self.drawmask = MASK_ENGINE;
-}
-#endif
const int ACTIVE_IDLE = 2;
const int ACTIVE_BUSY = 2;
const int ACTIVE_TOGGLE = 3;
-void trigger_push_draw();
#endif
/*
+++ /dev/null
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "../dpdefs/dpextensions.qh"
- #include "../warpzonelib/mathlib.qh"
- #include "../warpzonelib/common.qh"
- #include "../warpzonelib/util_server.qh"
- #include "../common/constants.qh"
- #include "../common/util.qh"
- #include "../common/weapons/weapons.qh"
- #include "constants.qh"
- #include "defs.qh"
- #include "../common/notifications.qh"
- #include "../common/deathtypes.qh"
- #include "command/common.qh"
- #include "../csqcmodellib/sv_model.qh"
-#endif
-
-#ifdef SVQC
-
-.float dmgtime2;
-void generic_plat_blocked()
-{
- if(self.dmg && other.takedamage != DAMAGE_NO) {
- if(self.dmgtime2 < time) {
- Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- self.dmgtime2 = time + self.dmgtime;
- }
-
- // Gib dead/dying stuff
- if(other.deadflag != DEAD_NO)
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
-}
-
-
-.entity trigger_field;
-
-void() plat_center_touch;
-void() plat_outside_touch;
-void() plat_trigger_use;
-void() plat_go_up;
-void() plat_go_down;
-void() plat_crush;
-const float PLAT_LOW_TRIGGER = 1;
-
-void plat_spawn_inside_trigger()
-{
- entity trigger;
- vector tmin, tmax;
-
- trigger = spawn();
- trigger.touch = plat_center_touch;
- trigger.movetype = MOVETYPE_NONE;
- trigger.solid = SOLID_TRIGGER;
- trigger.enemy = self;
-
- tmin = self.absmin + '25 25 0';
- tmax = self.absmax - '25 25 -8';
- tmin.z = tmax.z - (self.pos1_z - self.pos2_z + 8);
- if (self.spawnflags & PLAT_LOW_TRIGGER)
- tmax.z = tmin.z + 8;
-
- if (self.size.x <= 50)
- {
- tmin.x = (self.mins.x + self.maxs.x) / 2;
- tmax.x = tmin.x + 1;
- }
- if (self.size.y <= 50)
- {
- tmin.y = (self.mins.y + self.maxs.y) / 2;
- tmax.y = tmin.y + 1;
- }
-
- if(tmin.x < tmax.x)
- if(tmin.y < tmax.y)
- if(tmin.z < tmax.z)
- {
- setsize (trigger, tmin, tmax);
- return;
- }
-
- // otherwise, something is fishy...
- remove(trigger);
- objerror("plat_spawn_inside_trigger: platform has odd size or lip, can't spawn");
-}
-
-void plat_hit_top()
-{
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.state = 1;
- self.think = plat_go_down;
- self.nextthink = self.ltime + 3;
-}
-
-void plat_hit_bottom()
-{
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.state = 2;
-}
-
-void plat_go_down()
-{
- sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
- self.state = 3;
- SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, plat_hit_bottom);
-}
-
-void plat_go_up()
-{
- sound (self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_NORM);
- self.state = 4;
- SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, plat_hit_top);
-}
-
-void plat_center_touch()
-{
- if (!other.iscreature)
- return;
-
- if (other.health <= 0)
- return;
-
- self = self.enemy;
- if (self.state == 2)
- plat_go_up ();
- else if (self.state == 1)
- self.nextthink = self.ltime + 1; // delay going down
-}
-
-void plat_outside_touch()
-{
- if (!other.iscreature)
- return;
-
- if (other.health <= 0)
- return;
-
- self = self.enemy;
- if (self.state == 1)
- plat_go_down ();
-}
-
-void plat_trigger_use()
-{
- if (self.think)
- return; // already activated
- plat_go_down();
-}
-
-
-void plat_crush()
-{
- if((self.spawnflags & 4) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- } else {
- if((self.dmg) && (other.takedamage != DAMAGE_NO)) { // Shall we bite?
- Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- // Gib dead/dying stuff
- if(other.deadflag != DEAD_NO)
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
-
- if (self.state == 4)
- plat_go_down ();
- else if (self.state == 3)
- plat_go_up ();
- // when in other states, then the plat_crush event came delayed after
- // plat state already had changed
- // this isn't a bug per se!
- }
-}
-
-void plat_use()
-{
- self.use = func_null;
- if (self.state != 4)
- objerror ("plat_use: not in up state");
- plat_go_down();
-}
-
-.string sound1, sound2;
-
-void plat_reset()
-{
- IFTARGETED
- {
- setorigin (self, self.pos1);
- self.state = 4;
- self.use = plat_use;
- }
- else
- {
- setorigin (self, self.pos2);
- self.state = 2;
- self.use = plat_trigger_use;
- }
-}
-
-.float platmovetype_start_default, platmovetype_end_default;
-float set_platmovetype(entity e, string s)
-{
- // sets platmovetype_start and platmovetype_end based on a string consisting of two values
-
- float n;
- n = tokenize_console(s);
- if(n > 0)
- e.platmovetype_start = stof(argv(0));
- else
- e.platmovetype_start = 0;
-
- if(n > 1)
- e.platmovetype_end = stof(argv(1));
- else
- e.platmovetype_end = e.platmovetype_start;
-
- if(n > 2)
- if(argv(2) == "force")
- return true; // no checking, return immediately
-
- if(!cubic_speedfunc_is_sane(e.platmovetype_start, e.platmovetype_end))
- {
- objerror("Invalid platform move type; platform would go in reverse, which is not allowed.");
- return false;
- }
-
- return true;
-}
-
-void spawnfunc_path_corner()
-{
- // setup values for overriding train movement
- // if a second value does not exist, both start and end speeds are the single value specified
- if(!set_platmovetype(self, self.platmovetype))
- return;
-}
-void spawnfunc_func_plat()
-{
- if (self.sounds == 0)
- self.sounds = 2;
-
- if(self.spawnflags & 4)
- self.dmg = 10000;
-
- if(self.dmg && (self.message == ""))
- self.message = "was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
-
- if (self.sounds == 1)
- {
- precache_sound ("plats/plat1.wav");
- precache_sound ("plats/plat2.wav");
- self.noise = "plats/plat1.wav";
- self.noise1 = "plats/plat2.wav";
- }
-
- if (self.sounds == 2)
- {
- precache_sound ("plats/medplat1.wav");
- precache_sound ("plats/medplat2.wav");
- self.noise = "plats/medplat1.wav";
- self.noise1 = "plats/medplat2.wav";
- }
-
- if (self.sound1)
- {
- precache_sound (self.sound1);
- self.noise = self.sound1;
- }
- if (self.sound2)
- {
- precache_sound (self.sound2);
- self.noise1 = self.sound2;
- }
-
- self.mangle = self.angles;
- self.angles = '0 0 0';
-
- self.classname = "plat";
- if (!InitMovingBrushTrigger())
- return;
- self.effects |= EF_LOWPRECISION;
- setsize (self, self.mins , self.maxs);
-
- self.blocked = plat_crush;
-
- if (!self.speed)
- self.speed = 150;
- if (!self.lip)
- self.lip = 16;
- if (!self.height)
- self.height = self.size.z - self.lip;
-
- self.pos1 = self.origin;
- self.pos2 = self.origin;
- self.pos2_z = self.origin.z - self.height;
-
- self.reset = plat_reset;
- plat_reset();
-
- plat_spawn_inside_trigger (); // the "start moving" trigger
-}
-
-.float train_wait_turning;
-void() train_next;
-void train_wait()
-{
- entity oldself;
- oldself = self;
- self = self.enemy;
- SUB_UseTargets();
- self = oldself;
- self.enemy = world;
-
- // if turning is enabled, the train will turn toward the next point while waiting
- if(self.platmovetype_turn && !self.train_wait_turning)
- {
- entity targ, cp;
- vector ang;
- targ = find(world, targetname, self.target);
- if((self.spawnflags & 1) && targ.curvetarget)
- cp = find(world, targetname, targ.curvetarget);
- else
- cp = world;
-
- if(cp) // bezier curves movement
- ang = cp.origin - (self.origin - self.view_ofs); // use the origin of the control point of the next path_corner
- else // linear movement
- ang = targ.origin - (self.origin - self.view_ofs); // use the origin of the next path_corner
- ang = vectoangles(ang);
- ang.x = -ang.x; // flip up / down orientation
-
- if(self.wait > 0) // slow turning
- SUB_CalcAngleMove(ang, TSPEED_TIME, self.ltime - time + self.wait, train_wait);
- else // instant turning
- SUB_CalcAngleMove(ang, TSPEED_TIME, 0.0000001, train_wait);
- self.train_wait_turning = true;
- return;
- }
-
- if(self.noise != "")
- stopsoundto(MSG_BROADCAST, self, CH_TRIGGER_SINGLE); // send this as unreliable only, as the train will resume operation shortly anyway
-
- if(self.wait < 0 || self.train_wait_turning) // no waiting or we already waited while turning
- {
- self.train_wait_turning = false;
- train_next();
- }
- else
- {
- self.think = train_next;
- self.nextthink = self.ltime + self.wait;
- }
-}
-
-void train_next()
-{
- entity targ, cp = world;
- vector cp_org = '0 0 0';
-
- targ = find(world, targetname, self.target);
- self.target = targ.target;
- if (self.spawnflags & 1)
- {
- if(targ.curvetarget)
- {
- cp = find(world, targetname, targ.curvetarget); // get its second target (the control point)
- cp_org = cp.origin - self.view_ofs; // no control point found, assume a straight line to the destination
- }
- }
- if (self.target == "")
- objerror("train_next: no next target");
- self.wait = targ.wait;
- if (!self.wait)
- self.wait = 0.1;
-
- if(targ.platmovetype)
- {
- // this path_corner contains a movetype overrider, apply it
- self.platmovetype_start = targ.platmovetype_start;
- self.platmovetype_end = targ.platmovetype_end;
- }
- else
- {
- // this path_corner doesn't contain a movetype overrider, use the train's defaults
- self.platmovetype_start = self.platmovetype_start_default;
- self.platmovetype_end = self.platmovetype_end_default;
- }
-
- if (targ.speed)
- {
- if (cp)
- SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
- else
- SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, targ.speed, train_wait);
- }
- else
- {
- if (cp)
- SUB_CalcMove_Bezier(cp_org, targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
- else
- SUB_CalcMove(targ.origin - self.view_ofs, TSPEED_LINEAR, self.speed, train_wait);
- }
-
- if(self.noise != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
-}
-
-void func_train_find()
-{
- entity targ;
- targ = find(world, targetname, self.target);
- self.target = targ.target;
- if (self.target == "")
- objerror("func_train_find: no next target");
- setorigin(self, targ.origin - self.view_ofs);
- self.nextthink = self.ltime + 1;
- self.think = train_next;
-}
-
-/*QUAKED spawnfunc_func_train (0 .5 .8) ?
-Ridable platform, targets spawnfunc_path_corner path to follow.
-speed : speed the train moves (can be overridden by each spawnfunc_path_corner)
-target : targetname of first spawnfunc_path_corner (starts here)
-*/
-void spawnfunc_func_train()
-{
- if (self.noise != "")
- precache_sound(self.noise);
-
- if (self.target == "")
- objerror("func_train without a target");
- if (!self.speed)
- self.speed = 100;
-
- if (!InitMovingBrushTrigger())
- return;
- self.effects |= EF_LOWPRECISION;
-
- if (self.spawnflags & 2)
- {
- self.platmovetype_turn = true;
- self.view_ofs = '0 0 0'; // don't offset a rotating train, origin works differently now
- }
- else
- self.view_ofs = self.mins;
-
- // wait for targets to spawn
- InitializeEntity(self, func_train_find, INITPRIO_SETLOCATION);
-
- self.blocked = generic_plat_blocked;
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
- self.dmgtime2 = time;
-
- if(!set_platmovetype(self, self.platmovetype))
- return;
- self.platmovetype_start_default = self.platmovetype_start;
- self.platmovetype_end_default = self.platmovetype_end;
-
- // TODO make a reset function for this one
-}
-
-void func_rotating_setactive(float astate)
-{
-
- if (astate == ACTIVE_TOGGLE)
- {
- if(self.active == ACTIVE_ACTIVE)
- self.active = ACTIVE_NOT;
- else
- self.active = ACTIVE_ACTIVE;
- }
- else
- self.active = astate;
-
- if(self.active == ACTIVE_NOT)
- self.avelocity = '0 0 0';
- else
- self.avelocity = self.pos1;
-}
-
-/*QUAKED spawnfunc_func_rotating (0 .5 .8) ? - - X_AXIS Y_AXIS
-Brush model that spins in place on one axis (default Z).
-speed : speed to rotate (in degrees per second)
-noise : path/name of looping .wav file to play.
-dmg : Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime : See above.
-*/
-
-void spawnfunc_func_rotating()
-{
- if (self.noise != "")
- {
- precache_sound(self.noise);
- ambientsound(self.origin, self.noise, VOL_BASE, ATTEN_IDLE);
- }
-
- self.active = ACTIVE_ACTIVE;
- self.setactive = func_rotating_setactive;
-
- if (!self.speed)
- self.speed = 100;
- // FIXME: test if this turns the right way, then remove this comment (negate as needed)
- if (self.spawnflags & 4) // X (untested)
- self.avelocity = '0 0 1' * self.speed;
- // FIXME: test if this turns the right way, then remove this comment (negate as needed)
- else if (self.spawnflags & 8) // Y (untested)
- self.avelocity = '1 0 0' * self.speed;
- // FIXME: test if this turns the right way, then remove this comment (negate as needed)
- else // Z
- self.avelocity = '0 1 0' * self.speed;
-
- self.pos1 = self.avelocity;
-
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
-
-
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
-
- self.dmgtime2 = time;
-
- if (!InitMovingBrushTrigger())
- return;
- // no EF_LOWPRECISION here, as rounding angles is bad
-
- self.blocked = generic_plat_blocked;
-
- // wait for targets to spawn
- self.nextthink = self.ltime + 999999999;
- self.think = SUB_NullThink; // for PushMove
-
- // TODO make a reset function for this one
-}
-
-.float height;
-void func_bobbing_controller_think()
-{
- vector v;
- self.nextthink = time + 0.1;
-
- if(self.owner.active != ACTIVE_ACTIVE)
- {
- self.owner.velocity = '0 0 0';
- return;
- }
-
- // calculate sinewave using makevectors
- makevectors((self.nextthink * self.owner.cnt + self.owner.phase * 360) * '0 1 0');
- v = self.owner.destvec + self.owner.movedir * v_forward.y;
- if(self.owner.classname == "func_bobbing") // don't brake stuff if the func_bobbing was killtarget'ed
- // * 10 so it will arrive in 0.1 sec
- self.owner.velocity = (v - self.owner.origin) * 10;
-}
-
-/*QUAKED spawnfunc_func_bobbing (0 .5 .8) ? X_AXIS Y_AXIS
-Brush model that moves back and forth on one axis (default Z).
-speed : how long one cycle takes in seconds (default 4)
-height : how far the cycle moves (default 32)
-phase : cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
-noise : path/name of looping .wav file to play.
-dmg : Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime : See above.
-*/
-void spawnfunc_func_bobbing()
-{
- entity controller;
- if (self.noise != "")
- {
- precache_sound(self.noise);
- soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
- }
- if (!self.speed)
- self.speed = 4;
- if (!self.height)
- self.height = 32;
- // center of bobbing motion
- self.destvec = self.origin;
- // time scale to get degrees
- self.cnt = 360 / self.speed;
-
- self.active = ACTIVE_ACTIVE;
-
- // damage when blocked
- self.blocked = generic_plat_blocked;
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
- self.dmgtime2 = time;
-
- // how far to bob
- if (self.spawnflags & 1) // X
- self.movedir = '1 0 0' * self.height;
- else if (self.spawnflags & 2) // Y
- self.movedir = '0 1 0' * self.height;
- else // Z
- self.movedir = '0 0 1' * self.height;
-
- if (!InitMovingBrushTrigger())
- return;
-
- // wait for targets to spawn
- controller = spawn();
- controller.classname = "func_bobbing_controller";
- controller.owner = self;
- controller.nextthink = time + 1;
- controller.think = func_bobbing_controller_think;
- self.nextthink = self.ltime + 999999999;
- self.think = SUB_NullThink; // for PushMove
-
- // Savage: Reduce bandwith, critical on e.g. nexdm02
- self.effects |= EF_LOWPRECISION;
-
- // TODO make a reset function for this one
-}
-
-.float freq;
-void func_pendulum_controller_think()
-{
- float v;
- self.nextthink = time + 0.1;
-
- if (!(self.owner.active == ACTIVE_ACTIVE))
- {
- self.owner.avelocity_x = 0;
- return;
- }
-
- // calculate sinewave using makevectors
- makevectors((self.nextthink * self.owner.freq + self.owner.phase) * '0 360 0');
- v = self.owner.speed * v_forward.y + self.cnt;
- if(self.owner.classname == "func_pendulum") // don't brake stuff if the func_bobbing was killtarget'ed
- {
- // * 10 so it will arrive in 0.1 sec
- self.owner.avelocity_z = (remainder(v - self.owner.angles.z, 360)) * 10;
- }
-}
-
-void spawnfunc_func_pendulum()
-{
- entity controller;
- if (self.noise != "")
- {
- precache_sound(self.noise);
- soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
- }
-
- self.active = ACTIVE_ACTIVE;
-
- // keys: angle, speed, phase, noise, freq
-
- if(!self.speed)
- self.speed = 30;
- // not initializing self.dmg to 2, to allow damageless pendulum
-
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
- self.dmgtime2 = time;
-
- self.blocked = generic_plat_blocked;
-
- self.avelocity_z = 0.0000001;
- if (!InitMovingBrushTrigger())
- return;
-
- if(!self.freq)
- {
- // find pendulum length (same formula as Q3A)
- self.freq = 1 / (M_PI * 2) * sqrt(autocvar_sv_gravity / (3 * max(8, fabs(self.mins.z))));
- }
-
- // copy initial angle
- self.cnt = self.angles.z;
-
- // wait for targets to spawn
- controller = spawn();
- controller.classname = "func_pendulum_controller";
- controller.owner = self;
- controller.nextthink = time + 1;
- controller.think = func_pendulum_controller_think;
- self.nextthink = self.ltime + 999999999;
- self.think = SUB_NullThink; // for PushMove
-
- //self.effects |= EF_LOWPRECISION;
-
- // TODO make a reset function for this one
-}
-
-// button and multiple button
-
-void() button_wait;
-void() button_return;
-
-void button_wait()
-{
- self.state = STATE_TOP;
- self.nextthink = self.ltime + self.wait;
- self.think = button_return;
- activator = self.enemy;
- SUB_UseTargets();
- self.frame = 1; // use alternate textures
-}
-
-void button_done()
-{
- self.state = STATE_BOTTOM;
-}
-
-void button_return()
-{
- self.state = STATE_DOWN;
- SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, button_done);
- self.frame = 0; // use normal textures
- if (self.health)
- self.takedamage = DAMAGE_YES; // can be shot again
-}
-
-
-void button_blocked()
-{
- // do nothing, just don't come all the way back out
-}
-
-
-void button_fire()
-{
- self.health = self.max_health;
- self.takedamage = DAMAGE_NO; // will be reset upon return
-
- if (self.state == STATE_UP || self.state == STATE_TOP)
- return;
-
- if (self.noise != "")
- sound (self, CH_TRIGGER, self.noise, VOL_BASE, ATTEN_NORM);
-
- self.state = STATE_UP;
- SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, button_wait);
-}
-
-void button_reset()
-{
- self.health = self.max_health;
- setorigin(self, self.pos1);
- self.frame = 0; // use normal textures
- self.state = STATE_BOTTOM;
- if (self.health)
- self.takedamage = DAMAGE_YES; // can be shot again
-}
-
-void button_use()
-{
- if(self.active != ACTIVE_ACTIVE)
- return;
-
- self.enemy = activator;
- button_fire ();
-}
-
-void button_touch()
-{
- if (!other)
- return;
- if (!other.iscreature)
- return;
- if(other.velocity * self.movedir < 0)
- return;
- self.enemy = other;
- if (other.owner)
- self.enemy = other.owner;
- button_fire ();
-}
-
-void button_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
- if(self.spawnflags & DOOR_NOSPLASH)
- if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
- return;
- self.health = self.health - damage;
- if (self.health <= 0)
- {
- self.enemy = damage_attacker;
- button_fire ();
- }
-}
-
-
-/*QUAKED spawnfunc_func_button (0 .5 .8) ?
-When a button is touched, it moves some distance in the direction of it's angle, triggers all of it's targets, waits some time, then returns to it's original position where it can be triggered again.
-
-"angle" determines the opening direction
-"target" all entities with a matching targetname will be used
-"speed" override the default 40 speed
-"wait" override the default 1 second wait (-1 = never return)
-"lip" override the default 4 pixel lip remaining at end of move
-"health" if set, the button must be killed instead of touched. If set to -1, the button will fire on ANY attack, even damageless ones like the InstaGib laser
-"sounds"
-0) steam metal
-1) wooden clunk
-2) metallic click
-3) in-out
-*/
-void spawnfunc_func_button()
-{
- SetMovedir ();
-
- if (!InitMovingBrushTrigger())
- return;
- self.effects |= EF_LOWPRECISION;
-
- self.blocked = button_blocked;
- self.use = button_use;
-
-// if (self.health == 0) // all buttons are now shootable
-// self.health = 10;
- if (self.health)
- {
- self.max_health = self.health;
- self.event_damage = button_damage;
- self.takedamage = DAMAGE_YES;
- }
- else
- self.touch = button_touch;
-
- if (!self.speed)
- self.speed = 40;
- if (!self.wait)
- self.wait = 1;
- if (!self.lip)
- self.lip = 4;
-
- if(self.noise != "")
- precache_sound(self.noise);
-
- self.active = ACTIVE_ACTIVE;
-
- self.pos1 = self.origin;
- self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
- self.flags |= FL_NOTARGET;
-
- button_reset();
-}
-
-
-const float DOOR_START_OPEN = 1;
-const float DOOR_DONT_LINK = 4;
-const float DOOR_TOGGLE = 32;
-
-/*
-
-Doors are similar to buttons, but can spawn a fat trigger field around them
-to open without a touch, and they link together to form simultanious
-double/quad doors.
-
-Door.owner is the master door. If there is only one door, it points to itself.
-If multiple doors, all will point to a single one.
-
-Door.enemy chains from the master door through all doors linked in the chain.
-
-*/
-
-/*
-=============================================================================
-
-THINK FUNCTIONS
-
-=============================================================================
-*/
-
-void() door_go_down;
-void() door_go_up;
-void() door_rotating_go_down;
-void() door_rotating_go_up;
-
-void door_blocked()
-{
-
- if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- } else {
-
- if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
- Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-
- //Dont chamge direction for dead or dying stuff
- if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
- if (self.wait >= 0)
- {
- if (self.state == STATE_DOWN)
- if (self.classname == "door")
- {
- door_go_up ();
- } else
- {
- door_rotating_go_up ();
- }
- else
- if (self.classname == "door")
- {
- door_go_down ();
- } else
- {
- door_rotating_go_down ();
- }
- }
- } else {
- //gib dying stuff just to make sure
- if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
- }
-
- //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-// if a door has a negative wait, it would never come back if blocked,
-// so let it just squash the object to death real fast
-/* if (self.wait >= 0)
- {
- if (self.state == STATE_DOWN)
- door_go_up ();
- else
- door_go_down ();
- }
-*/
-}
-
-
-void door_hit_top()
-{
- if (self.noise1 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.state = STATE_TOP;
- if (self.spawnflags & DOOR_TOGGLE)
- return; // don't come down automatically
- if (self.classname == "door")
- {
- self.think = door_go_down;
- } else
- {
- self.think = door_rotating_go_down;
- }
- self.nextthink = self.ltime + self.wait;
-}
-
-void door_hit_bottom()
-{
- if (self.noise1 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.state = STATE_BOTTOM;
-}
-
-void door_go_down()
-{
- if (self.noise2 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- if (self.max_health)
- {
- self.takedamage = DAMAGE_YES;
- self.health = self.max_health;
- }
-
- self.state = STATE_DOWN;
- SUB_CalcMove (self.pos1, TSPEED_LINEAR, self.speed, door_hit_bottom);
-}
-
-void door_go_up()
-{
- if (self.state == STATE_UP)
- return; // already going up
-
- if (self.state == STATE_TOP)
- { // reset top wait time
- self.nextthink = self.ltime + self.wait;
- return;
- }
-
- if (self.noise2 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- self.state = STATE_UP;
- SUB_CalcMove (self.pos2, TSPEED_LINEAR, self.speed, door_hit_top);
-
- string oldmessage;
- oldmessage = self.message;
- self.message = "";
- SUB_UseTargets();
- self.message = oldmessage;
-}
-
-
-
-/*
-=============================================================================
-
-ACTIVATION FUNCTIONS
-
-=============================================================================
-*/
-
-float door_check_keys(void) {
- entity door = self.owner ? self.owner : self;
-
- // no key needed
- if (!door.itemkeys)
- return true;
-
- // this door require a key
- // only a player can have a key
- if (!IS_PLAYER(other))
- return false;
-
- if (item_keys_usekey(door, other)) {
- // some keys were used
- if (other.key_door_messagetime <= time) {
- play2(other, "misc/talk.wav");
- Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys));
- other.key_door_messagetime = time + 2;
- }
- } else {
- // no keys were used
- if (other.key_door_messagetime <= time) {
- play2(other, "misc/talk.wav");
- Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys));
- other.key_door_messagetime = time + 2;
- }
- }
-
- if (door.itemkeys) {
- // door is now unlocked
- play2(other, "misc/talk.wav");
- Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_UNLOCKED);
- return true;
- } else
- return false;
-}
-
-
-void door_fire()
-{
- entity oself;
- entity starte;
-
- if (self.owner != self)
- objerror ("door_fire: self.owner != self");
-
- oself = self;
-
- if (self.spawnflags & DOOR_TOGGLE)
- {
- if (self.state == STATE_UP || self.state == STATE_TOP)
- {
- starte = self;
- do
- {
- if (self.classname == "door")
- {
- door_go_down ();
- }
- else
- {
- door_rotating_go_down ();
- }
- self = self.enemy;
- } while ( (self != starte) && (self != world) );
- self = oself;
- return;
- }
- }
-
-// trigger all paired doors
- starte = self;
- do
- {
- if (self.classname == "door")
- {
- door_go_up ();
- } else
- {
- // if the BIDIR spawnflag (==2) is set and the trigger has set trigger_reverse, reverse the opening direction
- if ((self.spawnflags & 2) && other.trigger_reverse!=0 && self.lip!=666 && self.state == STATE_BOTTOM)
- {
- self.lip = 666; // self.lip is used to remember reverse opening direction for door_rotating
- self.pos2 = '0 0 0' - self.pos2;
- }
- // if BIDIR_IN_DOWN (==8) is set, prevent the door from reoping during closing if it is triggered from the wrong side
- if (!((self.spawnflags & 2) && (self.spawnflags & 8) && self.state == STATE_DOWN
- && (((self.lip==666) && (other.trigger_reverse==0)) || ((self.lip!=666) && (other.trigger_reverse!=0)))))
- {
- door_rotating_go_up ();
- }
- }
- self = self.enemy;
- } while ( (self != starte) && (self != world) );
- self = oself;
-}
-
-
-void door_use()
-{
- entity oself;
-
- //dprint("door_use (model: ");dprint(self.model);dprint(")\n");
-
- if (self.owner)
- {
- oself = self;
- self = self.owner;
- door_fire ();
- self = oself;
- }
-}
-
-
-void door_trigger_touch()
-{
- if (other.health < 1)
- if (!(other.iscreature && other.deadflag == DEAD_NO))
- return;
-
- if (time < self.attack_finished_single)
- return;
-
- // check if door is locked
- if (!door_check_keys())
- return;
-
- self.attack_finished_single = time + 1;
-
- activator = other;
-
- self = self.owner;
- door_use ();
-}
-
-
-void door_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
- entity oself;
- if(self.spawnflags & DOOR_NOSPLASH)
- if(!(DEATH_ISSPECIAL(deathtype)) && (deathtype & HITTYPE_SPLASH))
- return;
- self.health = self.health - damage;
-
- if (self.itemkeys) {
- // don't allow opening doors through damage if keys are required
- return;
- }
-
- if (self.health <= 0)
- {
- oself = self;
- self = self.owner;
- self.health = self.max_health;
- self.takedamage = DAMAGE_NO; // wil be reset upon return
- door_use ();
- self = oself;
- }
-}
-
-
-/*
-================
-door_touch
-
-Prints messages
-================
-*/
-void door_touch()
-{
- if (!IS_PLAYER(other))
- return;
- if (self.owner.attack_finished_single > time)
- return;
-
- self.owner.attack_finished_single = time + 2;
-
- if (!(self.owner.dmg) && (self.owner.message != ""))
- {
- if (IS_CLIENT(other))
- centerprint(other, self.owner.message);
- play2(other, "misc/talk.wav");
- }
-}
-
-
-void door_generic_plat_blocked()
-{
-
- if((self.spawnflags & 8) && (other.takedamage != DAMAGE_NO)) { // KIll Kill Kill!!
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- } else {
-
- if((self.dmg) && (other.takedamage == DAMAGE_YES)) // Shall we bite?
- Damage (other, self, self, self.dmg, DEATH_HURTTRIGGER, other.origin, '0 0 0');
-
- //Dont chamge direction for dead or dying stuff
- if(other.deadflag != DEAD_NO && (other.takedamage == DAMAGE_NO)) {
- if (self.wait >= 0)
- {
- if (self.state == STATE_DOWN)
- door_rotating_go_up ();
- else
- door_rotating_go_down ();
- }
- } else {
- //gib dying stuff just to make sure
- if((self.dmg) && (other.takedamage != DAMAGE_NO)) // Shall we bite?
- Damage (other, self, self, 10000, DEATH_HURTTRIGGER, other.origin, '0 0 0');
- }
- }
-
- //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-// if a door has a negative wait, it would never come back if blocked,
-// so let it just squash the object to death real fast
-/* if (self.wait >= 0)
- {
- if (self.state == STATE_DOWN)
- door_rotating_go_up ();
- else
- door_rotating_go_down ();
- }
-*/
-}
-
-
-void door_rotating_hit_top()
-{
- if (self.noise1 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.state = STATE_TOP;
- if (self.spawnflags & DOOR_TOGGLE)
- return; // don't come down automatically
- self.think = door_rotating_go_down;
- self.nextthink = self.ltime + self.wait;
-}
-
-void door_rotating_hit_bottom()
-{
- if (self.noise1 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- if (self.lip==666) // self.lip is used to remember reverse opening direction for door_rotating
- {
- self.pos2 = '0 0 0' - self.pos2;
- self.lip = 0;
- }
- self.state = STATE_BOTTOM;
-}
-
-void door_rotating_go_down()
-{
- if (self.noise2 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- if (self.max_health)
- {
- self.takedamage = DAMAGE_YES;
- self.health = self.max_health;
- }
-
- self.state = STATE_DOWN;
- SUB_CalcAngleMove (self.pos1, TSPEED_LINEAR, self.speed, door_rotating_hit_bottom);
-}
-
-void door_rotating_go_up()
-{
- if (self.state == STATE_UP)
- return; // already going up
-
- if (self.state == STATE_TOP)
- { // reset top wait time
- self.nextthink = self.ltime + self.wait;
- return;
- }
- if (self.noise2 != "")
- sound (self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- self.state = STATE_UP;
- SUB_CalcAngleMove (self.pos2, TSPEED_LINEAR, self.speed, door_rotating_hit_top);
-
- string oldmessage;
- oldmessage = self.message;
- self.message = "";
- SUB_UseTargets();
- self.message = oldmessage;
-}
-
-
-
-
-/*
-=============================================================================
-
-SPAWNING FUNCTIONS
-
-=============================================================================
-*/
-
-
-entity spawn_field(vector fmins, vector fmaxs)
-{
- entity trigger;
- vector t1, t2;
-
- trigger = spawn();
- trigger.classname = "doortriggerfield";
- trigger.movetype = MOVETYPE_NONE;
- trigger.solid = SOLID_TRIGGER;
- trigger.owner = self;
- trigger.touch = door_trigger_touch;
-
- t1 = fmins;
- t2 = fmaxs;
- setsize (trigger, t1 - '60 60 8', t2 + '60 60 8');
- return (trigger);
-}
-
-
-entity LinkDoors_nextent(entity cur, entity near, entity pass)
-{
- while((cur = find(cur, classname, self.classname)) && ((cur.spawnflags & 4) || cur.enemy))
- {
- }
- return cur;
-}
-
-float LinkDoors_isconnected(entity e1, entity e2, entity pass)
-{
- float DELTA = 4;
- if (e1.absmin.x > e2.absmax.x + DELTA)
- return false;
- if (e1.absmin.y > e2.absmax.y + DELTA)
- return false;
- if (e1.absmin.z > e2.absmax.z + DELTA)
- return false;
- if (e2.absmin.x > e1.absmax.x + DELTA)
- return false;
- if (e2.absmin.y > e1.absmax.y + DELTA)
- return false;
- if (e2.absmin.z > e1.absmax.z + DELTA)
- return false;
- return true;
-}
-
-/*
-=============
-LinkDoors
-
-
-=============
-*/
-void LinkDoors()
-{
- entity t;
- vector cmins, cmaxs;
-
- if (self.enemy)
- return; // already linked by another door
- if (self.spawnflags & 4)
- {
- self.owner = self.enemy = self;
-
- if (self.health)
- return;
- IFTARGETED
- return;
- if (self.items)
- return;
- self.trigger_field = spawn_field(self.absmin, self.absmax);
-
- return; // don't want to link this door
- }
-
- FindConnectedComponent(self, enemy, LinkDoors_nextent, LinkDoors_isconnected, world);
-
- // set owner, and make a loop of the chain
- dprint("LinkDoors: linking doors:");
- for(t = self; ; t = t.enemy)
- {
- dprint(" ", etos(t));
- t.owner = self;
- if(t.enemy == world)
- {
- t.enemy = self;
- break;
- }
- }
- dprint("\n");
-
- // collect health, targetname, message, size
- cmins = self.absmin;
- cmaxs = self.absmax;
- for(t = self; ; t = t.enemy)
- {
- if(t.health && !self.health)
- self.health = t.health;
- if((t.targetname != "") && (self.targetname == ""))
- self.targetname = t.targetname;
- if((t.message != "") && (self.message == ""))
- self.message = t.message;
- if (t.absmin.x < cmins.x)
- cmins.x = t.absmin.x;
- if (t.absmin.y < cmins.y)
- cmins.y = t.absmin.y;
- if (t.absmin.z < cmins.z)
- cmins.z = t.absmin.z;
- if (t.absmax.x > cmaxs.x)
- cmaxs.x = t.absmax.x;
- if (t.absmax.y > cmaxs.y)
- cmaxs.y = t.absmax.y;
- if (t.absmax.z > cmaxs.z)
- cmaxs.z = t.absmax.z;
- if(t.enemy == self)
- break;
- }
-
- // distribute health, targetname, message
- for(t = self; t; t = t.enemy)
- {
- t.health = self.health;
- t.targetname = self.targetname;
- t.message = self.message;
- if(t.enemy == self)
- break;
- }
-
- // shootable, or triggered doors just needed the owner/enemy links,
- // they don't spawn a field
-
- if (self.health)
- return;
- IFTARGETED
- return;
- if (self.items)
- return;
-
- self.trigger_field = spawn_field(cmins, cmaxs);
-}
-
-
-/*QUAKED spawnfunc_func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK GOLD_KEY SILVER_KEY TOGGLE
-if two doors touch, they are assumed to be connected and operate as a unit.
-
-TOGGLE causes the door to wait in both the start and end states for a trigger event.
-
-START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not useful for touch or takedamage doors).
-
-GOLD_KEY causes the door to open only if the activator holds a gold key.
-
-SILVER_KEY causes the door to open only if the activator holds a silver key.
-
-"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
-"angle" determines the opening direction
-"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
-"health" if set, door must be shot open
-"speed" movement speed (100 default)
-"wait" wait before returning (3 default, -1 = never return)
-"lip" lip remaining at end of move (8 default)
-"dmg" damage to inflict when blocked (2 default)
-"sounds"
-0) no sound
-1) stone
-2) base
-3) stone chain
-4) screechy metal
-FIXME: only one sound set available at the time being
-
-*/
-
-void door_init_startopen()
-{
- setorigin (self, self.pos2);
- self.pos2 = self.pos1;
- self.pos1 = self.origin;
-}
-
-void door_reset()
-{
- setorigin(self, self.pos1);
- self.velocity = '0 0 0';
- self.state = STATE_BOTTOM;
- self.think = func_null;
- self.nextthink = 0;
-}
-
-// spawnflags require key (for now only func_door)
-const float SPAWNFLAGS_GOLD_KEY = 8;
-const float SPAWNFLAGS_SILVER_KEY = 16;
-void spawnfunc_func_door()
-{
- // Quake 1 keys compatibility
- if (self.spawnflags & SPAWNFLAGS_GOLD_KEY)
- self.itemkeys |= ITEM_KEY_BIT(0);
- if (self.spawnflags & SPAWNFLAGS_SILVER_KEY)
- self.itemkeys |= ITEM_KEY_BIT(1);
-
- //if (!self.deathtype) // map makers can override this
- // self.deathtype = " got in the way";
- SetMovedir ();
-
- self.max_health = self.health;
- if (!InitMovingBrushTrigger())
- return;
- self.effects |= EF_LOWPRECISION;
- self.classname = "door";
-
- self.blocked = door_blocked;
- self.use = door_use;
-
- // FIXME: undocumented flag 8, originally (Q1) GOLD_KEY
- // if(self.spawnflags & 8)
- // self.dmg = 10000;
-
- if(self.dmg && (self.message == ""))
- self.message = "was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
-
- if (self.sounds > 0)
- {
- precache_sound ("plats/medplat1.wav");
- precache_sound ("plats/medplat2.wav");
- self.noise2 = "plats/medplat1.wav";
- self.noise1 = "plats/medplat2.wav";
- }
-
- if (!self.speed)
- self.speed = 100;
- if (!self.wait)
- self.wait = 3;
- if (!self.lip)
- self.lip = 8;
-
- self.pos1 = self.origin;
- self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
-
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
- if (self.spawnflags & DOOR_START_OPEN)
- InitializeEntity(self, door_init_startopen, INITPRIO_SETLOCATION);
-
- self.state = STATE_BOTTOM;
-
- if (self.health)
- {
- self.takedamage = DAMAGE_YES;
- self.event_damage = door_damage;
- }
-
- if (self.items)
- self.wait = -1;
-
- self.touch = door_touch;
-
-// LinkDoors can't be done until all of the doors have been spawned, so
-// the sizes can be detected properly.
- InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
-
- self.reset = door_reset;
-}
-
-/*QUAKED spawnfunc_func_door_rotating (0 .5 .8) ? START_OPEN BIDIR DOOR_DONT_LINK BIDIR_IN_DOWN x TOGGLE X_AXIS Y_AXIS
-if two doors touch, they are assumed to be connected and operate as a unit.
-
-TOGGLE causes the door to wait in both the start and end states for a trigger event.
-
-BIDIR makes the door work bidirectional, so that the opening direction is always away from the requestor.
-The usage of bidirectional doors requires two manually instantiated triggers (trigger_multiple), the one to open it in the other direction
-must have set trigger_reverse to 1.
-BIDIR_IN_DOWN will the door prevent from reopening while closing if it is triggered from the other side.
-
-START_OPEN causes the door to move to its destination when spawned, and operate in reverse. It is used to temporarily or permanently close off an area when triggered (not usefull for touch or takedamage doors).
-
-"message" is printed when the door is touched if it is a trigger door and it hasn't been fired yet
-"angle" determines the destination angle for opening. negative values reverse the direction.
-"targetname" if set, no touch field will be spawned and a remote button or trigger field activates the door.
-"health" if set, door must be shot open
-"speed" movement speed (100 default)
-"wait" wait before returning (3 default, -1 = never return)
-"dmg" damage to inflict when blocked (2 default)
-"sounds"
-0) no sound
-1) stone
-2) base
-3) stone chain
-4) screechy metal
-FIXME: only one sound set available at the time being
-*/
-
-void door_rotating_reset()
-{
- self.angles = self.pos1;
- self.avelocity = '0 0 0';
- self.state = STATE_BOTTOM;
- self.think = func_null;
- self.nextthink = 0;
-}
-
-void door_rotating_init_startopen()
-{
- self.angles = self.movedir;
- self.pos2 = '0 0 0';
- self.pos1 = self.movedir;
-}
-
-
-void spawnfunc_func_door_rotating()
-{
-
- //if (!self.deathtype) // map makers can override this
- // self.deathtype = " got in the way";
-
- // I abuse "movedir" for denoting the axis for now
- if (self.spawnflags & 64) // X (untested)
- self.movedir = '0 0 1';
- else if (self.spawnflags & 128) // Y (untested)
- self.movedir = '1 0 0';
- else // Z
- self.movedir = '0 1 0';
-
- if (self.angles.y ==0) self.angles.y = 90;
-
- self.movedir = self.movedir * self.angles.y;
- self.angles = '0 0 0';
-
- self.max_health = self.health;
- self.avelocity = self.movedir;
- if (!InitMovingBrushTrigger())
- return;
- self.velocity = '0 0 0';
- //self.effects |= EF_LOWPRECISION;
- self.classname = "door_rotating";
-
- self.blocked = door_blocked;
- self.use = door_use;
-
- if(self.spawnflags & 8)
- self.dmg = 10000;
-
- if(self.dmg && (self.message == ""))
- self.message = "was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
-
- if (self.sounds > 0)
- {
- precache_sound ("plats/medplat1.wav");
- precache_sound ("plats/medplat2.wav");
- self.noise2 = "plats/medplat1.wav";
- self.noise1 = "plats/medplat2.wav";
- }
-
- if (!self.speed)
- self.speed = 50;
- if (!self.wait)
- self.wait = 1;
- self.lip = 0; // self.lip is used to remember reverse opening direction for door_rotating
-
- self.pos1 = '0 0 0';
- self.pos2 = self.movedir;
-
-// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
-// but spawn in the open position
- if (self.spawnflags & DOOR_START_OPEN)
- InitializeEntity(self, door_rotating_init_startopen, INITPRIO_SETLOCATION);
-
- self.state = STATE_BOTTOM;
-
- if (self.health)
- {
- self.takedamage = DAMAGE_YES;
- self.event_damage = door_damage;
- }
-
- if (self.items)
- self.wait = -1;
-
- self.touch = door_touch;
-
-// LinkDoors can't be done until all of the doors have been spawned, so
-// the sizes can be detected properly.
- InitializeEntity(self, LinkDoors, INITPRIO_LINKDOORS);
-
- self.reset = door_rotating_reset;
-}
-
-/*
-=============================================================================
-
-SECRET DOORS
-
-=============================================================================
-*/
-
-void() fd_secret_move1;
-void() fd_secret_move2;
-void() fd_secret_move3;
-void() fd_secret_move4;
-void() fd_secret_move5;
-void() fd_secret_move6;
-void() fd_secret_done;
-
-const float SECRET_OPEN_ONCE = 1; // stays open
-const float SECRET_1ST_LEFT = 2; // 1st move is left of arrow
-const float SECRET_1ST_DOWN = 4; // 1st move is down from arrow
-const float SECRET_NO_SHOOT = 8; // only opened by trigger
-const float SECRET_YES_SHOOT = 16; // shootable even if targeted
-
-void fd_secret_use()
-{
- float temp;
- string message_save;
-
- self.health = 10000;
- self.bot_attack = true;
-
- // exit if still moving around...
- if (self.origin != self.oldorigin)
- return;
-
- message_save = self.message;
- self.message = ""; // no more message
- SUB_UseTargets(); // fire all targets / killtargets
- self.message = message_save;
-
- self.velocity = '0 0 0';
-
- // Make a sound, wait a little...
-
- if (self.noise1 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise1, VOL_BASE, ATTEN_NORM);
- self.nextthink = self.ltime + 0.1;
-
- temp = 1 - (self.spawnflags & SECRET_1ST_LEFT); // 1 or -1
- makevectors(self.mangle);
-
- if (!self.t_width)
- {
- if (self.spawnflags & SECRET_1ST_DOWN)
- self.t_width = fabs(v_up * self.size);
- else
- self.t_width = fabs(v_right * self.size);
- }
-
- if (!self.t_length)
- self.t_length = fabs(v_forward * self.size);
-
- if (self.spawnflags & SECRET_1ST_DOWN)
- self.dest1 = self.origin - v_up * self.t_width;
- else
- self.dest1 = self.origin + v_right * (self.t_width * temp);
-
- self.dest2 = self.dest1 + v_forward * self.t_length;
- SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move1);
- if (self.noise2 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
-}
-
-void fd_secret_damage(entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
-{
- fd_secret_use();
-}
-
-// Wait after first movement...
-void fd_secret_move1()
-{
- self.nextthink = self.ltime + 1.0;
- self.think = fd_secret_move2;
- if (self.noise3 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-// Start moving sideways w/sound...
-void fd_secret_move2()
-{
- if (self.noise2 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.dest2, TSPEED_LINEAR, self.speed, fd_secret_move3);
-}
-
-// Wait here until time to go back...
-void fd_secret_move3()
-{
- if (self.noise3 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
- if (!(self.spawnflags & SECRET_OPEN_ONCE))
- {
- self.nextthink = self.ltime + self.wait;
- self.think = fd_secret_move4;
- }
-}
-
-// Move backward...
-void fd_secret_move4()
-{
- if (self.noise2 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.dest1, TSPEED_LINEAR, self.speed, fd_secret_move5);
-}
-
-// Wait 1 second...
-void fd_secret_move5()
-{
- self.nextthink = self.ltime + 1.0;
- self.think = fd_secret_move6;
- if (self.noise3 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-void fd_secret_move6()
-{
- if (self.noise2 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise2, VOL_BASE, ATTEN_NORM);
- SUB_CalcMove(self.oldorigin, TSPEED_LINEAR, self.speed, fd_secret_done);
-}
-
-void fd_secret_done()
-{
- if (self.spawnflags&SECRET_YES_SHOOT)
- {
- self.health = 10000;
- self.takedamage = DAMAGE_YES;
- //self.th_pain = fd_secret_use;
- }
- if (self.noise3 != "")
- sound(self, CH_TRIGGER_SINGLE, self.noise3, VOL_BASE, ATTEN_NORM);
-}
-
-void secret_blocked()
-{
- if (time < self.attack_finished_single)
- return;
- self.attack_finished_single = time + 0.5;
- //T_Damage (other, self, self, self.dmg, self.dmg, self.deathtype, DT_IMPACT, (self.absmin + self.absmax) * 0.5, '0 0 0', Obituary_Generic);
-}
-
-/*
-==============
-secret_touch
-
-Prints messages
-================
-*/
-void secret_touch()
-{
- if (!other.iscreature)
- return;
- if (self.attack_finished_single > time)
- return;
-
- self.attack_finished_single = time + 2;
-
- if (self.message)
- {
- if (IS_CLIENT(other))
- centerprint(other, self.message);
- play2(other, "misc/talk.wav");
- }
-}
-
-void secret_reset()
-{
- if (self.spawnflags&SECRET_YES_SHOOT)
- {
- self.health = 10000;
- self.takedamage = DAMAGE_YES;
- }
- setorigin(self, self.oldorigin);
- self.think = func_null;
- self.nextthink = 0;
-}
-
-/*QUAKED spawnfunc_func_door_secret (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot
-Basic secret door. Slides back, then to the side. Angle determines direction.
-wait = # of seconds before coming back
-1st_left = 1st move is left of arrow
-1st_down = 1st move is down from arrow
-always_shoot = even if targeted, keep shootable
-t_width = override WIDTH to move back (or height if going down)
-t_length = override LENGTH to move sideways
-"dmg" damage to inflict when blocked (2 default)
-
-If a secret door has a targetname, it will only be opened by it's botton or trigger, not by damage.
-"sounds"
-1) medieval
-2) metal
-3) base
-*/
-
-void spawnfunc_func_door_secret()
-{
- /*if (!self.deathtype) // map makers can override this
- self.deathtype = " got in the way";*/
-
- if (!self.dmg)
- self.dmg = 2;
-
- // Magic formula...
- self.mangle = self.angles;
- self.angles = '0 0 0';
- self.classname = "door";
- if (!InitMovingBrushTrigger())
- return;
- self.effects |= EF_LOWPRECISION;
-
- self.touch = secret_touch;
- self.blocked = secret_blocked;
- self.speed = 50;
- self.use = fd_secret_use;
- IFTARGETED
- {
- }
- else
- self.spawnflags |= SECRET_YES_SHOOT;
-
- if(self.spawnflags&SECRET_YES_SHOOT)
- {
- self.health = 10000;
- self.takedamage = DAMAGE_YES;
- self.event_damage = fd_secret_damage;
- }
- self.oldorigin = self.origin;
- if (!self.wait)
- self.wait = 5; // 5 seconds before closing
-
- self.reset = secret_reset;
- secret_reset();
-}
-
-/*QUAKED spawnfunc_func_fourier (0 .5 .8) ?
-Brush model that moves in a pattern of added up sine waves, can be used e.g. for circular motions.
-netname: list of <frequencymultiplier> <phase> <x> <y> <z> quadruples, separated by spaces; note that phase 0 represents a sine wave, and phase 0.25 a cosine wave (by default, it uses 1 0 0 0 1, to match func_bobbing's defaults
-speed: how long one cycle of frequency multiplier 1 in seconds (default 4)
-height: amplitude modifier (default 32)
-phase: cycle timing adjustment (0-1 as a fraction of the cycle, default 0)
-noise: path/name of looping .wav file to play.
-dmg: Do this mutch dmg every .dmgtime intervall when blocked
-dmgtime: See above.
-*/
-
-void func_fourier_controller_think()
-{
- vector v;
- float n, i, t;
-
- self.nextthink = time + 0.1;
- if(self.owner.active != ACTIVE_ACTIVE)
- {
- self.owner.velocity = '0 0 0';
- return;
- }
-
-
- n = floor((tokenize_console(self.owner.netname)) / 5);
- t = self.nextthink * self.owner.cnt + self.owner.phase * 360;
-
- v = self.owner.destvec;
-
- for(i = 0; i < n; ++i)
- {
- makevectors((t * stof(argv(i*5)) + stof(argv(i*5+1)) * 360) * '0 1 0');
- v = v + ('1 0 0' * stof(argv(i*5+2)) + '0 1 0' * stof(argv(i*5+3)) + '0 0 1' * stof(argv(i*5+4))) * self.owner.height * v_forward.y;
- }
-
- if(self.owner.classname == "func_fourier") // don't brake stuff if the func_fourier was killtarget'ed
- // * 10 so it will arrive in 0.1 sec
- self.owner.velocity = (v - self.owner.origin) * 10;
-}
-
-void spawnfunc_func_fourier()
-{
- entity controller;
- if (self.noise != "")
- {
- precache_sound(self.noise);
- soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
- }
-
- if (!self.speed)
- self.speed = 4;
- if (!self.height)
- self.height = 32;
- self.destvec = self.origin;
- self.cnt = 360 / self.speed;
-
- self.blocked = generic_plat_blocked;
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message2 == ""))
- self.message2 = "was squished by";
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
- self.dmgtime2 = time;
-
- if(self.netname == "")
- self.netname = "1 0 0 0 1";
-
- if (!InitMovingBrushTrigger())
- return;
-
- self.active = ACTIVE_ACTIVE;
-
- // wait for targets to spawn
- controller = spawn();
- controller.classname = "func_fourier_controller";
- controller.owner = self;
- controller.nextthink = time + 1;
- controller.think = func_fourier_controller_think;
- self.nextthink = self.ltime + 999999999;
- self.think = SUB_NullThink; // for PushMove
-
- // Savage: Reduce bandwith, critical on e.g. nexdm02
- self.effects |= EF_LOWPRECISION;
-
- // TODO make a reset function for this one
-}
-
-// reusing some fields havocbots declared
-.entity wp00, wp01, wp02, wp03;
-
-.float targetfactor, target2factor, target3factor, target4factor;
-.vector targetnormal, target2normal, target3normal, target4normal;
-
-vector func_vectormamamam_origin(entity o, float t)
-{
- vector v, p;
- float f;
- entity e;
-
- f = o.spawnflags;
- v = '0 0 0';
-
- e = o.wp00;
- if(e)
- {
- p = e.origin + t * e.velocity;
- if(f & 1)
- v = v + (p * o.targetnormal) * o.targetnormal * o.targetfactor;
- else
- v = v + (p - (p * o.targetnormal) * o.targetnormal) * o.targetfactor;
- }
-
- e = o.wp01;
- if(e)
- {
- p = e.origin + t * e.velocity;
- if(f & 2)
- v = v + (p * o.target2normal) * o.target2normal * o.target2factor;
- else
- v = v + (p - (p * o.target2normal) * o.target2normal) * o.target2factor;
- }
-
- e = o.wp02;
- if(e)
- {
- p = e.origin + t * e.velocity;
- if(f & 4)
- v = v + (p * o.target3normal) * o.target3normal * o.target3factor;
- else
- v = v + (p - (p * o.target3normal) * o.target3normal) * o.target3factor;
- }
-
- e = o.wp03;
- if(e)
- {
- p = e.origin + t * e.velocity;
- if(f & 8)
- v = v + (p * o.target4normal) * o.target4normal * o.target4factor;
- else
- v = v + (p - (p * o.target4normal) * o.target4normal) * o.target4factor;
- }
-
- return v;
-}
-
-void func_vectormamamam_controller_think()
-{
- self.nextthink = time + 0.1;
-
- if(self.owner.active != ACTIVE_ACTIVE)
- {
- self.owner.velocity = '0 0 0';
- return;
- }
-
- if(self.owner.classname == "func_vectormamamam") // don't brake stuff if the func_vectormamamam was killtarget'ed
- self.owner.velocity = (self.owner.destvec + func_vectormamamam_origin(self.owner, 0.1) - self.owner.origin) * 10;
-}
-
-void func_vectormamamam_findtarget()
-{
- if(self.target != "")
- self.wp00 = find(world, targetname, self.target);
-
- if(self.target2 != "")
- self.wp01 = find(world, targetname, self.target2);
-
- if(self.target3 != "")
- self.wp02 = find(world, targetname, self.target3);
-
- if(self.target4 != "")
- self.wp03 = find(world, targetname, self.target4);
-
- if(!self.wp00 && !self.wp01 && !self.wp02 && !self.wp03)
- objerror("No reference entity found, so there is nothing to move. Aborting.");
-
- self.destvec = self.origin - func_vectormamamam_origin(self, 0);
-
- entity controller;
- controller = spawn();
- controller.classname = "func_vectormamamam_controller";
- controller.owner = self;
- controller.nextthink = time + 1;
- controller.think = func_vectormamamam_controller_think;
-}
-
-void spawnfunc_func_vectormamamam()
-{
- if (self.noise != "")
- {
- precache_sound(self.noise);
- soundto(MSG_INIT, self, CH_TRIGGER_SINGLE, self.noise, VOL_BASE, ATTEN_IDLE);
- }
-
- if(!self.targetfactor)
- self.targetfactor = 1;
-
- if(!self.target2factor)
- self.target2factor = 1;
-
- if(!self.target3factor)
- self.target3factor = 1;
-
- if(!self.target4factor)
- self.target4factor = 1;
-
- if(vlen(self.targetnormal))
- self.targetnormal = normalize(self.targetnormal);
-
- if(vlen(self.target2normal))
- self.target2normal = normalize(self.target2normal);
-
- if(vlen(self.target3normal))
- self.target3normal = normalize(self.target3normal);
-
- if(vlen(self.target4normal))
- self.target4normal = normalize(self.target4normal);
-
- self.blocked = generic_plat_blocked;
- if(self.dmg && (self.message == ""))
- self.message = " was squished";
- if(self.dmg && (self.message == ""))
- self.message2 = "was squished by";
- if(self.dmg && (!self.dmgtime))
- self.dmgtime = 0.25;
- self.dmgtime2 = time;
-
- if(self.netname == "")
- self.netname = "1 0 0 0 1";
-
- if (!InitMovingBrushTrigger())
- return;
-
- // wait for targets to spawn
- self.nextthink = self.ltime + 999999999;
- self.think = SUB_NullThink; // for PushMove
-
- // Savage: Reduce bandwith, critical on e.g. nexdm02
- self.effects |= EF_LOWPRECISION;
-
- self.active = ACTIVE_ACTIVE;
-
- InitializeEntity(self, func_vectormamamam_findtarget, INITPRIO_FINDTARGET);
-}
-
-#endif
-
-void conveyor_think()
-{
-#ifdef CSQC
- float dt = time - self.move_time;
- self.move_time = time;
- if(dt <= 0) { return; }
-#endif
- entity e;
-
- // set myself as current conveyor where possible
- for(e = world; (e = findentity(e, conveyor, self)); )
- e.conveyor = world;
-
- if(self.state)
- {
- for(e = findradius((self.absmin + self.absmax) * 0.5, vlen(self.absmax - self.absmin) * 0.5 + 1); e; e = e.chain)
- if(!e.conveyor.state)
-#ifdef SVQC
- if(isPushable(e))
-#elif defined(CSQC)
- if(e.isplayermodel)
-#endif
- {
- vector emin = e.absmin;
- vector emax = e.absmax;
- if(self.solid == SOLID_BSP)
- {
- emin -= '1 1 1';
- emax += '1 1 1';
- }
- if(boxesoverlap(emin, emax, self.absmin, self.absmax)) // quick
- if(WarpZoneLib_BoxTouchesBrush(emin, emax, self, e)) // accurate
- e.conveyor = self;
- }
-
- for(e = world; (e = findentity(e, conveyor, self)); )
- {
-#ifdef SVQC
- if(IS_CLIENT(e)) // doing it via velocity has quite some advantages
- continue; // done in SV_PlayerPhysics
-#elif defined(CSQC)
- if(e.isplayermodel)
- continue;
-#endif
-
- setorigin(e, e.origin + self.movedir * PHYS_INPUT_FRAMETIME);
- move_out_of_solid(e);
-#ifdef SVQC
- UpdateCSQCProjectile(e);
-#endif
- /*
- // stupid conveyor code
- tracebox(e.origin, e.mins, e.maxs, e.origin + self.movedir * sys_frametime, MOVE_NORMAL, e);
- if(trace_fraction > 0)
- setorigin(e, trace_endpos);
- */
- }
- }
-
-#ifdef SVQC
- self.nextthink = time;
-#endif
-}
-
-#ifdef SVQC
-
-void conveyor_use()
-{
- self.state = !self.state;
-
- self.SendFlags |= 2;
-}
-
-void conveyor_reset()
-{
- self.state = (self.spawnflags & 1);
-
- self.SendFlags |= 2;
-}
-
-float conveyor_send(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_CONVEYOR);
- WriteByte(MSG_ENTITY, sf);
-
- if(sf & 1)
- {
- WriteByte(MSG_ENTITY, self.warpzone_isboxy);
- WriteCoord(MSG_ENTITY, self.origin_x);
- WriteCoord(MSG_ENTITY, self.origin_y);
- WriteCoord(MSG_ENTITY, self.origin_z);
-
- WriteCoord(MSG_ENTITY, self.mins_x);
- WriteCoord(MSG_ENTITY, self.mins_y);
- WriteCoord(MSG_ENTITY, self.mins_z);
- WriteCoord(MSG_ENTITY, self.maxs_x);
- WriteCoord(MSG_ENTITY, self.maxs_y);
- WriteCoord(MSG_ENTITY, self.maxs_z);
-
- WriteCoord(MSG_ENTITY, self.movedir_x);
- WriteCoord(MSG_ENTITY, self.movedir_y);
- WriteCoord(MSG_ENTITY, self.movedir_z);
-
- WriteByte(MSG_ENTITY, self.speed);
- WriteByte(MSG_ENTITY, self.state);
-
- WriteString(MSG_ENTITY, self.targetname);
- WriteString(MSG_ENTITY, self.target);
- }
-
- if(sf & 2)
- WriteByte(MSG_ENTITY, self.state);
-
- return true;
-}
-
-void conveyor_init()
-{
- if (!self.speed)
- self.speed = 200;
- self.movedir = self.movedir * self.speed;
- self.think = conveyor_think;
- self.nextthink = time;
- IFTARGETED
- {
- self.use = conveyor_use;
- self.reset = conveyor_reset;
- conveyor_reset();
- }
- else
- self.state = 1;
-
- Net_LinkEntity(self, 0, false, conveyor_send);
-
- self.SendFlags |= 1;
-}
-
-void spawnfunc_trigger_conveyor()
-{
- SetMovedir();
- EXACTTRIGGER_INIT;
- conveyor_init();
-}
-
-void spawnfunc_func_conveyor()
-{
- SetMovedir();
- InitMovingBrushTrigger();
- self.movetype = MOVETYPE_NONE;
- conveyor_init();
-}
-
-#elif defined(CSQC)
-
-void conveyor_init()
-{
- self.draw = conveyor_think;
- self.drawmask = MASK_NORMAL;
-
- self.movetype = MOVETYPE_NONE;
- self.model = "";
- self.solid = SOLID_TRIGGER;
- self.move_origin = self.origin;
- self.move_time = time;
-}
-
-void ent_conveyor()
-{
- float sf = ReadByte();
-
- if(sf & 1)
- {
- self.warpzone_isboxy = ReadByte();
- self.origin_x = ReadCoord();
- self.origin_y = ReadCoord();
- self.origin_z = ReadCoord();
- setorigin(self, self.origin);
-
- self.mins_x = ReadCoord();
- self.mins_y = ReadCoord();
- self.mins_z = ReadCoord();
- self.maxs_x = ReadCoord();
- self.maxs_y = ReadCoord();
- self.maxs_z = ReadCoord();
- setsize(self, self.mins, self.maxs);
-
- self.movedir_x = ReadCoord();
- self.movedir_y = ReadCoord();
- self.movedir_z = ReadCoord();
-
- self.speed = ReadByte();
- self.state = ReadByte();
-
- self.targetname = strzone(ReadString());
- self.target = strzone(ReadString());
-
- conveyor_init();
- }
-
- if(sf & 2)
- self.state = ReadByte();
-}
-
-#endif
+++ /dev/null
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "../warpzonelib/util_server.qh"
- #include "../common/weapons/weapons.qh"
- #include "defs.qh"
- #include "../common/deathtypes.qh"
-#endif
-
-/*
-* t_swamp.c
-* Adds spawnfunc_trigger_swamp and suppoart routines for xonotic 1.2.1+
-* Author tZork (Jakob MG)
-* jakob@games43.se
-* 2005 11 29
-*/
-
-.float swamp_interval; //Hurt players in swamp with this interval
-.float swamp_slowdown; //Players in swamp get slowd down by this mutch 0-1 is slowdown 1-~ is speedup (!?)
-.entity swampslug;
-
-void spawnfunc_trigger_swamp(void);
-void swamp_touch(void);
-void swampslug_think();
-
-
-/*
-* Uses a entity calld swampslug to handle players in the swamp
-* It works like this: When the plyer enters teh swamp the spawnfunc_trigger_swamp
-* attaches a new "swampslug" to the player. As long as the plyer is inside
-* the swamp the swamp gives the slug new health. But the slug slowly kills itself
-* so when the player goes outside the swamp, it dies and releases the player from the
-* swamps curses (dmg/slowdown)
-*
-* I do it this way becuz there is no "untouch" event.
-*
-* --NOTE--
-* THE ACCTUAL slowdown is done in cl_physics.c on line 57-60
-* --NOTE--
-*/
-void swampslug_think(void)
-{
- //Slowly kill the slug
- self.health = self.health - 1;
-
- //Slug dead? then remove curses.
- if(self.health <= 0) {
- self.owner.in_swamp = 0;
- remove(self);
- //centerprint(self.owner,"Killing slug...\n");
- return;
- }
-
- // Slug still alive, so we are still in the swamp
- // Or we have exited it very recently.
- // Do the damage and renew the timer.
- Damage (self.owner, self, self, self.dmg, DEATH_SWAMP, other.origin, '0 0 0');
-
- self.nextthink = time + self.swamp_interval;
-}
-
-void swamp_touch(void)
-{
- // If whatever thats touching the swamp is not a player
- // or if its a dead player, just dont care abt it.
- if(!IS_PLAYER(other) || other.deadflag != DEAD_NO)
- return;
-
- EXACTTRIGGER_TOUCH;
-
- // Chech if player alredy got a swampslug.
- if(other.in_swamp != 1) {
- // If not attach one.
- //centerprint(other,"Entering swamp!\n");
- other.swampslug = spawn();
- other.swampslug.health = 2;
- other.swampslug.think = swampslug_think;
- other.swampslug.nextthink = time;
- other.swampslug.owner = other;
- other.swampslug.dmg = self.dmg;
- other.swampslug.swamp_interval = self.swamp_interval;
- other.swamp_slowdown = self.swamp_slowdown;
- other.in_swamp = 1;
- return;
- }
-
- //other.in_swamp = 1;
-
- //Revitalize players swampslug
- other.swampslug.health = 2;
-}
-
-/*QUAKED spawnfunc_trigger_swamp (.5 .5 .5) ?
-Players gettin into the swamp will
-get slowd down and damaged
-*/
-void spawnfunc_trigger_swamp(void)
-{
- // Init stuff
- EXACTTRIGGER_INIT;
- self.touch = swamp_touch;
-
- // Setup default keys, if missing
- if(self.dmg <= 0)
- self.dmg = 5;
- if(self.swamp_interval <= 0)
- self.swamp_interval = 1;
- if(self.swamp_slowdown <= 0)
- self.swamp_slowdown = 0.5;
-}
#include "../warpzonelib/util_server.qh"
#include "../warpzonelib/server.qh"
#include "../common/constants.qh"
+ #include "../common/triggers/subs.qh"
#include "../common/util.qh"
#include "weapons/csqcprojectile.qh"
#include "autocvars.qh"
+++ /dev/null
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "../dpdefs/dpextensions.qh"
- #include "../common/constants.qh"
- #include "constants.qh"
- #include "defs.qh"
-#endif
-
-.float lifetime;
-// values:
-// volume
-// noise
-// targetname
-// lifetime
-// fade_time
-// fade_rate
-// when triggered, the music is overridden for activator until lifetime (or forever, if lifetime is 0)
-// when targetname is not set, THIS ONE is default
-void target_music_sendto(float to, float is)
-{
- WriteByte(to, SVC_TEMPENTITY);
- WriteByte(to, TE_CSQC_TARGET_MUSIC);
- WriteShort(to, num_for_edict(self));
- WriteByte(to, self.volume * 255.0 * is);
- WriteByte(to, self.fade_time * 16.0);
- WriteByte(to, self.fade_rate * 16.0);
- WriteByte(to, self.lifetime);
- WriteString(to, self.noise);
-}
-void target_music_reset()
-{
- if(self.targetname == "")
- target_music_sendto(MSG_ALL, 1);
-}
-void target_music_use()
-{
- if(!activator)
- return;
- if(IS_REAL_CLIENT(activator))
- {
- msg_entity = activator;
- target_music_sendto(MSG_ONE, 1);
- }
- entity head;
- FOR_EACH_SPEC(head) if(head.enemy == activator) { msg_entity = head; target_music_sendto(MSG_ONE, 1); }
-}
-void spawnfunc_target_music()
-{
- self.use = target_music_use;
- self.reset = target_music_reset;
- if(!self.volume)
- self.volume = 1;
- if(self.targetname == "")
- target_music_sendto(MSG_INIT, 1);
- else
- target_music_sendto(MSG_INIT, 0);
-}
-void TargetMusic_RestoreGame()
-{
- for(self = world; (self = find(self, classname, "target_music")); )
- {
- if(self.targetname == "")
- target_music_sendto(MSG_INIT, 1);
- else
- target_music_sendto(MSG_INIT, 0);
- }
-}
-// values:
-// volume
-// noise
-// targetname
-// fade_time
-// spawnflags:
-// 1 = START_OFF
-// when triggered, it is disabled/enabled for everyone
-float trigger_music_SendEntity(entity to, float sf)
-{
- WriteByte(MSG_ENTITY, ENT_CLIENT_TRIGGER_MUSIC);
- sf &= ~0x80;
- if(self.cnt)
- sf |= 0x80;
- WriteByte(MSG_ENTITY, sf);
- if(sf & 4)
- {
- WriteCoord(MSG_ENTITY, self.origin.x);
- WriteCoord(MSG_ENTITY, self.origin.y);
- WriteCoord(MSG_ENTITY, self.origin.z);
- }
- if(sf & 1)
- {
- if(self.model != "null")
- {
- WriteShort(MSG_ENTITY, self.modelindex);
- WriteCoord(MSG_ENTITY, self.mins.x);
- WriteCoord(MSG_ENTITY, self.mins.y);
- WriteCoord(MSG_ENTITY, self.mins.z);
- WriteCoord(MSG_ENTITY, self.maxs.x);
- WriteCoord(MSG_ENTITY, self.maxs.y);
- WriteCoord(MSG_ENTITY, self.maxs.z);
- }
- else
- {
- WriteShort(MSG_ENTITY, 0);
- WriteCoord(MSG_ENTITY, self.maxs.x);
- WriteCoord(MSG_ENTITY, self.maxs.y);
- WriteCoord(MSG_ENTITY, self.maxs.z);
- }
- WriteByte(MSG_ENTITY, self.volume * 255.0);
- WriteByte(MSG_ENTITY, self.fade_time * 16.0);
- WriteByte(MSG_ENTITY, self.fade_rate * 16.0);
- WriteString(MSG_ENTITY, self.noise);
- }
- return 1;
-}
-void trigger_music_reset()
-{
- self.cnt = !(self.spawnflags & 1);
- self.SendFlags |= 0x80;
-}
-void trigger_music_use()
-{
- self.cnt = !self.cnt;
- self.SendFlags |= 0x80;
-}
-void spawnfunc_trigger_music()
-{
- if(self.model != "")
- setmodel(self, self.model);
- if(!self.volume)
- self.volume = 1;
- if(!self.modelindex)
- {
- setorigin(self, self.origin + self.mins);
- setsize(self, '0 0 0', self.maxs - self.mins);
- }
- trigger_music_reset();
-
- self.use = trigger_music_use;
- self.reset = trigger_music_reset;
-
- Net_LinkEntity(self, false, 0, trigger_music_SendEntity);
-}
+++ /dev/null
-#if defined(CSQC)
-#elif defined(MENUQC)
-#elif defined(SVQC)
- #include "../dpdefs/progsdefs.qh"
- #include "../dpdefs/dpextensions.qh"
- #include "../common/util.qh"
- #include "defs.qh"
-#endif
-
-// spawner entity
-// "classname" "target_spawn"
-// "message" "fieldname value fieldname value ..."
-// "spawnflags"
-// 1 = call the spawn function
-// 2 = trigger on map load
-
-float target_spawn_initialized;
-.void() target_spawn_spawnfunc;
-float target_spawn_spawnfunc_field;
-.entity target_spawn_activator;
-.float target_spawn_id;
-float target_spawn_count;
-
-void target_spawn_helper_setmodel()
-{
- setmodel(self, self.model);
-}
-
-void target_spawn_helper_setsize()
-{
- setsize(self, self.mins, self.maxs);
-}
-
-void target_spawn_edit_entity(entity e, string msg, entity kt, entity t2, entity t3, entity t4, entity act)
-{
- float i, n, valuefieldpos;
- string key, value, valuefield, valueoffset, valueoffsetrandom;
- entity valueent;
- vector data, data2;
- entity oldself;
- entity oldactivator;
-
- n = tokenize_console(msg);
-
- for(i = 0; i < n-1; i += 2)
- {
- key = argv(i);
- value = argv(i+1);
- if(key == "$")
- {
- data.x = -1;
- data.y = FIELD_STRING;
- }
- else
- {
- data = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", key)));
- if(data.y == 0) // undefined field, i.e., invalid type
- {
- print("target_spawn: invalid/unknown entity key ", key, " specified, ignored!\n");
- continue;
- }
- }
- if(substring(value, 0, 1) == "$")
- {
- value = substring(value, 1, strlen(value) - 1);
- if(substring(value, 0, 1) == "$")
- {
- // deferred replacement
- // do nothing
- // useful for creating target_spawns with this!
- }
- else
- {
- // replace me!
- valuefieldpos = strstrofs(value, "+", 0);
- valueoffset = "";
- if(valuefieldpos != -1)
- {
- valueoffset = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
- value = substring(value, 0, valuefieldpos);
- }
-
- valuefieldpos = strstrofs(valueoffset, "+", 0);
- valueoffsetrandom = "";
- if(valuefieldpos != -1)
- {
- valueoffsetrandom = substring(valueoffset, valuefieldpos + 1, strlen(valueoffset) - valuefieldpos - 1);
- valueoffset = substring(valueoffset, 0, valuefieldpos);
- }
-
- valuefieldpos = strstrofs(value, ".", 0);
- valuefield = "";
- if(valuefieldpos != -1)
- {
- valuefield = substring(value, valuefieldpos + 1, strlen(value) - valuefieldpos - 1);
- value = substring(value, 0, valuefieldpos);
- }
-
- if(value == "self")
- {
- valueent = self;
- value = "";
- }
- else if(value == "activator")
- {
- valueent = act;
- value = "";
- }
- else if(value == "other")
- {
- valueent = other;
- value = "";
- }
- else if(value == "pusher")
- {
- if(time < act.pushltime)
- valueent = act.pusher;
- else
- valueent = world;
- value = "";
- }
- else if(value == "target")
- {
- valueent = e;
- value = "";
- }
- else if(value == "killtarget")
- {
- valueent = kt;
- value = "";
- }
- else if(value == "target2")
- {
- valueent = t2;
- value = "";
- }
- else if(value == "target3")
- {
- valueent = t3;
- value = "";
- }
- else if(value == "target4")
- {
- valueent = t4;
- value = "";
- }
- else if(value == "time")
- {
- valueent = world;
- value = ftos(time);
- }
- else
- {
- print("target_spawn: invalid/unknown variable replacement ", value, " specified, ignored!\n");
- continue;
- }
-
- if(valuefield == "")
- {
- if(value == "")
- value = ftos(num_for_edict(valueent));
- }
- else
- {
- if(value != "")
- {
- print("target_spawn: try to get a field of a non-entity, ignored!\n");
- continue;
- }
- data2 = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", valuefield)));
- if(data2_y == 0) // undefined field, i.e., invalid type
- {
- print("target_spawn: invalid/unknown entity key replacement ", valuefield, " specified, ignored!\n");
- continue;
- }
- value = getentityfieldstring(data2_x, valueent);
- }
-
- if(valueoffset != "")
- {
- switch(data.y)
- {
- case FIELD_STRING:
- value = strcat(value, valueoffset);
- break;
- case FIELD_FLOAT:
- value = ftos(stof(value) + stof(valueoffset));
- break;
- case FIELD_VECTOR:
- value = vtos(stov(value) + stov(valueoffset));
- break;
- default:
- print("target_spawn: only string, float and vector fields can do calculations, calculation ignored!\n");
- break;
- }
- }
-
- if(valueoffsetrandom != "")
- {
- switch(data.y)
- {
- case FIELD_FLOAT:
- value = ftos(stof(value) + random() * stof(valueoffsetrandom));
- break;
- case FIELD_VECTOR:
- data2 = stov(valueoffsetrandom);
- value = vtos(stov(value) + random() * data2_x * '1 0 0' + random() * data2_y * '0 1 0' + random() * data2_z * '0 0 1');
- break;
- default:
- print("target_spawn: only float and vector fields can do random calculations, calculation ignored!\n");
- break;
- }
- }
- }
- }
- if(key == "$")
- {
- if(substring(value, 0, 1) == "_")
- value = strcat("target_spawn_helper", value);
- putentityfieldstring(target_spawn_spawnfunc_field, e, value);
-
- oldself = self;
- oldactivator = activator;
-
- self = e;
- activator = act;
-
- self.target_spawn_spawnfunc();
-
- self = oldself;
- activator = oldactivator;
- }
- else
- {
- if(data.y == FIELD_VECTOR)
- value = strreplace("'", "", value); // why?!?
- putentityfieldstring(data.x, e, value);
- }
- }
-}
-
-void target_spawn_useon(entity e)
-{
- self.target_spawn_activator = activator;
- target_spawn_edit_entity(
- e,
- self.message,
- find(world, targetname, self.killtarget),
- find(world, targetname, self.target2),
- find(world, targetname, self.target3),
- find(world, targetname, self.target4),
- activator
- );
-}
-
-float target_spawn_cancreate()
-{
- float c;
- entity e;
-
- c = self.count;
- if(c == 0) // no limit?
- return 1;
-
- ++c; // increase count to not include MYSELF
- for(e = world; (e = findfloat(e, target_spawn_id, self.target_spawn_id)); --c)
- ;
-
- // if c now is 0, we have AT LEAST the given count (maybe more), so don't spawn any more
- if(c == 0)
- return 0;
- return 1;
-}
-
-void target_spawn_use()
-{
- entity e;
-
- if(self.target == "")
- {
- // spawn new entity
- if(!target_spawn_cancreate())
- return;
- e = spawn();
- target_spawn_useon(e);
- e.target_spawn_id = self.target_spawn_id;
- }
- else if(self.target == "*activator")
- {
- // edit entity
- if(activator)
- target_spawn_useon(activator);
- }
- else
- {
- // edit entity
- for(e = world; (e = find(e, targetname, self.target)); )
- target_spawn_useon(e);
- }
-}
-
-void target_spawn_spawnfirst()
-{
- activator = self.target_spawn_activator;
- if(self.spawnflags & 2)
- target_spawn_use();
-}
-
-void initialize_field_db()
-{
- if(!target_spawn_initialized)
- {
- float n, i;
- string fn;
- vector prev, new;
- float ft;
-
- n = numentityfields();
- for(i = 0; i < n; ++i)
- {
- fn = entityfieldname(i);
- ft = entityfieldtype(i);
- new = i * '1 0 0' + ft * '0 1 0' + '0 0 1';
- prev = stov(db_get(TemporaryDB, strcat("/target_spawn/field/", fn)));
- if(prev.y == 0)
- {
- db_put(TemporaryDB, strcat("/target_spawn/field/", fn), vtos(new));
- if(fn == "target_spawn_spawnfunc")
- target_spawn_spawnfunc_field = i;
- }
- }
-
- target_spawn_initialized = 1;
- }
-}
-
-void spawnfunc_target_spawn()
-{
- initialize_field_db();
- self.use = target_spawn_use;
- self.message = strzone(strreplace("'", "\"", self.message));
- self.target_spawn_id = ++target_spawn_count;
- InitializeEntity(self, target_spawn_spawnfirst, INITPRIO_LAST);
-}
-
-
-void trigger_relay_if_use()
-{
- float n;
- n = self.count;
-
- // TODO make this generic AND faster than nextent()ing through all, if somehow possible
- n = (cvar_string(self.netname) == cvar_string(self.message));
- if(self.spawnflags & 1)
- n = !n;
-
- if(n)
- SUB_UseTargets();
-}
-
-void spawnfunc_trigger_relay_if()
-{
- self.use = trigger_relay_if_use;
-}
+#include "../../../common/triggers/subs.qh"
+
#define cvar_base "g_turrets_unit_"
.float clientframe;
void turrets_setframe(float _frame, float client_only)
#ifdef TURRET_DEBUG
-void SUB_Remove();
void marker_think()
{
if(self.cnt)
const vector RACER_MAX = '120 120 40';
#ifdef SVQC
+#include "../../common/triggers/trigger/impulse.qh"
+
void racer_exit(float eject);
void racer_enter();
+#include "../../common/triggers/subs.qh"
+
float autocvar_g_vehicles_crush_dmg;
float autocvar_g_vehicles_crush_force;
float autocvar_g_vehicles_delayspawn;
return nearest;
}
+float WarpZoneLib_BadClassname(string myclassname)
+{
+ switch(myclassname)
+ {
+ case "weapon_info":
+ case "monster_info":
+ case "deathtype":
+ case "callback":
+ case "callbackchain":
+ case "weaponentity":
+ case "exteriorweaponentity":
+ case "csqc_score_team":
+ case "pingplreport":
+ case "ent_client_scoreinfo":
+ case "saved_cvar_value":
+ case "accuracy":
+ case "entcs_sender_v2":
+ case "entcs_receiver_v2":
+ case "clientinit":
+ case "sprite_waypoint":
+ case "waypoint":
+ case "gibsplash":
+ //case "net_linked": // actually some real entities are linked without classname, fail
+ case "":
+ return true;
+ }
+
+ if(startsWith(myclassname, "msg_"))
+ return true;
+
+ if(startsWith(myclassname, "target_"))
+ return true;
+
+ if(startsWith(myclassname, "info_"))
+ return true;
+
+ return false;
+}
+
.float WarpZone_findradius_hit;
.entity WarpZone_findradius_next;
void WarpZone_FindRadius_Recurse(vector org, float rad, vector org0, vector transform, vector shift, float needlineofsight)
for(e = e0; e; e = e.chain)
{
+ if(WarpZoneLib_BadClassname(e.classname))
+ continue;
+ print(e.classname, ", first check\n");
p = WarpZoneLib_NearestPointOnBox(e.origin + e.mins, e.origin + e.maxs, org0);
if(needlineofsight)
{
}
for(e = wz; e; e = e.WarpZone_findradius_next)
{
+ if(WarpZoneLib_BadClassname(e.classname))
+ continue;
+
+ print(e.classname, ", second check\n");
+
org0_new = WarpZone_TransformOrigin(e, org);
traceline(e.warpzone_targetorigin, org0_new, MOVE_NOMONSTERS, e);
org_new = trace_endpos;
#include "common.qh"
#include "server.qh"
#include "../common/constants.qh"
+ #include "../common/triggers/subs.qh"
#include "../common/util.qh"
#include "../server/constants.qh"
#include "../server/defs.qh"