1101 lines
31 KiB
Plaintext
1101 lines
31 KiB
Plaintext
/*======================================================================
|
|
Rotate QuickC program by Jim Dose' 10/17/96
|
|
Copyright (c)1996 Hipnotic Interactive, Inc.
|
|
|
|
* All the functions and defs are self contained in this file
|
|
* Should make it easier to distribute as no other qc file required
|
|
* Collapsed and condensed this QC file as much as possible
|
|
* Renamed all the constants so they don't conflict
|
|
* It was a cool idea for its time (1996) but a horrible solution nowadays
|
|
It would be much better to do this in the engine (rotation collision)
|
|
Unfortunately most popular quake clients (QS/Fitz) refuse to add it
|
|
* Added target2 to func_rotate_door to allow alternative start positions
|
|
|
|
------------------------------------------------------------------------
|
|
/*QUAKED info_rotate (0 0.5 0) (-4 -4 -4) (4 4 4)
|
|
Used as the point of rotation for rotatable objects.
|
|
|
|
/*QUAKED func_rotate_entity (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE START_ON
|
|
Creates an entity that continually rotates. Can be toggled on and
|
|
off if targeted.
|
|
|
|
/*QUAKED path_rotate (0.5 0.3 0) (-8 -8 -8) (8 8 8) ROTATION ANGLES STOP NO_ROTATE DAMAGE MOVETIME SET_DAMAGE
|
|
Path for rotate_train.
|
|
|
|
/*QUAKED func_rotate_train (0 .5 .8) (-8 -8 -8) (8 8 8) x
|
|
Trains are moving platforms that players can ride.
|
|
If the train is the target of a button or trigger, it will not begin
|
|
moving until activated.
|
|
The func_rotate_train entity is the center of rotation of all objects
|
|
targeted by it.
|
|
|
|
/*QUAKED func_movewall (0 .5 .8) ? VISIBLE TOUCH NONBLOCKING
|
|
Used to emulate collision on rotating objects.
|
|
|
|
/*QUAKED rotate_object (0 .5 .8) ?
|
|
This defines an object to be rotated.
|
|
Used as the target of func_rotate_door.
|
|
|
|
/*QUAKED func_rotate_door (0 .5 .8) (-8 -8 -8) (8 8 8) STAYOPEN
|
|
Creates a door that rotates between two positions around a point of
|
|
rotation each time it's triggered.
|
|
|
|
------------------------------------------------------------------------
|
|
Most modern compilers will expect the rotate_object to always
|
|
target a info_rotate (entity) so that it can sort out texture offset
|
|
All func_rotating objects DO NOT start at 0,0,0 like other bmodels
|
|
|
|
------------------------------------------------------------------------
|
|
Explaination of func_rotating objects (by Mechtech)
|
|
|
|
func_rotating_train (controller for rotation)
|
|
targetname -> trigger to start/activate train
|
|
target -> rotate_object (bmodel) AND rotate_movewall (bmodel)
|
|
path-> path_rotate (entity chain)
|
|
|
|
rotate_object (train brushwork)
|
|
target -> info_rotate (entity, center point for rotation)
|
|
|
|
rotate_movewall (pretends to do collision)
|
|
|
|
path_rotate (entity chain for train to follow)
|
|
targetname -> Used for reference
|
|
target -> next entity in chain to follow
|
|
|
|
------------------------------------------------------------------------
|
|
Func rotating doors cannot be moved on start so extra key added
|
|
|
|
func_rotate_door (controller for rotation)
|
|
targetname -> trigger to start/activate two state door
|
|
target -> rotate_object (bmodel) AND rotate_movewall (bmodel)
|
|
target2 -> info_rotate (entity) starting location for bmodel
|
|
|
|
======================================================================*/
|
|
|
|
// from hipdefs.qc in hipnotic/ritual source qc files
|
|
.vector neworigin;
|
|
.vector rotate;
|
|
.float endtime;
|
|
.float rotate_type;
|
|
.string path;
|
|
.string group;
|
|
.string event;
|
|
.float duration;
|
|
.vector dest; // Could not find this in the source, added
|
|
|
|
//----------------------------------------------------------------------
|
|
// from hipsubs.qc in hipnotic/ritual source qc files
|
|
vector ( vector ang ) SUB_NormalizeAngles =
|
|
{
|
|
while( ang_x > 360 ) ang_x = ang_x - 360;
|
|
while( ang_x < 0 ) ang_x = ang_x + 360;
|
|
while( ang_y > 360 ) ang_y = ang_y - 360;
|
|
while( ang_y < 0 ) ang_y = ang_y + 360;
|
|
while( ang_z > 360 ) ang_z = ang_z - 360;
|
|
while( ang_z < 0 ) ang_z = ang_z + 360;
|
|
return ang;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// from triggers.qc in hipnotic/ritual source qc files
|
|
void( entity ent, float amount ) hurt_setdamage =
|
|
{
|
|
ent.dmg = amount;
|
|
if ( !amount ) ent.solid = SOLID_NOT;
|
|
else ent.solid = SOLID_TRIGGER;
|
|
ent.nextthink = -1;
|
|
};
|
|
|
|
//======================================================================
|
|
// Renamed all the constants so they don't conflict with anything else
|
|
// They were mostly setup as single words (which is not a good idea)
|
|
//======================================================================
|
|
float RSTATE_ACTIVE = 0;
|
|
float RSTATE_INACTIVE = 1;
|
|
float RSTATE_SPEEDINGUP = 2;
|
|
float RSTATE_SLOWINGDOWN = 3;
|
|
|
|
float RSTATE_CLOSED = 4;
|
|
float RSTATE_OPEN = 5;
|
|
float RSTATE_OPENING = 6;
|
|
float RSTATE_CLOSING = 7;
|
|
|
|
float RSTATE_WAIT = 0;
|
|
float RSTATE_MOVE = 1;
|
|
float RSTATE_STOP = 2;
|
|
float RSTATE_FIND = 3;
|
|
float RSTATE_NEXT = 4;
|
|
|
|
float OBJECT_ROTATE = 0;
|
|
float OBJECT_MOVEWALL = 1;
|
|
float OBJECT_SETORIGIN = 2;
|
|
|
|
float ROTENTITY_TOGGLE = 1;
|
|
float ROTENTITY_START_ON = 2;
|
|
|
|
float TRAIN_ROTATION = 1;
|
|
float TRAIN_ANGLES = 2;
|
|
float TRAIN_STOP = 4;
|
|
float TRAIN_NO_ROTATE = 8;
|
|
float TRAIN_DAMAGE = 16;
|
|
float TRAIN_MOVETIME = 32;
|
|
float TRAIN_SET_DAMAGE = 64;
|
|
|
|
float MOVEWALL_VISIBLE = 1;
|
|
float MOVEWALL_TOUCH = 2;
|
|
float MOVEWALL_NONBLOCKING = 4;
|
|
|
|
float DOOR_STAYOPEN = 1;
|
|
|
|
//======================================================================
|
|
/*QUAKED info_rotate (0 0.5 0) (-4 -4 -4) (4 4 4) x
|
|
center point for rotatable objects
|
|
-------- KEYS --------
|
|
-------- SPAWNFLAGS --------
|
|
-------- NOTES --------
|
|
center point for rotatable objects
|
|
Removes self 5s after spawning
|
|
*/
|
|
//======================================================================
|
|
|
|
void() info_rotate =
|
|
{
|
|
// remove self after a little while, to make sure that entities that
|
|
// have targeted it have had a chance to spawn
|
|
// Not sure why this entity is removed, maybe there was entity
|
|
// limits in the old days, but that is long gone!
|
|
// Leaving entity in map, no advantage to removal anymore
|
|
//self.nextthink = time + 5;
|
|
//self.think = SUB_Remove;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() RotateTargets =
|
|
{
|
|
local entity ent;
|
|
local vector vx;
|
|
local vector vy;
|
|
local vector vz;
|
|
local vector org;
|
|
|
|
makevectors (self.angles);
|
|
|
|
ent = find( world, targetname, self.target);
|
|
while( ent ) {
|
|
if ( ent.rotate_type == OBJECT_SETORIGIN ) {
|
|
org = ent.oldorigin;
|
|
vx = ( v_forward * org_x );
|
|
vy = ( v_right * org_y );
|
|
vy = vy * -1;
|
|
vz = ( v_up * org_z );
|
|
ent.neworigin = vx + vy + vz;
|
|
setorigin( ent, ent.neworigin + self.origin );
|
|
}
|
|
else if ( ent.rotate_type == OBJECT_ROTATE ) {
|
|
ent.angles = self.angles;
|
|
org = ent.oldorigin;
|
|
vx = ( v_forward * org_x );
|
|
vy = ( v_right * org_y );
|
|
vy = vy * -1;
|
|
vz = ( v_up * org_z );
|
|
ent.neworigin = vx + vy + vz;
|
|
setorigin( ent, ent.neworigin + self.origin );
|
|
}
|
|
else {
|
|
org = ent.oldorigin;
|
|
vx = ( v_forward * org_x );
|
|
vy = ( v_right * org_y );
|
|
vy = vy * -1;
|
|
vz = ( v_up * org_z );
|
|
ent.neworigin = vx + vy + vz;
|
|
ent.neworigin = self.origin - self.oldorigin + (ent.neworigin - ent.oldorigin);
|
|
ent.velocity = (ent.neworigin-ent.origin)*25;
|
|
}
|
|
ent = find( ent, targetname, self.target);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() RotateTargetsFinal =
|
|
{
|
|
local entity ent;
|
|
|
|
ent = find( world, targetname, self.target);
|
|
while( ent ) {
|
|
ent.velocity = '0 0 0';
|
|
if ( ent.rotate_type == OBJECT_ROTATE ) ent.angles = self.angles;
|
|
ent = find( ent, targetname, self.target);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() SetTargetOrigin =
|
|
{
|
|
local entity ent;
|
|
|
|
ent = find( world, targetname, self.target);
|
|
while( ent ) {
|
|
if ( ent.rotate_type == OBJECT_MOVEWALL ) {
|
|
setorigin( ent, self.origin - self.oldorigin +
|
|
(ent.neworigin - ent.oldorigin) );
|
|
}
|
|
else {
|
|
setorigin( ent, ent.neworigin + self.origin );
|
|
}
|
|
ent = find( ent, targetname, self.target);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() LinkRotateTargets =
|
|
{
|
|
local entity ent;
|
|
local vector tempvec;
|
|
|
|
self.oldorigin = self.origin;
|
|
ent = find( world, targetname, self.target);
|
|
while( ent ) {
|
|
if ( ent.classname == "rotate_object" ) {
|
|
ent.rotate_type = OBJECT_ROTATE;
|
|
ent.oldorigin = ent.origin - self.oldorigin;
|
|
ent.neworigin = ent.origin - self.oldorigin;
|
|
ent.owner = self;
|
|
}
|
|
else if ( ent.classname == "func_movewall" ) {
|
|
ent.rotate_type = OBJECT_MOVEWALL;
|
|
tempvec = ( ent.absmin + ent.absmax ) * 0.5;
|
|
ent.oldorigin = tempvec - self.oldorigin;
|
|
ent.neworigin = ent.oldorigin;
|
|
ent.owner = self;
|
|
}
|
|
else {
|
|
ent.rotate_type = OBJECT_SETORIGIN;
|
|
ent.oldorigin = ent.origin - self.oldorigin;
|
|
ent.neworigin = ent.origin - self.oldorigin;
|
|
}
|
|
ent = find (ent, targetname, self.target);
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void( float amount ) SetDamageOnTargets =
|
|
{
|
|
local entity ent;
|
|
|
|
ent = find( world, targetname, self.target);
|
|
while( ent ) {
|
|
if ( ent.classname == "trigger_hurt" ) hurt_setdamage( ent, amount );
|
|
else if ( ent.classname == "func_movewall" ) ent.dmg = amount;
|
|
ent = find( ent, targetname, self.target);
|
|
}
|
|
};
|
|
|
|
|
|
//----------------------------------------------------------------------
|
|
// Simple continual rotatation
|
|
//----------------------------------------------------------------------
|
|
void() rotate_entity_think =
|
|
{
|
|
local float t;
|
|
|
|
t = time - self.ltime;
|
|
self.ltime = time;
|
|
|
|
if ( self.state == RSTATE_SPEEDINGUP ) {
|
|
self.count = self.count + self.cnt * t;
|
|
if ( self.count > 1 ) self.count = 1;
|
|
|
|
// get rate of rotation
|
|
t = t * self.count;
|
|
}
|
|
else if ( self.state == RSTATE_SLOWINGDOWN ) {
|
|
self.count = self.count - self.cnt * t;
|
|
if ( self.count < 0 ) {
|
|
RotateTargetsFinal();
|
|
self.state = RSTATE_INACTIVE;
|
|
self.think = SUB_Null;
|
|
return;
|
|
}
|
|
|
|
// get rate of rotation
|
|
t = t * self.count;
|
|
}
|
|
|
|
self.angles = self.angles + ( self.rotate * t );
|
|
self.angles = SUB_NormalizeAngles( self.angles );
|
|
RotateTargets();
|
|
self.nextthink = time + 0.02;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_entity_use =
|
|
{
|
|
// change to alternate textures
|
|
self.frame = 1 - self.frame;
|
|
|
|
if ( self.state == RSTATE_ACTIVE ) {
|
|
if ( self.spawnflags & ROTENTITY_TOGGLE ) {
|
|
if ( self.speed ) {
|
|
self.count = 1;
|
|
self.state = RSTATE_SLOWINGDOWN;
|
|
}
|
|
else {
|
|
self.state = RSTATE_INACTIVE;
|
|
self.think = SUB_Null;
|
|
}
|
|
}
|
|
}
|
|
else if ( self.state == RSTATE_INACTIVE ) {
|
|
self.think = rotate_entity_think;
|
|
self.nextthink = time + 0.02;
|
|
self.ltime = time;
|
|
if ( self.speed ) {
|
|
self.count = 0;
|
|
self.state = RSTATE_SPEEDINGUP;
|
|
}
|
|
else {
|
|
self.state = RSTATE_ACTIVE;
|
|
}
|
|
}
|
|
else if ( self.state == RSTATE_SPEEDINGUP ) {
|
|
if ( self.spawnflags & ROTENTITY_TOGGLE ) {
|
|
self.state = RSTATE_SLOWINGDOWN;
|
|
}
|
|
}
|
|
else {
|
|
self.state = RSTATE_SPEEDINGUP;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_entity_firstthink =
|
|
{
|
|
LinkRotateTargets();
|
|
if ( self.spawnflags & ROTENTITY_START_ON ) {
|
|
self.state = RSTATE_ACTIVE;
|
|
self.think = rotate_entity_think;
|
|
self.nextthink = time + 0.02;
|
|
self.ltime = time;
|
|
}
|
|
else {
|
|
self.state = RSTATE_INACTIVE;
|
|
self.think = SUB_Null;
|
|
}
|
|
self.use = rotate_entity_use;
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED func_rotate_entity (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE STARTON
|
|
Basic rotating object
|
|
-------- KEYS --------
|
|
target : points to center of rotation entity
|
|
rotate : The rate of rotation in degrees
|
|
speed : time taken to go from zero to full speed and vice-versa
|
|
-------- SPAWNFLAGS --------
|
|
TOGGLE : allows the rotation to be toggled on/off
|
|
STARTON : whether the entity is spinning when spawned.
|
|
If TOGGLE is 0, entity can be turned on, but not off.
|
|
-------- NOTES --------
|
|
Basic rotating object
|
|
*/
|
|
//======================================================================
|
|
void() func_rotate_entity =
|
|
{
|
|
self.solid = SOLID_NOT;
|
|
self.movetype = MOVETYPE_NONE;
|
|
|
|
setmodel (self, self.model);
|
|
setsize( self, self.mins, self.maxs );
|
|
|
|
if ( self.speed != 0 ) self.cnt = 1 / self.speed;
|
|
|
|
self.think = rotate_entity_firstthink;
|
|
self.nextthink = time + 0.1;
|
|
self.ltime = time;
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED path_rotate (0.5 0.3 0) (-8 -8 -8) (8 8 8) ROTATION ANGLES STOP NO_ROTATE DAMAGE MOVETIME SET_DAMAGE
|
|
Corner Path for rotate_train
|
|
-------- KEYS --------
|
|
event : is a target to trigger when train arrives at path_rotate
|
|
noise : contains the name of the sound to play when train stops
|
|
noise1 : contains the name of the sound to play when train moves
|
|
speed : set speed to be the new speed of the train after it reaches
|
|
the path change. If speed is -1, the train will warp directly to the next
|
|
path change after the specified wait time. If MOVETIME is set on the
|
|
path_rotate, the train to interprets "speed" as the length of time to
|
|
take moving from one corner to another.
|
|
-------- SPAWNFLAGS --------
|
|
ROTATION : Rotate train at rate specified by 'rotate', use '0 0 0' to stop
|
|
ANGLES : Rotate to the angles specified by 'angles'
|
|
while traveling to this path_rotate. Use values < 0 or > 360 to
|
|
guarantee that it turns in a certain direction. Having this flag
|
|
set automatically clears any rotation
|
|
STOP : stop train and wait to be retriggered
|
|
NO_ROTATE : stop train rotating when waiting to be triggered
|
|
DAMAGE : Cause damage based on 'dmg'
|
|
MOVETIME : Interpret 'speed' as the length of time to take moving
|
|
SET_DAMAGE: Set all targets damage to 'dmg'
|
|
-------- NOTES --------
|
|
Corner Path for rotate_train
|
|
*/
|
|
//======================================================================
|
|
void() rotate_train;
|
|
void() rotate_train_next;
|
|
void() rotate_train_find;
|
|
|
|
//----------------------------------------------------------------------
|
|
void() path_rotate =
|
|
{
|
|
if ( self.noise ) precache_sound( self.noise );
|
|
if ( self.noise1 ) precache_sound( self.noise1 );
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_think =
|
|
{
|
|
local float t;
|
|
local float timeelapsed;
|
|
|
|
t = time - self.ltime;
|
|
self.ltime = time;
|
|
|
|
if ( ( self.endtime ) && ( time >= self.endtime ) ) {
|
|
self.endtime = 0;
|
|
if ( self.state == RSTATE_MOVE ) {
|
|
setorigin(self, self.finaldest);
|
|
self.velocity = '0 0 0';
|
|
}
|
|
|
|
if (self.think1) self.think1();
|
|
}
|
|
else {
|
|
timeelapsed = (time - self.cnt) * self.duration;
|
|
if ( timeelapsed > 1 ) timeelapsed = 1;
|
|
setorigin( self, self.dest1 + ( self.dest2 * timeelapsed ) );
|
|
}
|
|
|
|
self.angles = self.angles + ( self.rotate * t );
|
|
self.angles = SUB_NormalizeAngles( self.angles );
|
|
RotateTargets();
|
|
|
|
self.nextthink = time + 0.02;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_use =
|
|
{
|
|
if (self.think1 != rotate_train_find) {
|
|
if ( self.velocity != '0 0 0' ) return; // already activated
|
|
if ( self.think1 ) self.think1();
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_wait =
|
|
{
|
|
self.state = RSTATE_WAIT;
|
|
|
|
if ( self.goalentity.noise ) {
|
|
sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM);
|
|
}
|
|
else sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
|
|
|
if ( self.goalentity.spawnflags & TRAIN_ANGLES ) {
|
|
self.rotate = '0 0 0';
|
|
self.angles = self.finalangle;
|
|
}
|
|
|
|
if ( self.goalentity.spawnflags & TRAIN_NO_ROTATE ) self.rotate = '0 0 0';
|
|
|
|
self.endtime = self.ltime + self.goalentity.wait;
|
|
self.think1 = rotate_train_next;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_stop =
|
|
{
|
|
self.state = RSTATE_STOP;
|
|
|
|
if ( self.goalentity.noise ) {
|
|
sound (self, CHAN_VOICE, self.goalentity.noise, 1, ATTN_NORM);
|
|
}
|
|
else sound (self, CHAN_VOICE, self.noise, 1, ATTN_NORM);
|
|
|
|
if ( self.goalentity.spawnflags & TRAIN_ANGLES ) {
|
|
self.rotate = '0 0 0';
|
|
self.angles = self.finalangle;
|
|
}
|
|
if ( self.goalentity.spawnflags & TRAIN_NO_ROTATE ) self.rotate = '0 0 0';
|
|
|
|
self.dmg = 0;
|
|
self.think1 = rotate_train_next;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_next =
|
|
{
|
|
local entity targ;
|
|
local entity current;
|
|
local vector vdestdelta;
|
|
local float len, traveltime, div;
|
|
local string temp;
|
|
|
|
self.state = RSTATE_NEXT;
|
|
|
|
current = self.goalentity;
|
|
targ = find (world, targetname, self.path );
|
|
if ( targ.classname != "path_rotate" )
|
|
dprint("\b[ROT TRAIN]\b Next target is not path_rotate\n");
|
|
|
|
if ( self.goalentity.noise1 ) self.noise1 = self.goalentity.noise1;
|
|
sound (self, CHAN_VOICE, self.noise1, 1, ATTN_NORM);
|
|
|
|
self.goalentity = targ;
|
|
self.path = targ.target;
|
|
if (!self.path ) dprint("\b[ROT TRAIN]\b No next target\n");
|
|
|
|
if ( targ.spawnflags & TRAIN_STOP ) self.think1 = rotate_train_stop;
|
|
else if (targ.wait) self.think1 = rotate_train_wait;
|
|
else self.think1 = rotate_train_next;
|
|
|
|
if ( current.event ) {
|
|
// Trigger any events that should happen at the corner.
|
|
temp = self.target;
|
|
self.target = current.event;
|
|
self.message = current.message;
|
|
SUB_UseTargets();
|
|
self.target = temp;
|
|
self.message = string_null;
|
|
}
|
|
|
|
if ( current.spawnflags & TRAIN_ANGLES ) {
|
|
self.rotate = '0 0 0';
|
|
self.angles = self.finalangle;
|
|
}
|
|
|
|
if ( current.spawnflags & TRAIN_ROTATION ) self.rotate = current.rotate;
|
|
if ( current.spawnflags & TRAIN_DAMAGE ) self.dmg = current.dmg;
|
|
if ( current.spawnflags & TRAIN_SET_DAMAGE ) SetDamageOnTargets( current.dmg );
|
|
|
|
if ( current.speed == -1 ) {
|
|
// Warp to the next path_corner
|
|
setorigin( self, targ.origin );
|
|
self.endtime = self.ltime + 0.01;
|
|
SetTargetOrigin();
|
|
|
|
if ( targ.spawnflags & TRAIN_ANGLES ) self.angles = targ.angles;
|
|
|
|
self.duration = 1; // 1 / duration
|
|
self.cnt = time; // start time
|
|
self.dest2 = '0 0 0'; // delta
|
|
self.dest1 = self.origin; // original position
|
|
self.finaldest = self.origin;
|
|
}
|
|
else {
|
|
self.state = RSTATE_MOVE;
|
|
|
|
self.finaldest = targ.origin;
|
|
if (self.finaldest == self.origin) {
|
|
self.velocity = '0 0 0';
|
|
self.endtime = self.ltime + 0.1;
|
|
|
|
self.duration = 1; // 1 / duration
|
|
self.cnt = time; // start time
|
|
self.dest2 = '0 0 0'; // delta
|
|
self.dest1 = self.origin; // original position
|
|
self.finaldest = self.origin;
|
|
return;
|
|
}
|
|
// set destdelta to the vector needed to move
|
|
vdestdelta = self.finaldest - self.origin;
|
|
|
|
// calculate length of vector
|
|
len = vlen (vdestdelta);
|
|
|
|
if ( current.spawnflags & TRAIN_MOVETIME ) traveltime = current.speed;
|
|
else {
|
|
// check if there's a speed change
|
|
if (current.speed>0) self.speed = current.speed;
|
|
if (!self.speed) dprint("\b[ROT TRAIN]\b No speed is defined!\n");
|
|
|
|
// divide by speed to get time to reach dest
|
|
traveltime = len / self.speed;
|
|
}
|
|
|
|
if (traveltime < 0.1) {
|
|
self.velocity = '0 0 0';
|
|
self.endtime = self.ltime + 0.1;
|
|
if ( targ.spawnflags & TRAIN_ANGLES ) self.angles = targ.angles;
|
|
return;
|
|
}
|
|
|
|
// qcc won't take vec/float
|
|
div = 1 / traveltime;
|
|
|
|
if ( targ.spawnflags & TRAIN_ANGLES ) {
|
|
self.finalangle = SUB_NormalizeAngles( targ.angles );
|
|
self.rotate = ( targ.angles - self.angles ) * div;
|
|
}
|
|
|
|
// set endtime to trigger a think when dest is reached
|
|
self.endtime = self.ltime + traveltime;
|
|
|
|
// scale the destdelta vector by the time spent traveling to get velocity
|
|
self.velocity = vdestdelta * div;
|
|
|
|
self.duration = div; // 1 / duration
|
|
self.cnt = time; // start time
|
|
self.dest2 = vdestdelta; // delta
|
|
self.dest1 = self.origin; // original position
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_train_find =
|
|
{
|
|
local entity targ;
|
|
|
|
self.state = RSTATE_FIND;
|
|
LinkRotateTargets();
|
|
|
|
// the first target is the point of rotation.
|
|
// the second target is the path.
|
|
targ = find ( world, targetname, self.path);
|
|
if ( targ.classname != "path_rotate" )
|
|
dprint("\b[ROT TRAIN]\b Next target is not path_rotate\n");
|
|
|
|
// Save the current entity
|
|
self.goalentity = targ;
|
|
|
|
if ( targ.spawnflags & TRAIN_ANGLES ) {
|
|
self.angles = targ.angles;
|
|
self.finalangle = SUB_NormalizeAngles( targ.angles );
|
|
}
|
|
|
|
self.path = targ.target;
|
|
setorigin (self, targ.origin );
|
|
SetTargetOrigin();
|
|
RotateTargetsFinal();
|
|
self.think1 = rotate_train_next;
|
|
if (!self.targetname) {
|
|
// not triggered, so start immediately
|
|
self.endtime = self.ltime + 0.1;
|
|
}
|
|
else self.endtime = 0;
|
|
|
|
self.duration = 1; // 1 / duration
|
|
self.cnt = time; // start time
|
|
self.dest2 = '0 0 0'; // delta
|
|
self.dest1 = self.origin; // original position
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED func_rotate_train (0 .5 .8) (-8 -8 -8) (8 8 8) x
|
|
Trains are moving platforms that players can ride
|
|
-------- KEYS --------
|
|
path : Starts at'path_rotate' position
|
|
speed : travel speed (def=100)
|
|
dmg : blocking damage (def=0)
|
|
sounds : 0=Silent, 1=ratchet metal
|
|
noise : sound to play when train stops
|
|
noise1 : sound to play when train moves
|
|
-------- SPAWNFLAGS --------
|
|
-------- NOTES --------
|
|
Trains are moving platforms that players can ride
|
|
If the train is the target of a button or trigger, it will for trigger
|
|
The func_rotate_train entity is the center of rotation of all objects
|
|
targeted by it.
|
|
|
|
*/
|
|
//======================================================================
|
|
void() rotate_train =
|
|
{
|
|
dprint("\b[ROTATE]\b rotate_train not supported anymore!\n");
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() func_rotate_train =
|
|
{
|
|
if (!self.speed) self.speed = 100;
|
|
if (!self.target) dprint("\b[ROT TRAIN]\b missing target!\n");
|
|
|
|
if ( !self.noise ) {
|
|
if (self.sounds == 0) self.noise = SOUND_EMPTY;
|
|
if (self.sounds == 1) self.noise = "plats/train2.wav";
|
|
}
|
|
if ( !self.noise1 ) {
|
|
if (self.sounds == 0) self.noise1 = SOUND_EMPTY;
|
|
if (self.sounds == 1) self.noise1 = "plats/train1.wav";
|
|
}
|
|
|
|
precache_sound( self.noise );
|
|
precache_sound( self.noise1 );
|
|
|
|
self.cnt = 1;
|
|
self.solid = SOLID_NOT;
|
|
self.movetype = MOVETYPE_STEP;
|
|
self.use = rotate_train_use;
|
|
|
|
setmodel (self, self.model);
|
|
setsize (self, self.mins, self.maxs);
|
|
setorigin (self, self.origin);
|
|
|
|
// start trains on the second frame, to make sure their
|
|
// targets have had a chance to spawn
|
|
self.ltime = time;
|
|
self.nextthink = self.ltime + 0.1;
|
|
self.endtime = self.ltime + 0.1;
|
|
self.think = rotate_train_think;
|
|
self.think1 = rotate_train_find;
|
|
self.state = RSTATE_FIND;
|
|
|
|
self.duration = 1; // 1 / duration
|
|
self.cnt = 0.1; // start time
|
|
self.dest2 = '0 0 0'; // delta
|
|
self.dest1 = self.origin; // original position
|
|
|
|
self.flags = self.flags | FL_ONGROUND;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
// Moving clip walls
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_reversedirection;
|
|
void() rotate_door_group_reversedirection;
|
|
|
|
void() movewall_touch =
|
|
{
|
|
if (time < self.owner.attack_finished) return;
|
|
|
|
if ( self.dmg ) {
|
|
T_Damage (other, self, self.owner, self.dmg, DAMARMOR);
|
|
self.owner.attack_finished = time + 0.5;
|
|
}
|
|
else if ( self.owner.dmg ) {
|
|
T_Damage (other, self, self.owner, self.owner.dmg, DAMARMOR);
|
|
self.owner.attack_finished = time + 0.5;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() movewall_blocked =
|
|
{
|
|
local entity temp;
|
|
|
|
if (time < self.owner.attack_finished) return;
|
|
|
|
self.owner.attack_finished = time + 0.5;
|
|
|
|
if ( self.owner.classname == "func_rotate_door" ) {
|
|
temp = self;
|
|
self = self.owner;
|
|
rotate_door_group_reversedirection();
|
|
self = temp;
|
|
}
|
|
|
|
if ( self.dmg ) {
|
|
T_Damage (other, self, self.owner, self.dmg, DAMARMOR);
|
|
self.owner.attack_finished = time + 0.5;
|
|
}
|
|
else if ( self.owner.dmg ) {
|
|
T_Damage (other, self, self.owner, self.owner.dmg, DAMARMOR);
|
|
self.owner.attack_finished = time + 0.5;
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() movewall_think =
|
|
{
|
|
self.ltime = time;
|
|
self.nextthink = time + 0.02;
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED func_movewall (0 .5 .8) ? VISIBLE TOUCH NONBLOCKING
|
|
Emulate collision on rotating objects
|
|
-------- KEYS --------
|
|
dmg : damage to cause when touched or blocked
|
|
_dirt : -1 = will be excluded from dirtmapping
|
|
_minlight : Minimum light level for any surface of the brush model
|
|
_mincolor : Minimum light color for any surface (def='1 1 1' RGB)
|
|
_shadow : Will cast shadows on other models and itself
|
|
_shadowself : Will cast shadows on itself
|
|
-------- SPAWNFLAGS --------
|
|
VISIBLE : Causes brush to be displayed
|
|
TOUCH : Cause damage when touched by player
|
|
NONBLOCKING : makes the brush non-solid, This is useless if VISIBLE is set
|
|
-------- NOTES --------
|
|
Emulate collision on rotating objects
|
|
*/
|
|
//======================================================================
|
|
void() func_movewall =
|
|
{
|
|
if (check_bmodel_keys()) return; // Check for bmodel errors
|
|
|
|
self.angles = '0 0 0';
|
|
self.movetype = MOVETYPE_PUSH;
|
|
|
|
if ( self.spawnflags & MOVEWALL_NONBLOCKING ) self.solid = SOLID_NOT;
|
|
else {
|
|
self.solid = SOLID_BSP;
|
|
self.blocked = movewall_blocked;
|
|
}
|
|
|
|
if ( self.spawnflags & MOVEWALL_TOUCH ) {
|
|
self.touch = movewall_touch;
|
|
}
|
|
|
|
setmodel (self,self.model);
|
|
if ( !( self.spawnflags & MOVEWALL_VISIBLE ) ) self.model = string_null;
|
|
|
|
self.think = movewall_think;
|
|
self.nextthink = time + 0.02;
|
|
self.ltime = time;
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED rotate_object (0 .5 .8) ?
|
|
Defines a bmodel object to be rotated
|
|
-------- KEYS --------
|
|
targetname : name of bmodel object
|
|
target : rotating point entities to link too
|
|
_dirt : -1 = will be excluded from dirtmapping
|
|
_minlight : Minimum light level for any surface of the brush model
|
|
_mincolor : Minimum light color for any surface (def='1 1 1' RGB)
|
|
_shadow : Will cast shadows on other models and itself
|
|
_shadowself : Will cast shadows on itself
|
|
-------- SPAWNFLAGS --------
|
|
-------- NOTES --------
|
|
Defines a bmodel object to be rotated
|
|
Used as the target of func_rotate_door
|
|
*/
|
|
//======================================================================
|
|
void() rotate_object_move =
|
|
{
|
|
local entity ent;
|
|
|
|
ent = find( world, targetname, self.target2);
|
|
if (ent.classname == "info_rotate") {
|
|
// Move origin to new location
|
|
self.oldorigin = ent.origin;
|
|
setorigin( self, self.oldorigin );
|
|
}
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_object =
|
|
{
|
|
if (check_bmodel_keys()) return; // Check for bmodel errors
|
|
|
|
self.classname = "rotate_object";
|
|
self.solid = SOLID_NOT;
|
|
self.movetype = MOVETYPE_NONE;
|
|
self.bsporigin = TRUE; // bmodel origin 0,0,0
|
|
setmodel (self,self.model);
|
|
setsize( self, self.mins, self.maxs );
|
|
|
|
// Check for a target (info_rotate) to move the object
|
|
if (self.target2 != "") {
|
|
self.think = rotate_object_move;
|
|
self.nextthink = time + 1;
|
|
}
|
|
else self.think = SUB_Null;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_think2 =
|
|
{
|
|
local float t;
|
|
|
|
t = time - self.ltime;
|
|
self.ltime = time;
|
|
|
|
// change to alternate textures
|
|
self.frame = 1 - self.frame;
|
|
self.angles = self.dest;
|
|
|
|
if ( self.state == RSTATE_OPENING ) self.state = RSTATE_OPEN;
|
|
else {
|
|
if ( self.spawnflags & DOOR_STAYOPEN ) {
|
|
rotate_door_group_reversedirection();
|
|
return;
|
|
}
|
|
self.state = RSTATE_CLOSED;
|
|
}
|
|
|
|
sound(self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
|
|
self.think = SUB_Null;
|
|
|
|
RotateTargetsFinal();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_think =
|
|
{
|
|
local float t;
|
|
|
|
t = time - self.ltime;
|
|
self.ltime = time;
|
|
|
|
if ( time < self.endtime ) {
|
|
self.angles = self.angles + ( self.rotate * t );
|
|
RotateTargets();
|
|
}
|
|
else {
|
|
self.angles = self.dest;
|
|
RotateTargets();
|
|
self.think = rotate_door_think2;
|
|
}
|
|
|
|
self.nextthink = time + 0.01;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_reversedirection =
|
|
{
|
|
local vector start;
|
|
|
|
// change to alternate textures
|
|
self.frame = 1 - self.frame;
|
|
|
|
if ( self.state == RSTATE_CLOSING ) {
|
|
start = self.dest1;
|
|
self.dest = self.dest2;
|
|
self.state = RSTATE_OPENING;
|
|
}
|
|
else {
|
|
start = self.dest2;
|
|
self.dest = self.dest1;
|
|
self.state = RSTATE_CLOSING;
|
|
}
|
|
|
|
sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
|
|
|
|
self.rotate = ( self.dest - start ) * ( 1 / self.speed );
|
|
self.think = rotate_door_think;
|
|
self.nextthink = time + 0.02;
|
|
self.endtime = time + self.speed - ( self.endtime - time );
|
|
self.ltime = time;
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_group_reversedirection =
|
|
{
|
|
local string name;
|
|
|
|
// tell all associated rotaters to reverse direction
|
|
if ( self.group ) {
|
|
name = self.group;
|
|
self = find( world, group, name);
|
|
while( self ) {
|
|
rotate_door_reversedirection();
|
|
self = find( self, group, name);
|
|
}
|
|
}
|
|
else rotate_door_reversedirection();
|
|
};
|
|
|
|
//----------------------------------------------------------------------
|
|
void() rotate_door_use =
|
|
{
|
|
// local entity t;
|
|
local vector start;
|
|
|
|
if ( ( self.state != RSTATE_OPEN ) && ( self.state != RSTATE_CLOSED ) ) return;
|
|
|
|
if ( !self.cnt ) {
|
|
self.cnt = 1;
|
|
LinkRotateTargets();
|
|
}
|
|
|
|
// change to alternate textures
|
|
self.frame = 1 - self.frame;
|
|
|
|
if ( self.state == RSTATE_CLOSED ) {
|
|
start = self.dest1;
|
|
self.dest = self.dest2;
|
|
self.state = RSTATE_OPENING;
|
|
}
|
|
else {
|
|
start = self.dest2;
|
|
self.dest = self.dest1;
|
|
self.state = RSTATE_CLOSING;
|
|
}
|
|
|
|
sound(self, CHAN_VOICE, self.noise2, 1, ATTN_NORM);
|
|
|
|
self.rotate = ( self.dest - start ) * ( 1 / self.speed );
|
|
self.think = rotate_door_think;
|
|
self.nextthink = time + 0.01;
|
|
self.endtime = time + self.speed;
|
|
self.ltime = time;
|
|
};
|
|
|
|
//======================================================================
|
|
/*QUAKED func_rotate_door (0 .5 .8) (-8 -8 -8) (8 8 8) STAYOPEN
|
|
Rotating door moving around a point of rotation
|
|
-------- KEYS --------
|
|
target : Points to the rotate_object bmodel entity
|
|
target2 : Move on spawn to this entity (info_rotate) location
|
|
dmg : Damage caused when blocked (def=2) negative numbers =no damage
|
|
speed : Time taken to rotate open/close
|
|
sounds : 1=medieval 2=metal 3=base 4=Silent (def=1)
|
|
-------- SPAWNFLAGS --------
|
|
STAYOPEN : Reopen after closing, stops a once only door from closing if blocked
|
|
-------- NOTES --------
|
|
Rotating door moving around a point of rotation
|
|
*/
|
|
//======================================================================
|
|
void() func_rotate_door =
|
|
{
|
|
if (!self.target) dprint("\b[ROT DOOR]\b missing target!\n");
|
|
|
|
self.dest1 = '0 0 0';
|
|
self.dest2 = self.angles;
|
|
self.angles = self.dest1;
|
|
|
|
// default to 2 seconds
|
|
if ( !self.speed ) self.speed = 2;
|
|
self.cnt = 0;
|
|
|
|
if (!self.dmg) self.dmg = 2;
|
|
else if ( self.dmg < 0 ) self.dmg = 0;
|
|
|
|
if (self.sounds == 0) self.sounds = 1;
|
|
if (self.sounds == 1) {
|
|
self.noise1 = "doors/latch2.wav";
|
|
self.noise2 = "doors/winch2.wav";
|
|
self.noise3 = "doors/drclos4.wav";
|
|
}
|
|
if (self.sounds == 2) {
|
|
self.noise2 = "doors/airdoor1.wav";
|
|
self.noise1 = "doors/airdoor2.wav";
|
|
self.noise3 = "doors/airdoor2.wav";
|
|
}
|
|
if (self.sounds == 3) {
|
|
self.noise2 = "doors/basesec1.wav";
|
|
self.noise1 = "doors/basesec2.wav";
|
|
self.noise3 = "doors/basesec2.wav";
|
|
}
|
|
if (self.sounds == 4) {
|
|
self.noise1 = SOUND_EMPTY;
|
|
self.noise2 = SOUND_EMPTY;
|
|
self.noise3 = SOUND_EMPTY;
|
|
}
|
|
|
|
precache_sound (self.noise1);
|
|
precache_sound (self.noise2);
|
|
precache_sound (self.noise3);
|
|
|
|
self.solid = SOLID_NOT;
|
|
self.movetype = MOVETYPE_NONE;
|
|
setmodel (self, self.model);
|
|
setorigin( self, self.origin );
|
|
setsize( self, self.mins, self.maxs );
|
|
self.state = RSTATE_CLOSED;
|
|
self.use = rotate_door_use;
|
|
self.think = SUB_Null;
|
|
};
|