quakec/source/server/entities/doors.qc
2024-09-01 18:12:38 -07:00

737 lines
15 KiB
C++

/*
server/entities/doors.qc
Doors are similar to buttons, but can spawn a fat trigger field
around them to open without a touch, and they link together to
form simultanious double/quad doors.
Door.owner is the master door. If there is only one door, it
points to itself. If multiple doors, all will point to a single
one.
Door.enemy chains from the master door through all doors linked
in the chain.
Copyright (C) 2021-2024 NZ:P Team
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:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
*/
/*
code for show that you need money EDITASAP
*/
float DOOR_START_OPEN = 1;
float DOOR_DONT_LINK = 4;
float DOOR_DEBRIS = 8;
float DOOR_SILVER_KEY = 16;
float DOOR_TOGGLE = 32;
float DOOR_POWER = 64;
.float delay;
.void() think1;
.vector finaldest;
.entity trigger_field;
.float wait;
.float speed;
.vector pos1;
.vector pos2;
.float cost;
.float attack_delay;
.float lip;
void() print_need_power =
{
if(other.classname == "player" && !other.downed)
{
/*if (self.owner.message)
centerprint(other, self.owner.message);
else
centerprint(other, "Power must be activated first");*/
useprint (other, 8, 0, 0);
}
};
/*
=============================================================================
THINK FUNCTIONS
=============================================================================
*/
void() door_go_down;
void() door_go_up;
void() door_blocked =
{
DamageHandler (other, self, self.dmg, DMG_TYPE_OTHER);
// if a door has a negative wait, it would never come back if blocked,
// so let it just squash the object to death real fast
if (self.wait >= 0)
{
if (self.state == STATE_DOWN)
door_go_up ();
else
door_go_down ();
}
};
void() door_hit_top =
{
self.state = STATE_TOP;
if ((self.classname == "door_nzp_cost" || self.classname == "door_nzp" || self.classname == "door_open"))
{
//remove (self.owner.trigger_field); //moto - what does this do lol
if (!(self.spawnflags & 128)) {
setmodel(self, "");
self.solid = SOLID_NOT;
}
self.isopen = 1;
return;//so we dont have to reopen doors
}
if (self.spawnflags & DOOR_TOGGLE)
return; // don't come down automatically
self.think = door_go_down;
self.nextthink = self.ltime + self.wait;
};
void() door_hit_bottom =
{
self.state = STATE_BOTTOM;
};
void() door_go_down =
{
if ((self.classname == "door_nzp_cost" || self.classname == "door_nzp" || self.classname == "door_open"))
{
if (!(self.spawnflags & 128))
setmodel(self, "");
self.isopen = 1;
return;//so we dont have to reopen doors
}
if (self.max_health)
{
self.takedamage = DAMAGE_YES;
self.health = self.max_health;
}
self.state = STATE_DOWN;
if (self.spawnflags & 256)
SUB_CalcAngleMove(self.pos1, self.speed, door_hit_bottom);
else
SUB_CalcMove (self.pos1, self.speed, door_hit_bottom);
};
void() door_go_up =
{
if (self.state == STATE_UP)
return; // allready going up
if (self.state == STATE_TOP)
{ // reset top wait time
self.nextthink = self.ltime + self.wait;
return;
}
self.state = STATE_UP;
if (self.spawnflags & 256)
SUB_CalcAngleMove(self.pos2, self.speed, door_hit_top);
else
SUB_CalcMove (self.pos2, self.speed, door_hit_top);
#ifndef FTE
Open_Waypoint(self.wayTarget);
#endif // FTE
SUB_UseTargets();
};
/*
=============================================================================
ACTIVATION FUNCTIONS
=============================================================================
*/
void() door_fire =
{
local entity oself;
local entity starte;
if (isPowerOn == FALSE)
{
if (self.spawnflags & DOOR_POWER )
{
if(other.classname == "player" && !other.downed)
{
/*if (self.message)
centerprint(other, self.message);
else
centerprint(other, "Power must be activated first");*/
useprint (other, 8, 0, 0);
return;
}
}
}
self.message = string_null; // no more message
oself = self;
if (self.door_model_target)
{
entity tempe = find(world, classname, "func_door_model");
if (tempe != world) {
///door_model_name, self.door_model_target
if (tempe.door_model_name == self.door_model_target) {
setmodel(tempe, "");
remove(tempe);
} else {
bprint(PRINT_HIGH, "Could not find door_model_name: ");
bprint(PRINT_HIGH, self.door_model_target);
bprint(PRINT_HIGH, "\n");
}
}
}
if (self.spawnflags & DOOR_TOGGLE)
{
if (self.state == STATE_UP || self.state == STATE_TOP)
{
starte = self;
do
{
door_go_down ();
self = self.enemy;
} while ( (self != starte) && (self != world) );
self = oself;
return;
}
}
// trigger all paired doors
starte = self;
do
{
door_go_up ();
//self.isopen = true;
self = self.enemy;
} while ( (self != starte) && (self != world) );
self = oself;
SUB_UseTargets();
};
void() cost_door =
{
if (self.state == STATE_TOP || self.state == STATE_UP)
return;
if (isPowerOn == FALSE)
{
if (self.spawnflags & DOOR_POWER )
{
if(other.classname == "player" && !other.downed)
{
/*if (self.message)
centerprint(other, self.message);
else
centerprint(other, "Power must be activated first");*/
useprint (other, 8, 0, 0);
return;
}
}
}
if (other.button7 && !(other.semi_actions & SEMIACTION_USE))
{
other.semi_actions |= SEMIACTION_USE;
if (other.points >= self.cost)
{
door_fire();
// We need to create a new entity with the specific assignment of
// just playing the door sound, due to the door moving if we just
// assign the sound to the door, it cannot be heard when the engine
// supports SOUNDFLAG_FOLLOW.
entity door_sound = spawn();
setorigin(door_sound, other.origin);
door_sound.think = SUB_Remove;
door_sound.nextthink = time + 5;
Sound_PlaySound(other, "sounds/misc/ching.wav", SOUND_TYPE_ENV_CHING, SOUND_PRIORITY_PLAYALWAYS);
Sound_PlaySound(door_sound, "sounds/misc/buy.wav", SOUND_TYPE_ENV_IMPORTANT, SOUND_PRIORITY_PLAYALWAYS);
switch(self.sounds)
{
case 1:
Sound_PlaySound(door_sound, "sounds/misc/wood_door.wav", SOUND_TYPE_ENV_CHING, SOUND_PRIORITY_PLAYALWAYS);
break;
case 2:
Sound_PlaySound(door_sound, "sounds/misc/debris.wav", SOUND_TYPE_ENV_CHING, SOUND_PRIORITY_PLAYALWAYS);
break;
default:
break;
}
Player_RemoveScore(other, self.cost);
self.solid = SOLID_NOT;
}
else
{
if(other.classname == "player" && !other.downed)
{
centerprint (other, STR_NOTENOUGHPOINTS);
Sound_PlaySound(other, "sounds/misc/denybuy.wav", SOUND_TYPE_ENV_CHING, SOUND_PRIORITY_PLAYALWAYS);
other.semi_actions |= SEMIACTION_USE;
}
}
}
else if (!other.button7)
{
if(other.classname == "player" && !other.downed)
{
/*if (!self.message)
centerprint3(other, "press use to open door for ", b = ftos (self.cost), " points\n");
else
centerprint (other, self.message);*/
if (self.spawnflags & DOOR_DEBRIS)
useprint (other, 2, self.cost, 0);
else
useprint (other, 1, self.cost, 0);
return;
}
}
};
void() door_use =
{
local entity oself;
oself = self;
self = self.owner;
if (self.cost)
cost_door();
else
door_fire ();
self = oself;
};
void() door_trigger_touch =
{
if(other.classname != "player" || other.downed)
return;
if(cvar("waypoint_mode"))
{
if(other.active_door != self.owner)
{
bprint(PRINT_HIGH, "Current Door for special waypoints set to ");
bprint(PRINT_HIGH, self.owner.wayTarget);
bprint(PRINT_HIGH, "\n");
other.active_door = self.owner;
}
return;
}
if (self.owner.targetname && self.owner.classname != "door_nzp_cost")
return;
if (other.health <= 20)
return;
activator = other;
self = self.owner;
door_use ();
};
void() door_killed =
{
local entity oself;
oself = self;
self = self.owner;
self.health = self.max_health;
self.takedamage = DAMAGE_NO; // will be reset upon return
door_use ();
self = oself;
};
/*
================
door_touch
Prints messages and opens key doors
================
*/
void() door_touch =
{
if (other.classname != "player")
return;
if (other.button7)
{
return;
}
if (self.owner.message != "")
{
centerprint (other, self.owner.message);
}
// Since we've removed support for Quake keys (*.items),
// this additional check is needed to prevent standard
// func_door entities from activating on-touch if it's
// set to only be triggered.
if (self.classname == "door" && self.targetname)
return;
if (isPowerOn == FALSE)
{
if (self.owner.spawnflags & DOOR_POWER)
{
self.touch = print_need_power;
return;
}
}
self.touch = SUB_Null;
if (self.enemy)
self.enemy.touch = SUB_Null; // get paired door
door_use ();
};
/*
=============================================================================
SPAWNING FUNCTIONS
=============================================================================
*/
entity(vector fmins, vector fmaxs) spawn_field =
{
local entity trigger;
local vector t1, t2;
trigger = spawn();
trigger.movetype = MOVETYPE_NONE;
trigger.solid = SOLID_TRIGGER;
trigger.owner = self;
trigger.touch = door_trigger_touch;
setorigin(trigger, self.origin);
t1 = fmins;
t2 = fmaxs;
setsize (trigger, t1/* - '15 15 8'*/, t2/* + '15 15 8'*/);
return (trigger);
};
float (entity e1, entity e2) EntitiesTouching =
{
if (e1.mins_x > e2.maxs_x)
return FALSE;
if (e1.mins_y > e2.maxs_y)
return FALSE;
if (e1.mins_z > e2.maxs_z)
return FALSE;
if (e1.maxs_x < e2.mins_x)
return FALSE;
if (e1.maxs_y < e2.mins_y)
return FALSE;
if (e1.maxs_z < e2.mins_z)
return FALSE;
return TRUE;
};
/*
=============
LinkDoors
=============
*/
void() LinkDoors =
{
local entity t, starte;
local vector cmins, cmaxs;
if (self.enemy)
return; // already linked by another door
if (self.spawnflags & 4)
{
self.owner = self.enemy = self;
return; // don't want to link this door
}
cmins = self.mins;
cmaxs = self.maxs;
starte = self;
t = self;
do
{
self.owner = starte; // master door
if (self.health)
starte.health = self.health;
if (self.targetname)
starte.targetname = self.targetname;
if (self.message != "")
starte.message = self.message;
t = find (t, classname, self.classname);
if (!t)
{
self.enemy = starte; // make the chain a loop
// shootable, fired, or key doors just needed the owner/enemy links,
// they don't spawn a field
self = self.owner;
// This guards against spawning more than one
// trigger field upon restart.
if (self.owner.trigger_field)
return;
if (self.health)
return;
// Only disable trigger spawn fields if this is a vanilla Quake door with a targetname
// AND its without a wayTarget. Implies the door isnt really a "door" and like a bonus
// or something, since if its a proper door you will want waypoint entry..
if (self.targetname && self.classname != "door_nzp_cost" && !self.wayTarget)
return;
self.owner.trigger_field = spawn_field(cmins, cmaxs);
return;
}
if (EntitiesTouching(self,t))
{
if (t.enemy)
objerror ("cross connected doors");
self.enemy = t;
self = t;
if (t.mins_x < cmins_x)
cmins_x = t.mins_x;
if (t.mins_y < cmins_y)
cmins_y = t.mins_y;
if (t.mins_z < cmins_z)
cmins_z = t.mins_z;
if (t.maxs_x > cmaxs_x)
cmaxs_x = t.maxs_x;
if (t.maxs_y > cmaxs_y)
cmaxs_y = t.maxs_y;
if (t.maxs_z > cmaxs_z)
cmaxs_z = t.maxs_z;
}
} while (1 );
};
/*
=====================
SPECIAL DOORS
thease doors can be opened by money or when power is on
=====================
*/
void() func_door_model =
{
place_model();
}
void() func_door =
{
SetMovedir ();
self.max_health = self.health;
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
setorigin (self, self.origin);
setmodel (self, self.model);
self.classname = "door";
self.blocked = door_blocked;
self.use = door_use;
if (!self.speed)
self.speed = 100;
if (!self.wait)
self.wait = 3;
if (!self.lip)
self.lip = 8;
if (!self.dmg)
self.dmg = 2;
self.pos1 = self.origin;
self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
// but spawn in the open position
if (self.spawnflags & DOOR_START_OPEN)
{
setorigin (self, self.pos2);
self.pos2 = self.pos1;
self.pos1 = self.origin;
}
self.state = STATE_BOTTOM;
if (self.health)
{
self.takedamage = DAMAGE_YES;
self.th_die = door_killed;
}
self.touch = door_touch;
// LinkDoors can't be done until all of the doors have been spawned, so
// the sizes can be detected properly.
self.think = LinkDoors;
self.nextthink = self.ltime + 0.1;
#ifdef FTE
HalfLife_DoRender();
#endif // FTE
};
void() func_door_nzp =
{
#ifndef FTE
if (!self.renderamt)
self.renderamt = 255;
if (!self.rendermode)
self.rendermode = 4;
if (!self.rendercolor)
self.rendercolor = '0 0 0';
if (!self.mapversion)
self.mapversion = 0;
if (!self.ammo)
self.ammo = 0; //thease are just here because they can be sp there is no error message. serve no real purpose
#endif // FTE
// naievil (FIXME) ^^^^ hl rendermodes, mapversion
makevectors(self.angles);
SetMovedir ();
self.max_health = self.health;
self.solid = SOLID_BSP;
self.movetype = MOVETYPE_PUSH;
self.oldmodel = self.model;
self.oldorigin = self.origin;
self.oldstate = self.state;
setorigin (self, self.origin);
setmodel (self, self.model);
self.blocked = door_blocked;
self.use = door_use;
if (!roundinit) {
if (self.cost) {
self.classname = "door_nzp_cost";
switch(self.sounds)
{
case 1: precache_sound("sounds/misc/wood_door.wav"); break;
case 2: precache_sound("sounds/misc/debris.wav"); break;
default: break;
}
} else
self.classname = "door_nzp";
}
if (!self.speed)
self.speed = 100;
if (!self.wait)
self.wait = 3;
if (!self.lip)
self.lip = 8;
if (!self.dmg)
self.dmg = 2;
// only rotate on the Y axis.. potentially change?
if (self.spawnflags & 256) {
self.pos1 = self.angles;
self.pos2 = self.pos1;
self.pos2_y = self.pos1_y + self.distance;
} else {
self.pos1 = self.origin;
self.pos2 = self.pos1 + self.movedir*(fabs(self.movedir*self.size) - self.lip);
}
// DOOR_START_OPEN is to allow an entity to be lighted in the closed position
// but spawn in the open position
if (self.spawnflags & DOOR_START_OPEN)
{
setorigin (self, self.pos2);
self.pos2 = self.pos1;
self.pos1 = self.origin;
}
self.state = STATE_BOTTOM;
if (self.health)
{
self.takedamage = DAMAGE_YES;
self.th_die = door_killed;
}
self.touch = door_touch;
// LinkDoors can't be done until all of the doors have been spawned, so
// the sizes can be detected properly.
self.think = LinkDoors;
self.nextthink = self.ltime + 0.1;
#ifdef FTE
HalfLife_DoRender();
#endif // FTE
};