mirror of
https://github.com/id-Software/quake-rerelease-qc.git
synced 2024-11-10 07:22:06 +00:00
556 lines
12 KiB
C++
556 lines
12 KiB
C++
|
/* Copyright (C) 1996-2022 id Software LLC
|
||
|
|
||
|
This program is free software; you can redistribute it and/or modify
|
||
|
it under the terms of the GNU General Public License as published by
|
||
|
the Free Software Foundation; either version 2 of the License, or
|
||
|
(at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with this program; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
See file, 'COPYING', for details.
|
||
|
*/
|
||
|
|
||
|
.float storednextthink;
|
||
|
.void() storedthink;
|
||
|
|
||
|
//============================================================================
|
||
|
// SCREENSHAKE
|
||
|
//============================================================================
|
||
|
float SILENT_SHAKE = 1;
|
||
|
|
||
|
void() T_Shake =
|
||
|
{
|
||
|
local entity t;
|
||
|
local float starttime;
|
||
|
local float hit;
|
||
|
|
||
|
starttime = self.storednextthink - self.wait;
|
||
|
|
||
|
// early out if intermission is running
|
||
|
if (intermission_running)
|
||
|
return;
|
||
|
|
||
|
// Completed, cleanup!
|
||
|
if (time > self.storednextthink)
|
||
|
{
|
||
|
if (!(self.spawnflags & SILENT_SHAKE))
|
||
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
||
|
|
||
|
t = find( world, classname, "player" );
|
||
|
while (t)
|
||
|
{
|
||
|
t.v_angle_z = 0;
|
||
|
t = find( t, classname, "player" );
|
||
|
}
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Ramp up?
|
||
|
if (time < self.delay)
|
||
|
hit = (time - starttime) / (self.wait/3);
|
||
|
|
||
|
else
|
||
|
hit = 1;
|
||
|
|
||
|
// dprint("intensity: ");
|
||
|
// dprint(ftos(hit));
|
||
|
// dprint("\n");
|
||
|
|
||
|
hit = self.dmg * hit;
|
||
|
|
||
|
// for each player
|
||
|
t = find( world, classname, "player" );
|
||
|
while (t)
|
||
|
{
|
||
|
// do the shake!
|
||
|
t.punchangle_x = random() * hit;
|
||
|
t.punchangle_y = crandom() * hit;
|
||
|
t.punchangle_z = random() * hit;
|
||
|
|
||
|
t = find( t, classname, "player" );
|
||
|
}
|
||
|
|
||
|
self.nextthink = time + 0.05;
|
||
|
};
|
||
|
|
||
|
void() screenshake_use =
|
||
|
{
|
||
|
self.storednextthink = time + self.wait; // when to end
|
||
|
self.delay = time + (self.wait / 3); // how long to ramp up
|
||
|
|
||
|
if (!(self.spawnflags & SILENT_SHAKE))
|
||
|
sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
|
||
|
self.think = T_Shake;
|
||
|
self.nextthink = time + 0.05;
|
||
|
};
|
||
|
|
||
|
/*QUAKED trigger_screenshake
|
||
|
|
||
|
wait is duration of the shake
|
||
|
dmg is the intensity of the shake
|
||
|
*/
|
||
|
void() trigger_screenshake =
|
||
|
{
|
||
|
INHIBIT_COOP
|
||
|
if (RemovedOutsideCoop()) return;
|
||
|
|
||
|
// wait is sustain time
|
||
|
// dmg is intensity
|
||
|
if (!(self.spawnflags & SILENT_SHAKE))
|
||
|
{
|
||
|
self.noise = "misc/quake.wav";
|
||
|
self.noise1 = "misc/quakeend.wav";
|
||
|
precache_sound(self.noise);
|
||
|
precache_sound(self.noise1);
|
||
|
}
|
||
|
|
||
|
if (!self.wait) // no duration!
|
||
|
self.wait = 2;
|
||
|
|
||
|
if (!self.dmg) // no intensity!
|
||
|
self.dmg = 3;
|
||
|
|
||
|
self.use = screenshake_use;
|
||
|
}
|
||
|
|
||
|
//============================================================================
|
||
|
// SOUND TRIGGER
|
||
|
//============================================================================
|
||
|
|
||
|
void() trigger_sound_play =
|
||
|
{
|
||
|
if (!self.noise)
|
||
|
return;
|
||
|
|
||
|
sound(self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
||
|
};
|
||
|
/*QUAKED trigger_sound
|
||
|
*/
|
||
|
void() trigger_sound =
|
||
|
{
|
||
|
if (!self.noise)
|
||
|
remove(self);
|
||
|
|
||
|
precache_sound (self.noise);
|
||
|
self.use = trigger_sound_play;
|
||
|
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
// TRIGGER LIGHTNING
|
||
|
//============================================================================
|
||
|
|
||
|
const float LIGHTNING_RANDOM_TARGET = 1;
|
||
|
const float LIGHTNING_TRACE_BACKWARDS = 2;
|
||
|
const float LIGHTNING_NO_ACTIVATE_TARGET = 4;
|
||
|
const float LIGHTNING_TRIGGER_TARGETS_ONCE = 8;
|
||
|
|
||
|
void() play_lightning =
|
||
|
{
|
||
|
if (!self.target)
|
||
|
{
|
||
|
dprint("lightning has no target!\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
float rt = 0;
|
||
|
if(self.spawnflags & LIGHTNING_RANDOM_TARGET)
|
||
|
{
|
||
|
float cnt = SUB_CountTargets(self);
|
||
|
if(cnt == 0) return;
|
||
|
rt = floor(cnt * random());
|
||
|
}
|
||
|
float triggertwice = (self.spawnflags & LIGHTNING_TRIGGER_TARGETS_ONCE) ? FALSE : TRUE; //Note boolean reversal
|
||
|
float activatetargets = (self.spawnflags & LIGHTNING_NO_ACTIVATE_TARGET) ? FALSE : TRUE; //Note boolean reversal
|
||
|
|
||
|
local entity t, oself;
|
||
|
|
||
|
float i = 0;
|
||
|
t = find(world, targetname, self.target);
|
||
|
while(t)
|
||
|
{
|
||
|
if(self.spawnflags & LIGHTNING_RANDOM_TARGET)
|
||
|
{
|
||
|
if (i < rt){
|
||
|
t = find(t, targetname, self.target);
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
if (i > rt) return;
|
||
|
i++;
|
||
|
}
|
||
|
|
||
|
vector endpos, startpos;
|
||
|
if(self.spawnflags & LIGHTNING_TRACE_BACKWARDS)
|
||
|
{
|
||
|
traceline(t.origin, self.origin, TRUE, self);
|
||
|
endpos = trace_endpos;
|
||
|
startpos = t.origin;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
traceline(self.origin, t.origin, TRUE, self);
|
||
|
endpos = trace_endpos;
|
||
|
startpos = self.origin;
|
||
|
}
|
||
|
|
||
|
|
||
|
sound (t, CHAN_AUTO, self.noise, self.volume, ATTN_NORM); //Changed to CHAN_AUTO to allow more overlapping sounds
|
||
|
|
||
|
// create the temp lightning entity
|
||
|
WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
|
||
|
WriteByte (MSG_BROADCAST, self.style);
|
||
|
WriteEntity (MSG_BROADCAST, t);
|
||
|
WriteCoord (MSG_BROADCAST, startpos_x);
|
||
|
WriteCoord (MSG_BROADCAST, startpos_y);
|
||
|
WriteCoord (MSG_BROADCAST, startpos_z);
|
||
|
WriteCoord (MSG_BROADCAST, endpos_x);
|
||
|
WriteCoord (MSG_BROADCAST, endpos_y);
|
||
|
WriteCoord (MSG_BROADCAST, endpos_z);
|
||
|
|
||
|
if(self.dmg)
|
||
|
{
|
||
|
LightningDamage (startpos, endpos, self, self.dmg);
|
||
|
}
|
||
|
|
||
|
//If activatetargets and t has a target, activate it.
|
||
|
if(activatetargets && t.target)
|
||
|
{
|
||
|
oself = self;
|
||
|
self = t;
|
||
|
SUB_UseTargets();
|
||
|
if(triggertwice)
|
||
|
{
|
||
|
float odelay = self.delay;
|
||
|
self.delay = odelay + 0.2;
|
||
|
SUB_UseTargets(); //Quickly turn on and off
|
||
|
self.delay = odelay;
|
||
|
}
|
||
|
self = oself;
|
||
|
}
|
||
|
|
||
|
t = find(t, targetname, self.target);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void() trigger_lightning =
|
||
|
{
|
||
|
if(!self.noise) self.noise = "misc/power.wav";
|
||
|
precache_sound(self.noise);
|
||
|
self.use = play_lightning;
|
||
|
|
||
|
switch(self.style)
|
||
|
{
|
||
|
default:
|
||
|
case 0:
|
||
|
self.style = TE_LIGHTNING3;
|
||
|
break;
|
||
|
case 1:
|
||
|
self.style = TE_LIGHTNING1;
|
||
|
break;
|
||
|
case 2:
|
||
|
self.style = TE_LIGHTNING2;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(!self.volume) self.volume = 1;
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
// TRIGGER FADE
|
||
|
//============================================================================
|
||
|
/* Checks all targets to see if they are dead. If they are, then it starts a fade
|
||
|
*/
|
||
|
void() SUB_FadeTargets =
|
||
|
{
|
||
|
local entity t;
|
||
|
local float count; // how many entities are being faded
|
||
|
count = 0;
|
||
|
|
||
|
if (self.target)
|
||
|
{
|
||
|
if(!self.state)
|
||
|
{
|
||
|
t = world;
|
||
|
//Initialize all the targets with alpha = 1.0, so the fade out works.
|
||
|
t = find (t, targetname, self.target);
|
||
|
while (t)
|
||
|
{
|
||
|
t.alpha = 1.0;
|
||
|
t = find (t, targetname, self.target);
|
||
|
}
|
||
|
self.state = 1; // Don't repeat this initialization after the first think.
|
||
|
}
|
||
|
|
||
|
t = world;
|
||
|
t = find (t, targetname, self.target);
|
||
|
while (t)
|
||
|
{
|
||
|
if (t.health <= 0)
|
||
|
{
|
||
|
if (t.alpha > 0)
|
||
|
{
|
||
|
t.alpha = t.alpha - (frametime/self.delay);
|
||
|
count = count + 1;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dprint("fade complete, removing ");
|
||
|
dprint(t.classname);
|
||
|
dprint("\n");
|
||
|
remove(t);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
dprint ("target is still alive\n");
|
||
|
}
|
||
|
t = find (t, targetname, self.target);
|
||
|
}
|
||
|
}
|
||
|
if (count > 0)
|
||
|
{
|
||
|
self.nextthink = time;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
remove(self);
|
||
|
dprint("removed fade manager\n");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
void() spawn_fade_manager =
|
||
|
{
|
||
|
local entity fademanager;
|
||
|
fademanager = spawn();
|
||
|
dprint("spawn fade manager\n");
|
||
|
fademanager.think = SUB_FadeTargets;
|
||
|
fademanager.nextthink = time;
|
||
|
fademanager.target = self.target;
|
||
|
fademanager.delay = self.delay; // use delay to determine fade time, default to 1
|
||
|
}
|
||
|
|
||
|
void() trigger_fade =
|
||
|
{
|
||
|
INHIBIT_COOP
|
||
|
if (RemovedOutsideCoop()) return;
|
||
|
|
||
|
self.use = spawn_fade_manager;
|
||
|
|
||
|
if (!self.delay)
|
||
|
self.delay = 1; // delay determines fade time
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
// TRIGGER FREEZE
|
||
|
//============================================================================
|
||
|
// Yoder Jan 14 2021
|
||
|
|
||
|
|
||
|
void() SUB_Freeze =
|
||
|
{
|
||
|
self.storednextthink = self.nextthink;
|
||
|
self.storedthink = self.think;
|
||
|
self.nextthink = time;
|
||
|
self.think = SUB_Null;
|
||
|
self.is_frozen = 1;
|
||
|
};
|
||
|
|
||
|
void() SUB_Unfreeze =
|
||
|
{
|
||
|
self.nextthink = self.storednextthink;
|
||
|
self.think = self.storedthink;
|
||
|
self.storednextthink = -1;
|
||
|
self.is_frozen = 0;
|
||
|
};
|
||
|
|
||
|
void() SUB_FreezeTargets =
|
||
|
{
|
||
|
local entity t;
|
||
|
|
||
|
if (self.target)
|
||
|
{
|
||
|
t = world;
|
||
|
do
|
||
|
{
|
||
|
t = find (t, targetname, self.target);
|
||
|
if (!t)
|
||
|
return;
|
||
|
|
||
|
if (!(t.is_frozen)) // check if already frozen
|
||
|
{
|
||
|
// freeze
|
||
|
t.storednextthink = t.nextthink;
|
||
|
t.storedthink = t.think;
|
||
|
t.nextthink = time;
|
||
|
t.think = SUB_Null;
|
||
|
t.is_frozen = 1;
|
||
|
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// unfreeze
|
||
|
t.nextthink = t.storednextthink;
|
||
|
t.think = t.storedthink;
|
||
|
t.storednextthink = -1;
|
||
|
t.is_frozen = 0;
|
||
|
}
|
||
|
} while (1);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/* Define the entity, trigger_freeze
|
||
|
Works like a trigger relay
|
||
|
*/
|
||
|
void() trigger_freeze =
|
||
|
{
|
||
|
INHIBIT_COOP
|
||
|
if (RemovedOutsideCoop()) return;
|
||
|
|
||
|
self.use = SUB_FreezeTargets;
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
// particle_embers
|
||
|
//============================================================================
|
||
|
void() particle_embers_think =
|
||
|
{
|
||
|
local vector pos;
|
||
|
local vector speed;
|
||
|
speed = '0 0 2' * random() + '0 0 2';
|
||
|
speed_x = crandom() * self.velocity_x;
|
||
|
speed_y = crandom() * self.velocity_y;
|
||
|
speed_z = speed_z * self.velocity_z;
|
||
|
pos = self.origin;
|
||
|
pos_x = pos_x + self.size_x * crandom();
|
||
|
pos_y = pos_y + self.size_y * crandom();
|
||
|
//pos = crandom() * self.size + self.origin;
|
||
|
particle(pos, speed, 234, 2);
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
}
|
||
|
|
||
|
void() particle_embers =
|
||
|
{
|
||
|
// size determines the box the particles can spawn within
|
||
|
if (!self.size)
|
||
|
self.size = '128 128 0';
|
||
|
// delay is random time added per loop
|
||
|
if (!self.delay)
|
||
|
self.delay = 0.1;
|
||
|
|
||
|
// wait is time always added per loop
|
||
|
if (!self.wait)
|
||
|
self.wait = 0.05;
|
||
|
|
||
|
// velocity is used as a scalar on speed
|
||
|
if (!self.velocity)
|
||
|
self.velocity = '1 1 1';
|
||
|
|
||
|
self.think = particle_embers_think;
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
};
|
||
|
|
||
|
void() particle_embers_tall =
|
||
|
{
|
||
|
if (!self.size)
|
||
|
self.size = '40 40 0';
|
||
|
if (!self.delay)
|
||
|
self.delay = 0.1;
|
||
|
if (!self.wait)
|
||
|
self.wait = 0.05;
|
||
|
if (!self.velocity) // scalar for speed
|
||
|
self.velocity = '1 1 2';
|
||
|
self.think = particle_embers_think;
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
};
|
||
|
|
||
|
//============================================================================
|
||
|
// particle_tele
|
||
|
//============================================================================
|
||
|
void() particle_tele_think =
|
||
|
{
|
||
|
local vector pos; // where to spawn
|
||
|
local float dist; // scalar from org, used for speed too
|
||
|
local vector rando;
|
||
|
|
||
|
rando_x = crandom() * 10;
|
||
|
rando_y = crandom() * 10;
|
||
|
rando_z = crandom() * 5;
|
||
|
rando = normalize(rando);
|
||
|
|
||
|
dist = 64;
|
||
|
|
||
|
pos = self.origin + (rando * dist);
|
||
|
|
||
|
// spawn particle
|
||
|
particle(pos, rando * dist * -.125, 3, 3);
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
}
|
||
|
|
||
|
|
||
|
void() particle_tele =
|
||
|
{
|
||
|
/*
|
||
|
1. get origin
|
||
|
2. get a random vector, then normalize it. Save it for later
|
||
|
3. scale a copy of the vector by distance
|
||
|
4. spawn the particle at vector + origin
|
||
|
5. set the particle's velocity to the old normalized vector * -scalar.
|
||
|
*/
|
||
|
// size determines the box the particles can spawn within
|
||
|
if (!self.size)
|
||
|
self.size = '128 128 0';
|
||
|
// delay is random time added per loop
|
||
|
if (!self.delay)
|
||
|
self.delay = 0.1;
|
||
|
|
||
|
// wait is time always added per loop
|
||
|
if (!self.wait)
|
||
|
self.wait = 0;
|
||
|
|
||
|
self.think = particle_tele_think;
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
}
|
||
|
|
||
|
void() particle_tele_fountain_think =
|
||
|
{
|
||
|
local vector dir;
|
||
|
dir_x = crandom() * self.velocity_x;
|
||
|
dir_y = crandom() * self.velocity_y;
|
||
|
dir_z = self.velocity_z;
|
||
|
particle(self.origin, dir, 13, 2);
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
};
|
||
|
void() particle_tele_fountain_use =
|
||
|
{
|
||
|
self.think = particle_tele_fountain_think;
|
||
|
self.nextthink = time + 0.1;
|
||
|
};
|
||
|
void() particle_tele_fountain =
|
||
|
{
|
||
|
if (!self.delay)
|
||
|
self.delay = 0.1;
|
||
|
|
||
|
if (!self.wait)
|
||
|
self.wait = 0.05;
|
||
|
|
||
|
if (!self.velocity)
|
||
|
self.velocity = '1 1 6';
|
||
|
|
||
|
if (self.spawnflags & START_OFF)
|
||
|
self.use = particle_tele_fountain_use;
|
||
|
else
|
||
|
{
|
||
|
self.think = particle_tele_fountain_think;
|
||
|
self.nextthink = time + self.wait + self.delay * random();
|
||
|
}
|
||
|
};
|