From e053dbda6abe97a2ca90c23de16dde99d253dcc8 Mon Sep 17 00:00:00 2001 From: Mario Date: Fri, 30 Jan 2015 01:38:01 +1100 Subject: [PATCH] It actually compiles --- qcsrc/client/command/cl_cmd.qc | 26 + qcsrc/client/progs.src | 3 - qcsrc/common/physics.qc | 14 - qcsrc/common/triggers/func/bobbing.qc | 85 + qcsrc/common/triggers/func/button.qc | 155 ++ qcsrc/common/triggers/func/conveyor.qc | 197 ++ .../triggers/{f_door.qc => func/door.qc} | 116 +- .../triggers/{f_door.qh => func/door.qh} | 0 qcsrc/common/triggers/func/door_rotating.qc | 126 + qcsrc/common/triggers/func/door_secret.qc | 236 ++ qcsrc/common/triggers/func/fourier.qc | 89 + qcsrc/common/triggers/func/include.qc | 16 + qcsrc/common/triggers/func/include.qh | 2 + qcsrc/common/triggers/func/ladder.qc | 115 + qcsrc/common/triggers/func/ladder.qh | 2 + qcsrc/common/triggers/func/pendulum.qc | 77 + qcsrc/common/triggers/func/plat.qc | 69 + qcsrc/common/triggers/func/pointparticles.qc | 180 ++ qcsrc/common/triggers/func/rainsnow.qc | 92 + qcsrc/common/triggers/func/rotating.qc | 77 + qcsrc/common/triggers/func/stardust.qc | 6 + qcsrc/common/triggers/func/train.qc | 165 ++ qcsrc/common/triggers/func/vectormamamam.qc | 159 ++ qcsrc/common/triggers/include.qc | 16 +- qcsrc/common/triggers/include.qh | 18 +- qcsrc/common/triggers/misc/corner.qc | 9 + qcsrc/common/triggers/misc/follow.qc | 67 + qcsrc/common/triggers/misc/include.qc | 3 + qcsrc/common/triggers/misc/include.qh | 1 + qcsrc/common/triggers/misc/laser.qc | 257 +++ qcsrc/common/triggers/platforms.qc | 198 ++ qcsrc/common/triggers/platforms.qh | 11 + qcsrc/common/triggers/target/changelevel.qc | 18 + qcsrc/common/triggers/target/include.qc | 3 + qcsrc/common/triggers/target/include.qh | 1 + qcsrc/common/triggers/target/speaker.qc | 133 ++ qcsrc/common/triggers/target/voicescript.qc | 101 + qcsrc/common/triggers/trigger/counter.qc | 49 + qcsrc/common/triggers/trigger/delay.qc | 22 + qcsrc/common/triggers/trigger/disablerelay.qc | 31 + qcsrc/common/triggers/trigger/flipflop.qc | 19 + qcsrc/common/triggers/trigger/gamestart.qc | 22 + qcsrc/common/triggers/trigger/gravity.qc | 106 + qcsrc/common/triggers/trigger/heal.qc | 42 + qcsrc/common/triggers/trigger/hurt.qc | 92 + qcsrc/common/triggers/trigger/impulse.qc | 152 ++ qcsrc/common/triggers/trigger/include.qc | 17 + qcsrc/common/triggers/trigger/include.qh | 1 + .../triggers/trigger/jumppads.qc} | 27 +- qcsrc/common/triggers/trigger/magicear.qc | 204 ++ qcsrc/common/triggers/trigger/monoflop.qc | 49 + qcsrc/common/triggers/trigger/multi.qc | 204 ++ qcsrc/common/triggers/trigger/multi.qh | 4 + .../common/triggers/trigger/multivibrator.qc | 73 + qcsrc/common/triggers/trigger/relay.qc | 10 + .../triggers/trigger/relay_activators.qc | 45 + .../triggers/trigger/relay_teamcheck.qc | 35 + qcsrc/common/triggers/triggers.qc | 2033 +---------------- qcsrc/common/triggers/triggers.qh | 21 +- qcsrc/server/item_key.qh | 3 +- qcsrc/server/miscfunctions.qc | 80 +- qcsrc/server/progs.src | 5 +- qcsrc/server/t_halflife.qc | 121 - qcsrc/server/t_plats.qc | 1650 ------------- 64 files changed, 4014 insertions(+), 3946 deletions(-) create mode 100644 qcsrc/common/triggers/func/bobbing.qc create mode 100644 qcsrc/common/triggers/func/button.qc create mode 100644 qcsrc/common/triggers/func/conveyor.qc rename qcsrc/common/triggers/{f_door.qc => func/door.qc} (92%) rename qcsrc/common/triggers/{f_door.qh => func/door.qh} (100%) create mode 100644 qcsrc/common/triggers/func/door_rotating.qc create mode 100644 qcsrc/common/triggers/func/door_secret.qc create mode 100644 qcsrc/common/triggers/func/fourier.qc create mode 100644 qcsrc/common/triggers/func/include.qc create mode 100644 qcsrc/common/triggers/func/include.qh create mode 100644 qcsrc/common/triggers/func/ladder.qc create mode 100644 qcsrc/common/triggers/func/ladder.qh create mode 100644 qcsrc/common/triggers/func/pendulum.qc create mode 100644 qcsrc/common/triggers/func/plat.qc create mode 100644 qcsrc/common/triggers/func/pointparticles.qc create mode 100644 qcsrc/common/triggers/func/rainsnow.qc create mode 100644 qcsrc/common/triggers/func/rotating.qc create mode 100644 qcsrc/common/triggers/func/stardust.qc create mode 100644 qcsrc/common/triggers/func/train.qc create mode 100644 qcsrc/common/triggers/func/vectormamamam.qc create mode 100644 qcsrc/common/triggers/misc/corner.qc create mode 100644 qcsrc/common/triggers/misc/follow.qc create mode 100644 qcsrc/common/triggers/misc/include.qc create mode 100644 qcsrc/common/triggers/misc/include.qh create mode 100644 qcsrc/common/triggers/misc/laser.qc create mode 100644 qcsrc/common/triggers/platforms.qc create mode 100644 qcsrc/common/triggers/platforms.qh create mode 100644 qcsrc/common/triggers/target/changelevel.qc create mode 100644 qcsrc/common/triggers/target/include.qc create mode 100644 qcsrc/common/triggers/target/include.qh create mode 100644 qcsrc/common/triggers/target/speaker.qc create mode 100644 qcsrc/common/triggers/target/voicescript.qc create mode 100644 qcsrc/common/triggers/trigger/counter.qc create mode 100644 qcsrc/common/triggers/trigger/delay.qc create mode 100644 qcsrc/common/triggers/trigger/disablerelay.qc create mode 100644 qcsrc/common/triggers/trigger/flipflop.qc create mode 100644 qcsrc/common/triggers/trigger/gamestart.qc create mode 100644 qcsrc/common/triggers/trigger/gravity.qc create mode 100644 qcsrc/common/triggers/trigger/heal.qc create mode 100644 qcsrc/common/triggers/trigger/hurt.qc create mode 100644 qcsrc/common/triggers/trigger/impulse.qc create mode 100644 qcsrc/common/triggers/trigger/include.qc create mode 100644 qcsrc/common/triggers/trigger/include.qh rename qcsrc/{server/t_jumppads.qc => common/triggers/trigger/jumppads.qc} (96%) create mode 100644 qcsrc/common/triggers/trigger/magicear.qc create mode 100644 qcsrc/common/triggers/trigger/monoflop.qc create mode 100644 qcsrc/common/triggers/trigger/multi.qc create mode 100644 qcsrc/common/triggers/trigger/multi.qh create mode 100644 qcsrc/common/triggers/trigger/multivibrator.qc create mode 100644 qcsrc/common/triggers/trigger/relay.qc create mode 100644 qcsrc/common/triggers/trigger/relay_activators.qc create mode 100644 qcsrc/common/triggers/trigger/relay_teamcheck.qc delete mode 100644 qcsrc/server/t_plats.qc diff --git a/qcsrc/client/command/cl_cmd.qc b/qcsrc/client/command/cl_cmd.qc index 9aae77dde..477fe2b2e 100644 --- a/qcsrc/client/command/cl_cmd.qc +++ b/qcsrc/client/command/cl_cmd.qc @@ -337,6 +337,31 @@ void LocalCommand_mv_download(float request, float argc) } } +void LocalCommand_find(float request, float 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(float request, float argc) { switch(request) @@ -408,6 +433,7 @@ void LocalCommand_(float 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 */ diff --git a/qcsrc/client/progs.src b/qcsrc/client/progs.src index 0e6a4e962..71c0de988 100644 --- a/qcsrc/client/progs.src +++ b/qcsrc/client/progs.src @@ -125,9 +125,6 @@ command/cl_cmd.qc ../common/physics.qh ../server/mutators/mutator_dodging.qc ../server/mutators/mutator_multijump.qc -../server/t_halflife.qc -../server/t_jumppads.qc -../server/t_plats.qc ../common/nades.qc ../common/buffs.qc diff --git a/qcsrc/common/physics.qc b/qcsrc/common/physics.qc index a2f6b5d4a..5351570cc 100644 --- a/qcsrc/common/physics.qc +++ b/qcsrc/common/physics.qc @@ -1273,18 +1273,6 @@ void PM_ladder(float maxspd_mod) 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'); @@ -1947,8 +1935,6 @@ void PM_Main() #endif CheckPlayerJump(); - PM_check_jumppad(); - if (self.flags & /* FL_WATERJUMP */ 2048) { self.velocity_x = self.movedir_x; diff --git a/qcsrc/common/triggers/func/bobbing.qc b/qcsrc/common/triggers/func/bobbing.qc new file mode 100644 index 000000000..f671b8ac3 --- /dev/null +++ b/qcsrc/common/triggers/func/bobbing.qc @@ -0,0 +1,85 @@ +#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 diff --git a/qcsrc/common/triggers/func/button.qc b/qcsrc/common/triggers/func/button.qc new file mode 100644 index 000000000..4bcbab32c --- /dev/null +++ b/qcsrc/common/triggers/func/button.qc @@ -0,0 +1,155 @@ +#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 diff --git a/qcsrc/common/triggers/func/conveyor.qc b/qcsrc/common/triggers/func/conveyor.qc new file mode 100644 index 000000000..62b2d6848 --- /dev/null +++ b/qcsrc/common/triggers/func/conveyor.qc @@ -0,0 +1,197 @@ +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 diff --git a/qcsrc/common/triggers/f_door.qc b/qcsrc/common/triggers/func/door.qc similarity index 92% rename from qcsrc/common/triggers/f_door.qc rename to qcsrc/common/triggers/func/door.qc index e2840ebc5..8ce8e1fd7 100644 --- a/qcsrc/common/triggers/f_door.qc +++ b/qcsrc/common/triggers/func/door.qc @@ -20,17 +20,6 @@ THINK FUNCTIONS ============================================================================= */ -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); -} - void() door_go_down; void() door_go_up; void() door_rotating_go_down; @@ -181,15 +170,15 @@ float door_check_keys(void) if (!IS_PLAYER(other)) return FALSE; +#ifdef SVQC if (item_keys_usekey(door, other)) { // some keys were used if (other.key_door_messagetime <= time) { -#ifdef SVQC + play2(other, "misc/talk.wav"); Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_ALSONEED, item_keys_keylist(door.itemkeys)); -#endif other.key_door_messagetime = time + 2; } } @@ -198,13 +187,13 @@ float door_check_keys(void) // no keys were used if (other.key_door_messagetime <= time) { -#ifdef SVQC play2(other, "misc/talk.wav"); Send_Notification(NOTIF_ONE, other, MSG_CENTER, CENTER_DOOR_LOCKED_NEED, item_keys_keylist(door.itemkeys)); -#endif + other.key_door_messagetime = time + 2; } } +#endif if (door.itemkeys) { @@ -711,11 +700,13 @@ float door_send(entity to, float sf) 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.warpzone_isboxy); + WriteByte(MSG_ENTITY, self.scale); - WriteAngle(MSG_ENTITY, self.origin_x); - WriteAngle(MSG_ENTITY, self.origin_y); - WriteAngle(MSG_ENTITY, self.origin_z); + 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); @@ -732,25 +723,22 @@ float door_send(entity to, float sf) WriteAngle(MSG_ENTITY, self.angles_y); WriteAngle(MSG_ENTITY, self.angles_z); - WriteAngle(MSG_ENTITY, self.pos1_x); - WriteAngle(MSG_ENTITY, self.pos1_y); - WriteAngle(MSG_ENTITY, self.pos1_z); - WriteAngle(MSG_ENTITY, self.pos2_x); - WriteAngle(MSG_ENTITY, self.pos2_y); - WriteAngle(MSG_ENTITY, self.pos2_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); - WriteByte(MSG_ENTITY, self.wait); + 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); - - WriteString(MSG_ENTITY, self.model); - WriteShort(MSG_ENTITY, self.modelindex); } if(sf & SF_TRIGGER_RESET) @@ -760,16 +748,16 @@ float door_send(entity to, float sf) if(sf & SF_TRIGGER_UPDATE) { - WriteAngle(MSG_ENTITY, self.origin_x); - WriteAngle(MSG_ENTITY, self.origin_y); - WriteAngle(MSG_ENTITY, self.origin_z); - - WriteAngle(MSG_ENTITY, self.pos1_x); - WriteAngle(MSG_ENTITY, self.pos1_y); - WriteAngle(MSG_ENTITY, self.pos1_z); - WriteAngle(MSG_ENTITY, self.pos2_x); - WriteAngle(MSG_ENTITY, self.pos2_y); - WriteAngle(MSG_ENTITY, self.pos2_z); + 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; @@ -890,13 +878,16 @@ void ent_door() float myenemy = ReadShort(); self.sv_entnum = ReadShort(); - self.warpzone_isboxy = ReadByte(); + self.scale = ReadByte(); - self.origin_x = ReadAngle(); - self.origin_y = ReadAngle(); - self.origin_z = ReadAngle(); + 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(); @@ -913,28 +904,25 @@ void ent_door() self.angles_y = ReadAngle(); self.angles_z = ReadAngle(); - self.pos1_x = ReadAngle(); - self.pos1_y = ReadAngle(); - self.pos1_z = ReadAngle(); - self.pos2_x = ReadAngle(); - self.pos2_y = ReadAngle(); - self.pos2_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 = ReadByte(); + self.wait = ReadShort(); self.speed = ReadShort(); self.lip = ReadByte(); self.state = ReadByte(); self.ltime = ReadShort(); - self.model = strzone(ReadString()); - self.modelindex = ReadShort(); - self.movetype = MOVETYPE_PUSH; - self.solid = SOLID_TRIGGER; + self.solid = SOLID_BSP; self.trigger_touch = door_touch; self.draw = trigger_draw_generic; self.drawmask = MASK_NORMAL; @@ -942,6 +930,8 @@ void ent_door() 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)); } @@ -953,17 +943,17 @@ void ent_door() if(sf & SF_TRIGGER_UPDATE) { - self.origin_x = ReadAngle(); - self.origin_y = ReadAngle(); - self.origin_z = ReadAngle(); + self.origin_x = ReadCoord(); + self.origin_y = ReadCoord(); + self.origin_z = ReadCoord(); setorigin(self, self.origin); - self.pos1_x = ReadAngle(); - self.pos1_y = ReadAngle(); - self.pos1_z = ReadAngle(); - self.pos2_x = ReadAngle(); - self.pos2_y = ReadAngle(); - self.pos2_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(); } } diff --git a/qcsrc/common/triggers/f_door.qh b/qcsrc/common/triggers/func/door.qh similarity index 100% rename from qcsrc/common/triggers/f_door.qh rename to qcsrc/common/triggers/func/door.qh diff --git a/qcsrc/common/triggers/func/door_rotating.qc b/qcsrc/common/triggers/func/door_rotating.qc new file mode 100644 index 000000000..bdf05a009 --- /dev/null +++ b/qcsrc/common/triggers/func/door_rotating.qc @@ -0,0 +1,126 @@ +#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 diff --git a/qcsrc/common/triggers/func/door_secret.qc b/qcsrc/common/triggers/func/door_secret.qc new file mode 100644 index 000000000..09ded99b9 --- /dev/null +++ b/qcsrc/common/triggers/func/door_secret.qc @@ -0,0 +1,236 @@ +#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 diff --git a/qcsrc/common/triggers/func/fourier.qc b/qcsrc/common/triggers/func/fourier.qc new file mode 100644 index 000000000..b6ba9cdfb --- /dev/null +++ b/qcsrc/common/triggers/func/fourier.qc @@ -0,0 +1,89 @@ +#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 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 diff --git a/qcsrc/common/triggers/func/include.qc b/qcsrc/common/triggers/func/include.qc new file mode 100644 index 000000000..4fad7b412 --- /dev/null +++ b/qcsrc/common/triggers/func/include.qc @@ -0,0 +1,16 @@ +#include "bobbing.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" diff --git a/qcsrc/common/triggers/func/include.qh b/qcsrc/common/triggers/func/include.qh new file mode 100644 index 000000000..67558ff4e --- /dev/null +++ b/qcsrc/common/triggers/func/include.qh @@ -0,0 +1,2 @@ +#include "door.qh" +#include "ladder.qh" diff --git a/qcsrc/common/triggers/func/ladder.qc b/qcsrc/common/triggers/func/ladder.qc new file mode 100644 index 000000000..dfa3194a4 --- /dev/null +++ b/qcsrc/common/triggers/func/ladder.qc @@ -0,0 +1,115 @@ +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 diff --git a/qcsrc/common/triggers/func/ladder.qh b/qcsrc/common/triggers/func/ladder.qh new file mode 100644 index 000000000..774e7cf40 --- /dev/null +++ b/qcsrc/common/triggers/func/ladder.qh @@ -0,0 +1,2 @@ +.float ladder_time; +.entity ladder_entity; diff --git a/qcsrc/common/triggers/func/pendulum.qc b/qcsrc/common/triggers/func/pendulum.qc new file mode 100644 index 000000000..503b4591d --- /dev/null +++ b/qcsrc/common/triggers/func/pendulum.qc @@ -0,0 +1,77 @@ +#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 diff --git a/qcsrc/common/triggers/func/plat.qc b/qcsrc/common/triggers/func/plat.qc new file mode 100644 index 000000000..14b17d87d --- /dev/null +++ b/qcsrc/common/triggers/func/plat.qc @@ -0,0 +1,69 @@ +#ifdef SVQC +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 +} +#endif diff --git a/qcsrc/common/triggers/func/pointparticles.qc b/qcsrc/common/triggers/func/pointparticles.qc new file mode 100644 index 000000000..896bd796b --- /dev/null +++ b/qcsrc/common/triggers/func/pointparticles.qc @@ -0,0 +1,180 @@ +#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 diff --git a/qcsrc/common/triggers/func/rainsnow.qc b/qcsrc/common/triggers/func/rainsnow.qc new file mode 100644 index 000000000..61922f434 --- /dev/null +++ b/qcsrc/common/triggers/func/rainsnow.qc @@ -0,0 +1,92 @@ +#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 diff --git a/qcsrc/common/triggers/func/rotating.qc b/qcsrc/common/triggers/func/rotating.qc new file mode 100644 index 000000000..2474f3181 --- /dev/null +++ b/qcsrc/common/triggers/func/rotating.qc @@ -0,0 +1,77 @@ +#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 diff --git a/qcsrc/common/triggers/func/stardust.qc b/qcsrc/common/triggers/func/stardust.qc new file mode 100644 index 000000000..e8043fea7 --- /dev/null +++ b/qcsrc/common/triggers/func/stardust.qc @@ -0,0 +1,6 @@ +#ifdef SVQC +void spawnfunc_func_stardust() +{ + self.effects = EF_STARDUST; +} +#endif diff --git a/qcsrc/common/triggers/func/train.qc b/qcsrc/common/triggers/func/train.qc new file mode 100644 index 000000000..e442eb580 --- /dev/null +++ b/qcsrc/common/triggers/func/train.qc @@ -0,0 +1,165 @@ +#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 diff --git a/qcsrc/common/triggers/func/vectormamamam.qc b/qcsrc/common/triggers/func/vectormamamam.qc new file mode 100644 index 000000000..7d0d20fe2 --- /dev/null +++ b/qcsrc/common/triggers/func/vectormamamam.qc @@ -0,0 +1,159 @@ +#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 diff --git a/qcsrc/common/triggers/include.qc b/qcsrc/common/triggers/include.qc index 8a0060915..b90a75c27 100644 --- a/qcsrc/common/triggers/include.qc +++ b/qcsrc/common/triggers/include.qc @@ -1,3 +1,17 @@ +// some required common stuff #include "subs.qc" #include "triggers.qc" -#include "f_door.qc" +#include "platforms.qc" + +// func +#include "func/include.qc" + +// misc +#include "misc/include.qc" + +// target +#include "target/include.qc" + +// trigger +#include "trigger/include.qc" + diff --git a/qcsrc/common/triggers/include.qh b/qcsrc/common/triggers/include.qh index d819fcead..705dec7f1 100644 --- a/qcsrc/common/triggers/include.qh +++ b/qcsrc/common/triggers/include.qh @@ -1,7 +1,19 @@ +// some required common stuff #ifdef CSQC -#include "../../server/item_key.qh" + #include "../../server/item_key.qh" #endif -#include "f_door.qh" #include "triggers.qh" #include "subs.qh" -#include "triggers.qh" +#include "platforms.qh" + +// func +#include "func/include.qh" + +// misc +#include "misc/include.qh" + +// target +#include "target/include.qh" + +// trigger +#include "trigger/include.qh" diff --git a/qcsrc/common/triggers/misc/corner.qc b/qcsrc/common/triggers/misc/corner.qc new file mode 100644 index 000000000..82fe6bbaf --- /dev/null +++ b/qcsrc/common/triggers/misc/corner.qc @@ -0,0 +1,9 @@ +#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 diff --git a/qcsrc/common/triggers/misc/follow.qc b/qcsrc/common/triggers/misc/follow.qc new file mode 100644 index 000000000..aace4531e --- /dev/null +++ b/qcsrc/common/triggers/misc/follow.qc @@ -0,0 +1,67 @@ +#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 diff --git a/qcsrc/common/triggers/misc/include.qc b/qcsrc/common/triggers/misc/include.qc new file mode 100644 index 000000000..16212a4e9 --- /dev/null +++ b/qcsrc/common/triggers/misc/include.qc @@ -0,0 +1,3 @@ +#include "corner.qc" +#include "follow.qc" +#include "laser.qc" diff --git a/qcsrc/common/triggers/misc/include.qh b/qcsrc/common/triggers/misc/include.qh new file mode 100644 index 000000000..8f9537e92 --- /dev/null +++ b/qcsrc/common/triggers/misc/include.qh @@ -0,0 +1 @@ +// nothing yet diff --git a/qcsrc/common/triggers/misc/laser.qc b/qcsrc/common/triggers/misc/laser.qc new file mode 100644 index 000000000..987777a65 --- /dev/null +++ b/qcsrc/common/triggers/misc/laser.qc @@ -0,0 +1,257 @@ +#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 diff --git a/qcsrc/common/triggers/platforms.qc b/qcsrc/common/triggers/platforms.qc new file mode 100644 index 000000000..7dff8429e --- /dev/null +++ b/qcsrc/common/triggers/platforms.qc @@ -0,0 +1,198 @@ +#ifdef SVQC +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'); + } +} + +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; +} +#endif diff --git a/qcsrc/common/triggers/platforms.qh b/qcsrc/common/triggers/platforms.qh new file mode 100644 index 000000000..426b3c52a --- /dev/null +++ b/qcsrc/common/triggers/platforms.qh @@ -0,0 +1,11 @@ +.float dmgtime2; + +#ifdef SVQC +void() plat_center_touch; +void() plat_outside_touch; +void() plat_trigger_use; +void() plat_go_up; +void() plat_go_down; +void() plat_crush; +#endif +const float PLAT_LOW_TRIGGER = 1; diff --git a/qcsrc/common/triggers/target/changelevel.qc b/qcsrc/common/triggers/target/changelevel.qc new file mode 100644 index 000000000..1ec8cc9e7 --- /dev/null +++ b/qcsrc/common/triggers/target/changelevel.qc @@ -0,0 +1,18 @@ +#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 diff --git a/qcsrc/common/triggers/target/include.qc b/qcsrc/common/triggers/target/include.qc new file mode 100644 index 000000000..43e4741cb --- /dev/null +++ b/qcsrc/common/triggers/target/include.qc @@ -0,0 +1,3 @@ +#include "changelevel.qc" +#include "speaker.qc" +#include "voicescript.qc" diff --git a/qcsrc/common/triggers/target/include.qh b/qcsrc/common/triggers/target/include.qh new file mode 100644 index 000000000..8f9537e92 --- /dev/null +++ b/qcsrc/common/triggers/target/include.qh @@ -0,0 +1 @@ +// nothing yet diff --git a/qcsrc/common/triggers/target/speaker.qc b/qcsrc/common/triggers/target/speaker.qc new file mode 100644 index 000000000..7be8b91b4 --- /dev/null +++ b/qcsrc/common/triggers/target/speaker.qc @@ -0,0 +1,133 @@ +#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 diff --git a/qcsrc/common/triggers/target/voicescript.qc b/qcsrc/common/triggers/target/voicescript.qc new file mode 100644 index 000000000..c173d80ee --- /dev/null +++ b/qcsrc/common/triggers/target/voicescript.qc @@ -0,0 +1,101 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/counter.qc b/qcsrc/common/triggers/trigger/counter.qc new file mode 100644 index 000000000..bf1d9b2da --- /dev/null +++ b/qcsrc/common/triggers/trigger/counter.qc @@ -0,0 +1,49 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/delay.qc b/qcsrc/common/triggers/trigger/delay.qc new file mode 100644 index 000000000..981c3fe81 --- /dev/null +++ b/qcsrc/common/triggers/trigger/delay.qc @@ -0,0 +1,22 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/disablerelay.qc b/qcsrc/common/triggers/trigger/disablerelay.qc new file mode 100644 index 000000000..cd5fdff32 --- /dev/null +++ b/qcsrc/common/triggers/trigger/disablerelay.qc @@ -0,0 +1,31 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/flipflop.qc b/qcsrc/common/triggers/trigger/flipflop.qc new file mode 100644 index 000000000..12d8a5940 --- /dev/null +++ b/qcsrc/common/triggers/trigger/flipflop.qc @@ -0,0 +1,19 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/gamestart.qc b/qcsrc/common/triggers/trigger/gamestart.qc new file mode 100644 index 000000000..3ad419d22 --- /dev/null +++ b/qcsrc/common/triggers/trigger/gamestart.qc @@ -0,0 +1,22 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/gravity.qc b/qcsrc/common/triggers/trigger/gravity.qc new file mode 100644 index 000000000..a656afc65 --- /dev/null +++ b/qcsrc/common/triggers/trigger/gravity.qc @@ -0,0 +1,106 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/heal.qc b/qcsrc/common/triggers/trigger/heal.qc new file mode 100644 index 000000000..6d68610b5 --- /dev/null +++ b/qcsrc/common/triggers/trigger/heal.qc @@ -0,0 +1,42 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/hurt.qc b/qcsrc/common/triggers/trigger/hurt.qc new file mode 100644 index 000000000..fbf11a522 --- /dev/null +++ b/qcsrc/common/triggers/trigger/hurt.qc @@ -0,0 +1,92 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/impulse.qc b/qcsrc/common/triggers/trigger/impulse.qc new file mode 100644 index 000000000..dbdfaa0fa --- /dev/null +++ b/qcsrc/common/triggers/trigger/impulse.qc @@ -0,0 +1,152 @@ +#ifdef SVQC +// tZorks trigger impulse / gravity +.float radius; +.float falloff; +.float strength; +.float lastpushtime; + +// 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 diff --git a/qcsrc/common/triggers/trigger/include.qc b/qcsrc/common/triggers/trigger/include.qc new file mode 100644 index 000000000..36e1883e8 --- /dev/null +++ b/qcsrc/common/triggers/trigger/include.qc @@ -0,0 +1,17 @@ +#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_teamcheck.qc" diff --git a/qcsrc/common/triggers/trigger/include.qh b/qcsrc/common/triggers/trigger/include.qh new file mode 100644 index 000000000..ebde18dae --- /dev/null +++ b/qcsrc/common/triggers/trigger/include.qh @@ -0,0 +1 @@ +#include "multi.qh" diff --git a/qcsrc/server/t_jumppads.qc b/qcsrc/common/triggers/trigger/jumppads.qc similarity index 96% rename from qcsrc/server/t_jumppads.qc rename to qcsrc/common/triggers/trigger/jumppads.qc index fa2f4fe50..9b1f112ca 100644 --- a/qcsrc/server/t_jumppads.qc +++ b/qcsrc/common/triggers/trigger/jumppads.qc @@ -1,15 +1,6 @@ -.float height; +// TODO: split target_push and put it in the target folder -#ifdef CSQC -.float active; -.string target; -.string targetname; -#define ACTIVE_NOT 0 -#define ACTIVE_ACTIVE 1 -#define ACTIVE_IDLE 2 -#define ACTIVE_BUSY 2 -#define ACTIVE_TOGGLE 3 -#endif +.float height; #ifdef SVQC @@ -269,7 +260,6 @@ void trigger_push_touch() #endif } -.vector dest; #ifdef SVQC void trigger_push_link(); void trigger_push_updatelink(); @@ -454,15 +444,6 @@ 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(); @@ -496,10 +477,10 @@ void ent_trigger_push() self.angles_z = ReadCoord(); self.solid = SOLID_TRIGGER; - //self.draw = trigger_push_draw; + self.draw = trigger_draw_generic; + self.trigger_touch = trigger_push_touch; self.drawmask = MASK_ENGINE; self.move_time = time; - //self.touch = trigger_push_touch; trigger_push_findtarget(); } diff --git a/qcsrc/common/triggers/trigger/magicear.qc b/qcsrc/common/triggers/trigger/magicear.qc new file mode 100644 index 000000000..1034d5d68 --- /dev/null +++ b/qcsrc/common/triggers/trigger/magicear.qc @@ -0,0 +1,204 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/monoflop.qc b/qcsrc/common/triggers/trigger/monoflop.qc new file mode 100644 index 000000000..45ce761e0 --- /dev/null +++ b/qcsrc/common/triggers/trigger/monoflop.qc @@ -0,0 +1,49 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/multi.qc b/qcsrc/common/triggers/trigger/multi.qc new file mode 100644 index 000000000..ae73aaf7a --- /dev/null +++ b/qcsrc/common/triggers/trigger/multi.qc @@ -0,0 +1,204 @@ +// 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 diff --git a/qcsrc/common/triggers/trigger/multi.qh b/qcsrc/common/triggers/trigger/multi.qh new file mode 100644 index 000000000..f60ff2a84 --- /dev/null +++ b/qcsrc/common/triggers/trigger/multi.qh @@ -0,0 +1,4 @@ +#ifdef SVQC +void multi_trigger(); +void multi_reset(); +#endif diff --git a/qcsrc/common/triggers/trigger/multivibrator.qc b/qcsrc/common/triggers/trigger/multivibrator.qc new file mode 100644 index 000000000..02a258e87 --- /dev/null +++ b/qcsrc/common/triggers/trigger/multivibrator.qc @@ -0,0 +1,73 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/relay.qc b/qcsrc/common/triggers/trigger/relay.qc new file mode 100644 index 000000000..e037028ae --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay.qc @@ -0,0 +1,10 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/relay_activators.qc b/qcsrc/common/triggers/trigger/relay_activators.qc new file mode 100644 index 000000000..83c0103d0 --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay_activators.qc @@ -0,0 +1,45 @@ +#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 diff --git a/qcsrc/common/triggers/trigger/relay_teamcheck.qc b/qcsrc/common/triggers/trigger/relay_teamcheck.qc new file mode 100644 index 000000000..8a77cef05 --- /dev/null +++ b/qcsrc/common/triggers/trigger/relay_teamcheck.qc @@ -0,0 +1,35 @@ +#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 diff --git a/qcsrc/common/triggers/triggers.qc b/qcsrc/common/triggers/triggers.qc index a25b270f3..c9b5fdc6d 100644 --- a/qcsrc/common/triggers/triggers.qc +++ b/qcsrc/common/triggers/triggers.qc @@ -9,6 +9,17 @@ void DelayThink() 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 @@ -131,2025 +142,3 @@ void SUB_UseTargets() self = stemp; other = otemp; } - -#ifdef SVQC -//============================================================================= - -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() -{ - 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 -} - -.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; -} - -////////////////////////////////////////////////////////////// -// -// -// -//Trigger heal --a04191b92fbd93aa67214ef7e72d6d2e -// -////////////////////////////////////////////////////////////// - -.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); -} - - -////////////////////////////////////////////////////////////// -// -// -// -//End trigger_heal -// -////////////////////////////////////////////////////////////// - -.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; - } -} - -//============================================================================= - -// 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() -{ - 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); -} - -.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; -} - -// tZorks trigger impulse / gravity -.float radius; -.float falloff; -.float strength; -.float lastpushtime; - -// 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); -} - - - - -.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")); - } -} - - - -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; -} - -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 -} - -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; -} - -.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 diff --git a/qcsrc/common/triggers/triggers.qh b/qcsrc/common/triggers/triggers.qh index c5e0d466f..281c4c375 100644 --- a/qcsrc/common/triggers/triggers.qh +++ b/qcsrc/common/triggers/triggers.qh @@ -2,6 +2,9 @@ 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; .string bgmscript; @@ -11,9 +14,25 @@ const float SF_TRIGGER_RESET = 4; .float bgmscriptrelease; // used elsewhere (will fix) -void multi_touch(); +#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 active; +.string target; +.string targetname; +#define ACTIVE_NOT 0 +#define ACTIVE_ACTIVE 1 +#define ACTIVE_IDLE 2 +#define ACTIVE_BUSY 2 +#define ACTIVE_TOGGLE 3 +#endif diff --git a/qcsrc/server/item_key.qh b/qcsrc/server/item_key.qh index f0569291d..b077c0070 100644 --- a/qcsrc/server/item_key.qh +++ b/qcsrc/server/item_key.qh @@ -10,7 +10,6 @@ */ #ifdef SVQC string item_keys_names[ITEM_KEY_MAX]; -#endif /** * Use keys from p on l. @@ -22,4 +21,4 @@ float item_keys_usekey(entity l, entity p); * Returns a string with a comma separated list of key names, as specified in keylist. */ string item_keys_keylist(float keylist); - +#endif diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc index fe21d088c..b029850b8 100644 --- a/qcsrc/server/miscfunctions.qc +++ b/qcsrc/server/miscfunctions.qc @@ -1044,7 +1044,7 @@ float precache_sound_index (string s) = #19; #define SND_LARGEENTITY 8 #define SND_LARGESOUND 16 -float sound_allowed(float dest, entity e) +float sound_allowed(float destin, entity e) { // sounds from world may always pass for (;;) @@ -1059,7 +1059,7 @@ float sound_allowed(float dest, entity e) 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 @@ -1071,18 +1071,18 @@ float sound_allowed(float dest, entity e) } #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 destin, entity e, vector o, float chan, string samp, float vol, float attenu) { float entno, idx; - if (!sound_allowed(dest, e)) + if (!sound_allowed(destin, e)) return; entno = num_for_edict(e); @@ -1091,61 +1091,61 @@ void soundtoat(float dest, entity e, vector o, float chan, string samp, float vo 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 (idx >= 256) sflags |= SND_LARGESOUND; - WriteByte(dest, SVC_SOUND); - WriteByte(dest, sflags); + WriteByte(destin, SVC_SOUND); + WriteByte(destin, sflags); if (sflags & SND_VOLUME) - WriteByte(dest, vol); + WriteByte(destin, vol); if (sflags & SND_ATTENUATION) - WriteByte(dest, atten); + WriteByte(destin, attenu); if (sflags & SND_LARGEENTITY) { - WriteShort(dest, entno); - WriteByte(dest, chan); + WriteShort(destin, entno); + WriteByte(destin, chan); } else { - WriteShort(dest, entno * 8 + chan); + WriteShort(destin, entno * 8 + chan); } if (sflags & SND_LARGESOUND) - WriteShort(dest, idx); + WriteShort(destin, idx); else - WriteByte(dest, idx); + WriteByte(destin, idx); - WriteCoord(dest, o_x); - WriteCoord(dest, o_y); - WriteCoord(dest, o_z); + WriteCoord(destin, o_x); + WriteCoord(destin, o_y); + WriteCoord(destin, o_z); } -void soundto(float dest, entity e, float chan, string samp, float vol, float atten) +void soundto(float destin, entity e, float chan, string samp, float vol, float attenu) { vector o; - if (!sound_allowed(dest, e)) + if (!sound_allowed(destin, e)) return; o = e.origin + 0.5 * (e.mins + e.maxs); - soundtoat(dest, e, o, chan, samp, vol, atten); + soundtoat(destin, e, o, chan, samp, vol, attenu); } -void soundat(entity e, vector o, float chan, string samp, float vol, float atten) +void soundat(entity e, vector o, float chan, string samp, float vol, float attenu) { - soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, atten); + soundtoat(((chan & 8) ? MSG_ALL : MSG_BROADCAST), e, o, chan, samp, vol, attenu); } -void stopsoundto(float dest, entity e, float chan) +void stopsoundto(float destin, entity e, float chan) { float entno; - if (!sound_allowed(dest, e)) + if (!sound_allowed(destin, e)) return; entno = num_for_edict(e); @@ -1157,22 +1157,22 @@ void stopsoundto(float dest, entity e, float chan) sflags = SND_LARGEENTITY; if (idx >= 256) sflags |= SND_LARGESOUND; - WriteByte(dest, SVC_SOUND); - WriteByte(dest, sflags); - WriteShort(dest, entno); - WriteByte(dest, chan); + WriteByte(destin, SVC_SOUND); + WriteByte(destin, sflags); + WriteShort(destin, entno); + WriteByte(destin, chan); if (sflags & SND_LARGESOUND) - WriteShort(dest, idx); + WriteShort(destin, idx); else - WriteByte(dest, idx); - WriteCoord(dest, e.origin_x); - WriteCoord(dest, e.origin_y); - WriteCoord(dest, e.origin_z); + WriteByte(destin, idx); + WriteCoord(destin, e.origin_x); + WriteCoord(destin, e.origin_y); + WriteCoord(destin, e.origin_z); } else { - WriteByte(dest, SVC_STOPSOUND); - WriteShort(dest, entno * 8 + chan); + WriteByte(destin, SVC_STOPSOUND); + WriteShort(destin, entno * 8 + chan); } } void stopsound(entity e, float chan) @@ -1193,7 +1193,7 @@ void play2(entity e, string filename) // use this one if you might be causing spam (e.g. from touch functions that might get called more than once per frame) .float spamtime; -float spamsound(entity e, float chan, string samp, float vol, float atten) +float spamsound(entity e, float chan, string samp, float vol, float attenu) { if (!sound_allowed(MSG_BROADCAST, e)) return FALSE; @@ -1201,7 +1201,7 @@ float spamsound(entity e, float chan, string samp, float vol, float atten) if (time > e.spamtime) { e.spamtime = time; - sound(e, chan, samp, vol, atten); + sound(e, chan, samp, vol, attenu); return TRUE; } return FALSE; diff --git a/qcsrc/server/progs.src b/qcsrc/server/progs.src index 073982116..0750ad3e6 100644 --- a/qcsrc/server/progs.src +++ b/qcsrc/server/progs.src @@ -140,7 +140,8 @@ g_casings.qc mapvoting.qc -t_jumppads.qc +../common/triggers/include.qc + t_teleporters.qc sv_main.qc @@ -173,8 +174,6 @@ cl_player.qc cl_client.qc antilag.qc -../common/triggers/include.qc - //ctf.qc //domination.qc //mode_onslaught.qc diff --git a/qcsrc/server/t_halflife.qc b/qcsrc/server/t_halflife.qc index 1f45f9c82..f041dd396 100644 --- a/qcsrc/server/t_halflife.qc +++ b/qcsrc/server/t_halflife.qc @@ -1,7 +1,3 @@ -.float ladder_time; -.entity ladder_entity; - -#ifdef SVQC .float roomtype; .float radius; .float pitch; @@ -36,120 +32,3 @@ void spawnfunc_info_node() {} 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 diff --git a/qcsrc/server/t_plats.qc b/qcsrc/server/t_plats.qc deleted file mode 100644 index 592b2f8fb..000000000 --- a/qcsrc/server/t_plats.qc +++ /dev/null @@ -1,1650 +0,0 @@ -#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'); - } -} - - -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(); -} - -/*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 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 - // 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; - - 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 -- 2.39.2