mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
3157 lines
76 KiB
C++
3157 lines
76 KiB
C++
/*
|
|
===========================================================================
|
|
Copyright (C) 2025 the OpenMoHAA team
|
|
|
|
This file is part of OpenMoHAA source code.
|
|
|
|
OpenMoHAA source code 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.
|
|
|
|
OpenMoHAA source code 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 OpenMoHAA source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
// misc.cpp: Basically the big stew pot of the DLLs, or maybe a garbage bin, whichever
|
|
// metaphore you prefer. This really should be cleaned up. Anyway, this
|
|
// should contain utility functions that could be used by any entity.
|
|
// Right now it contains everything from entities that could be in their
|
|
// own file to my mother's pot roast recipes.
|
|
//
|
|
|
|
#include "g_local.h"
|
|
#include "entity.h"
|
|
#include "trigger.h"
|
|
#include "explosion.h"
|
|
#include "misc.h"
|
|
#include "navigate.h"
|
|
#include "specialfx.h"
|
|
#include "player.h"
|
|
#include "weaputils.h"
|
|
#include "g_spawn.h"
|
|
#include "g_phys.h"
|
|
#include "../script/scriptexception.h"
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED detail (0.5 0 1.0) ?
|
|
|
|
Used to fake detail brushes, convenient for grouping
|
|
|
|
******************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_group (0.5 0.5 0.5) ?
|
|
|
|
Used to group brushes together just for editor convenience.
|
|
|
|
******************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_remove (0.75 0.75 0.75) ?
|
|
|
|
Used for lighting and such
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, FuncRemove, "func_remove") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
FuncRemove::FuncRemove()
|
|
{
|
|
PostEvent(EV_Remove, EV_REMOVE);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED misc_model (1 0.5 1) (0 0 0) (0 0 0)
|
|
"model" arbitrary .tik file to display
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, MiscModel, "misc_model") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
MiscModel::MiscModel()
|
|
{
|
|
PostEvent(EV_Remove, EV_REMOVE);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
|
|
|
|
Used as a positional target for spotlights, etc.
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Listener, InfoNull, "info_null") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
InfoNull::InfoNull()
|
|
{
|
|
PostEvent(EV_Remove, EV_REMOVE);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4)
|
|
|
|
Used as a positional target for lightning.
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, InfoNotNull, "info_notnull") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_explodingwall (0 0.25 0.5) ? RANDOMANGLES LANDSHATTER NOT_PLAYERS MONSTERS PROJECTILES INVISIBLE ACCUMALATIVE TWOSTAGE
|
|
|
|
Blows up on activation or when attacked
|
|
|
|
"explosions" number of explosions to spawn ( default 1 )
|
|
"land_angles" The angles you want this piece to\
|
|
orient to when it lands on the ground
|
|
"land_radius" The distance of the ground the piece\
|
|
should be when on the ground ( default 16 )
|
|
"anglespeed" Speed at which pieces rotate ( default 100 ) \
|
|
if RANDOMANGLES ( default is 600 )
|
|
"key" The item needed to activate this. (default nothing)
|
|
"base_velocity" The speed that the debris will have when triggered. (default 0 0 280)
|
|
"random_velocity" The variation of the velocity. x & y will be from -n < X,Y < n and z is 0 <= Z < n. (default 140 140 140)
|
|
|
|
|
|
IF RANDOMANGLES is set, object randomly spins while in the air.
|
|
IF LANDSHATTER is set, object shatters when it hits the ground.
|
|
IF TWOSTAGE is set, object can be shattered once it lands on the ground.
|
|
IF ACCUMALATIVE is set, damage is accumlative not threshold
|
|
IF INVISIBLE is set, these are invisible and not solid until triggered
|
|
If NOT_PLAYERS is set, the trigger does not respond to players
|
|
If MONSTERS is set, the trigger will respond to monsters
|
|
If PROJECTILES is set, the trigger will respond to projectiles (rockets, grenades, etc.)
|
|
|
|
******************************************************************************/
|
|
#define RANDOMANGLES (1 << 0)
|
|
#define LANDSHATTER (1 << 1)
|
|
#define INVISIBLE (1 << 5)
|
|
#define ACCUMULATIVE (1 << 6)
|
|
#define TWOSTAGE (1 << 7)
|
|
|
|
Event EV_ExplodingWall_StopRotating
|
|
(
|
|
"stoprotating", EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Stop rotating the wall.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_OnGround
|
|
(
|
|
"checkonground", EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Check if exploding wall is on ground.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_ExplodingWall_AngleSpeed
|
|
(
|
|
"anglespeed", EV_DEFAULT,
|
|
"f",
|
|
"speed",
|
|
"Set the angle speed.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_LandRadius
|
|
(
|
|
"land_radius", EV_DEFAULT,
|
|
"f",
|
|
"radius",
|
|
"Set the land radius.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_LandAngles
|
|
(
|
|
"land_angles", EV_DEFAULT,
|
|
"v",
|
|
"angles",
|
|
"Set the land angles.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_BaseVelocity
|
|
(
|
|
"base_velocity", EV_DEFAULT,
|
|
"v",
|
|
"velocity",
|
|
"Set the base velocity.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_RandomVelocity(
|
|
"random_velocity",
|
|
EV_DEFAULT,
|
|
"v",
|
|
"velocity",
|
|
"Set the amount of random variation of the base velocity.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_SetDmg
|
|
(
|
|
"dmg", EV_DEFAULT,
|
|
"i",
|
|
"dmg",
|
|
"Set the damage from the exploding wall.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_ExplodingWall_SetExplosions
|
|
(
|
|
"explosions", EV_DEFAULT,
|
|
"i",
|
|
"explosions",
|
|
"Set the number of explosions.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_ExplodingWall_Setup
|
|
(
|
|
"setup", EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Initializes the exploding wall.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Trigger, ExplodingWall, "func_explodingwall") {
|
|
{&EV_ExplodingWall_Setup, &ExplodingWall::Setup },
|
|
{&EV_Trigger_Effect, &ExplodingWall::Explode },
|
|
{&EV_Damage, &ExplodingWall::DamageEvent },
|
|
{&EV_Touch, &ExplodingWall::TouchFunc },
|
|
{&EV_ExplodingWall_StopRotating, &ExplodingWall::StopRotating },
|
|
{&EV_ExplodingWall_OnGround, &ExplodingWall::CheckOnGround },
|
|
{&EV_ExplodingWall_AngleSpeed, &ExplodingWall::AngleSpeed },
|
|
{&EV_ExplodingWall_LandRadius, &ExplodingWall::LandRadius },
|
|
{&EV_ExplodingWall_LandAngles, &ExplodingWall::LandAngles },
|
|
{&EV_ExplodingWall_BaseVelocity, &ExplodingWall::BaseVelocity },
|
|
{&EV_ExplodingWall_RandomVelocity, &ExplodingWall::RandomVelocity},
|
|
{&EV_ExplodingWall_SetDmg, &ExplodingWall::SetDmg },
|
|
{&EV_ExplodingWall_SetExplosions, &ExplodingWall::SetExplosions },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
void ExplodingWall::AngleSpeed(Event *ev)
|
|
{
|
|
angle_speed = ev->GetFloat(1);
|
|
}
|
|
|
|
void ExplodingWall::LandRadius(Event *ev)
|
|
{
|
|
land_radius = ev->GetFloat(1);
|
|
}
|
|
|
|
void ExplodingWall::LandAngles(Event *ev)
|
|
{
|
|
land_angles = ev->GetVector(1);
|
|
}
|
|
|
|
void ExplodingWall::BaseVelocity(Event *ev)
|
|
{
|
|
base_velocity = ev->GetVector(1);
|
|
}
|
|
|
|
void ExplodingWall::RandomVelocity(Event *ev)
|
|
{
|
|
random_velocity = ev->GetVector(1);
|
|
}
|
|
|
|
void ExplodingWall::SetDmg(Event *ev)
|
|
{
|
|
dmg = ev->GetInteger(1);
|
|
}
|
|
|
|
void ExplodingWall::SetExplosions(Event *ev)
|
|
{
|
|
explosions = ev->GetInteger(1);
|
|
}
|
|
|
|
void ExplodingWall::Explode(Event *ev)
|
|
{
|
|
Entity *other;
|
|
Vector pos;
|
|
Vector mins, maxs;
|
|
int i;
|
|
|
|
if (spawnflags & INVISIBLE) {
|
|
showModel();
|
|
setSolidType(SOLID_BSP);
|
|
takedamage = DAMAGE_YES;
|
|
}
|
|
|
|
if (takedamage == DAMAGE_NO) {
|
|
return;
|
|
}
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
health = 0;
|
|
takedamage = DAMAGE_NO;
|
|
|
|
// Create explosions
|
|
for (i = 0; i < explosions; i++) {
|
|
pos[0] = absmin[0] + G_Random(size[0]);
|
|
pos[1] = absmin[1] + G_Random(size[1]);
|
|
pos[2] = absmin[2] + G_Random(size[2]);
|
|
|
|
CreateExplosion(pos, dmg, this, other, this);
|
|
}
|
|
|
|
// throw itself
|
|
state = 1;
|
|
on_ground = false;
|
|
PostEvent(EV_ExplodingWall_OnGround, FRAMETIME);
|
|
|
|
velocity.x = base_velocity.x + G_CRandom(random_velocity.x);
|
|
velocity.y = base_velocity.y + G_CRandom(random_velocity.y);
|
|
velocity.z = base_velocity.z + G_Random(random_velocity.z);
|
|
|
|
setMoveType(MOVETYPE_BOUNCE);
|
|
setSolidType(SOLID_BBOX);
|
|
if (spawnflags & RANDOMANGLES) {
|
|
avelocity[0] = G_Random(angle_speed);
|
|
avelocity[1] = G_Random(angle_speed);
|
|
avelocity[2] = G_Random(angle_speed);
|
|
} else {
|
|
Vector delta;
|
|
float most;
|
|
float time;
|
|
int t;
|
|
|
|
delta = land_angles - angles;
|
|
if (delta[0] > 180) {
|
|
delta[0] -= 360;
|
|
}
|
|
if (delta[0] < -180) {
|
|
delta[0] += 360;
|
|
}
|
|
if (delta[1] > 180) {
|
|
delta[1] -= 360;
|
|
}
|
|
if (delta[1] < -180) {
|
|
delta[1] += 360;
|
|
}
|
|
if (delta[2] > 180) {
|
|
delta[2] -= 360;
|
|
}
|
|
if (delta[2] < -180) {
|
|
delta[2] += 360;
|
|
}
|
|
most = MaxValue(delta);
|
|
if (!angle_speed) {
|
|
angle_speed = 1;
|
|
}
|
|
t = 10 * most / angle_speed;
|
|
time = (float)t / 10;
|
|
delta = delta * (1.0 / time);
|
|
avelocity = delta;
|
|
PostEvent(EV_ExplodingWall_StopRotating, time);
|
|
state = 2;
|
|
}
|
|
|
|
ActivateTargets(ev);
|
|
|
|
if (land_radius > 0) {
|
|
mins[0] = mins[1] = mins[2] = -land_radius;
|
|
maxs[0] = maxs[1] = maxs[2] = land_radius;
|
|
setSize(mins, maxs);
|
|
}
|
|
|
|
attack_finished = 0;
|
|
}
|
|
|
|
void ExplodingWall::DamageEvent(Event *ev)
|
|
{
|
|
Event *event;
|
|
Entity *inflictor;
|
|
Entity *attacker;
|
|
int damage;
|
|
|
|
if (takedamage == DAMAGE_NO) {
|
|
return;
|
|
}
|
|
|
|
if (on_ground) {
|
|
GroundDamage(ev);
|
|
return;
|
|
}
|
|
|
|
attacker = ev->GetEntity(1);
|
|
damage = ev->GetInteger(2);
|
|
inflictor = ev->GetEntity(3);
|
|
|
|
if (spawnflags & ACCUMULATIVE) {
|
|
health -= damage;
|
|
if (health > 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (damage < health) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
event = new Event(EV_Activate);
|
|
event->AddEntity(attacker);
|
|
ProcessEvent(event);
|
|
}
|
|
|
|
void ExplodingWall::GroundDamage(Event *ev)
|
|
{
|
|
Entity *inflictor;
|
|
Entity *attacker;
|
|
Vector pos;
|
|
int damage;
|
|
|
|
if (takedamage == DAMAGE_NO) {
|
|
return;
|
|
}
|
|
|
|
damage = ev->GetInteger(1);
|
|
inflictor = ev->GetEntity(2);
|
|
attacker = ev->GetEntity(3);
|
|
|
|
if (spawnflags & ACCUMULATIVE) {
|
|
health -= damage;
|
|
if (health > 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
if (damage < health) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (explosions) {
|
|
pos[0] = absmin[0] + G_Random(size[0]);
|
|
pos[1] = absmin[1] + G_Random(size[1]);
|
|
pos[2] = absmin[2] + G_Random(size[2]);
|
|
|
|
CreateExplosion(pos, damage, this, attacker, this);
|
|
}
|
|
takedamage = DAMAGE_NO;
|
|
hideModel();
|
|
BroadcastAIEvent();
|
|
PostEvent(EV_Remove, 0);
|
|
}
|
|
|
|
void ExplodingWall::SetupSecondStage(void)
|
|
{
|
|
health = max_health;
|
|
takedamage = DAMAGE_YES;
|
|
}
|
|
|
|
void ExplodingWall::StopRotating(Event *ev)
|
|
{
|
|
avelocity = vec_zero;
|
|
setAngles(land_angles);
|
|
if (spawnflags & TWOSTAGE) {
|
|
SetupSecondStage();
|
|
}
|
|
}
|
|
|
|
void ExplodingWall::CheckOnGround(Event *ev)
|
|
{
|
|
if ((velocity == vec_zero) && groundentity) {
|
|
Vector delta;
|
|
float most;
|
|
float time;
|
|
int t;
|
|
|
|
delta = land_angles - angles;
|
|
if (delta.length() > 1) {
|
|
if (delta[0] > 180) {
|
|
delta[0] -= 360;
|
|
}
|
|
if (delta[0] < -180) {
|
|
delta[0] += 360;
|
|
}
|
|
if (delta[1] > 180) {
|
|
delta[1] -= 360;
|
|
}
|
|
if (delta[1] < -180) {
|
|
delta[1] += 360;
|
|
}
|
|
if (delta[2] > 180) {
|
|
delta[2] -= 360;
|
|
}
|
|
if (delta[2] < -180) {
|
|
delta[2] += 360;
|
|
}
|
|
most = MaxValue(delta);
|
|
if (angle_speed > 3) {
|
|
t = 10.0f * most / (angle_speed / 3);
|
|
} else {
|
|
t = 10.0f * most;
|
|
}
|
|
time = (float)t / 10;
|
|
delta = delta * (1.0 / time);
|
|
avelocity = delta;
|
|
PostEvent(EV_ExplodingWall_StopRotating, time);
|
|
}
|
|
state = 2;
|
|
setSize(orig_mins, orig_maxs);
|
|
on_ground = true;
|
|
} else {
|
|
PostEvent(ev, FRAMETIME);
|
|
}
|
|
}
|
|
|
|
void ExplodingWall::TouchFunc(Event *ev)
|
|
{
|
|
Entity *other;
|
|
|
|
if ((velocity == vec_zero) || (level.time < attack_finished)) {
|
|
return;
|
|
}
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
if ((spawnflags & LANDSHATTER) && (other == world)) {
|
|
Vector pos;
|
|
|
|
takedamage = DAMAGE_NO;
|
|
|
|
if (explosions) {
|
|
pos[0] = absmin[0] + G_Random(size[0]);
|
|
pos[1] = absmin[1] + G_Random(size[1]);
|
|
pos[2] = absmin[2] + G_Random(size[2]);
|
|
|
|
CreateExplosion(pos, dmg, this, other, this);
|
|
}
|
|
hideModel();
|
|
BroadcastAIEvent();
|
|
PostEvent(EV_Remove, 0);
|
|
return;
|
|
}
|
|
|
|
if (other->takedamage) {
|
|
other->Damage(this, activator, dmg, origin, vec_zero, vec_zero, 20, 0, MOD_EXPLODEWALL);
|
|
Sound("debris_generic", CHAN_WEAPON);
|
|
attack_finished = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
void ExplodingWall::Setup(Event *ev)
|
|
{
|
|
if (spawnflags & INVISIBLE) {
|
|
if (Targeted()) {
|
|
takedamage = DAMAGE_YES;
|
|
} else {
|
|
takedamage = DAMAGE_NO;
|
|
}
|
|
hideModel();
|
|
setSolidType(SOLID_NOT);
|
|
} else {
|
|
showModel();
|
|
setSolidType(SOLID_BSP);
|
|
takedamage = DAMAGE_YES;
|
|
}
|
|
|
|
setMoveType(MOVETYPE_PUSH);
|
|
setOrigin();
|
|
}
|
|
|
|
ExplodingWall::ExplodingWall()
|
|
{
|
|
if (LoadingSavegame) {
|
|
return;
|
|
}
|
|
|
|
health = 60;
|
|
max_health = health;
|
|
on_ground = false;
|
|
|
|
state = 0;
|
|
angle_speed = (spawnflags & RANDOMANGLES) ? 600 : 100;
|
|
land_radius = 16;
|
|
dmg = 10;
|
|
explosions = 1;
|
|
|
|
base_velocity = Vector(0, 0, 280);
|
|
random_velocity = Vector(140, 140, 140);
|
|
|
|
orig_mins = mins;
|
|
orig_maxs = maxs;
|
|
|
|
respondto = spawnflags ^ TRIGGER_PLAYERS;
|
|
|
|
PostEvent(EV_ExplodingWall_Setup, EV_POSTSPAWN);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED trigger_teleport (0.5 0.5 0.5) ? VISIBLE x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES NO_EFFECTS
|
|
|
|
Touching this entity will teleport players to the targeted object.
|
|
|
|
"key" The item needed to activate this. (default nothing)
|
|
|
|
"teleportthread" The thread that is run when the player is teleported
|
|
|
|
If NOT_PLAYERS is set, the teleporter does not teleport players
|
|
If NOT_MONSTERS is set, the teleporter does not teleport monsters
|
|
If NOT_PROJECTILES is set, the teleporter does not teleport projectiles (rockets, grenades, etc.)
|
|
If NO_EFFECTS is set, the special effect will not happen and the teleport will be instant
|
|
|
|
******************************************************************************/
|
|
|
|
#define NO_EFFECTS (1 << 5)
|
|
|
|
Event EV_Teleporter_Teleport
|
|
(
|
|
"teleport",
|
|
EV_CODEONLY,
|
|
"e",
|
|
"entity",
|
|
"Teleports the entity to destination.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_Teleporter_StopTeleport
|
|
(
|
|
"stopteleport",
|
|
EV_CODEONLY,
|
|
"e",
|
|
"entity",
|
|
"Releases the entity at the end of the teleport.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_Teleporter_SetThread
|
|
(
|
|
"teleportthread",
|
|
EV_CODEONLY,
|
|
"s",
|
|
"thread_name",
|
|
"Sets the thread to run when the player is teleported.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Trigger, Teleporter, "trigger_teleport") {
|
|
{&EV_Trigger_Effect, &Teleporter::StartTeleport},
|
|
{&EV_Teleporter_Teleport, &Teleporter::Teleport },
|
|
{&EV_Teleporter_StopTeleport, &Teleporter::StopTeleport },
|
|
{&EV_Teleporter_SetThread, &Teleporter::SetThread },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
void Teleporter::SetThread(Event *ev)
|
|
{
|
|
teleport_label.SetThread(ev->GetValue(1));
|
|
}
|
|
|
|
void Teleporter::StartTeleport(Event *ev)
|
|
{
|
|
Animate *fx;
|
|
Entity *other;
|
|
Event *event;
|
|
qboolean is_sentient;
|
|
Vector new_position;
|
|
|
|
if (in_use) {
|
|
return;
|
|
}
|
|
|
|
in_use = true;
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
if (!other) {
|
|
return;
|
|
}
|
|
|
|
if (spawnflags & NO_EFFECTS) {
|
|
event = new Event(EV_Teleporter_Teleport);
|
|
event->AddEntity(other);
|
|
ProcessEvent(event);
|
|
return;
|
|
}
|
|
|
|
if (other->isSubclassOf(Sentient)) {
|
|
is_sentient = true;
|
|
} else {
|
|
is_sentient = false;
|
|
}
|
|
|
|
if (is_sentient) {
|
|
new_position = origin;
|
|
new_position.z += mins.z;
|
|
other->setOrigin(new_position);
|
|
other->NoLerpThisFrame();
|
|
}
|
|
|
|
// Create the teleport special effect
|
|
|
|
fx = new Animate;
|
|
fx->setOrigin(other->origin);
|
|
|
|
fx->NewAnim("idle", EV_Remove);
|
|
|
|
if (is_sentient) {
|
|
fx->setModel("fx_bigteleport.tik");
|
|
//fx->Sound( "sound/environment/electric/singles/dimming.wav" );
|
|
} else {
|
|
fx->setModel("fx_teleport2.tik");
|
|
}
|
|
|
|
if (is_sentient) {
|
|
// Freeze the entity that went into the teleporter
|
|
|
|
other->flags |= FL_IMMOBILE;
|
|
other->takedamage = DAMAGE_NO;
|
|
}
|
|
|
|
// Make the entity teleport
|
|
|
|
event = new Event(EV_Teleporter_Teleport);
|
|
event->AddEntity(other);
|
|
|
|
if (is_sentient) {
|
|
PostEvent(event, 4);
|
|
} else {
|
|
PostEvent(event, 0);
|
|
}
|
|
|
|
if (is_sentient) {
|
|
other->PostEvent(EV_CODEONLY, 2);
|
|
|
|
/* if ( !( spawnflags & NO_EFFECTS ) )
|
|
{
|
|
event = new Event( EV_Sound );
|
|
event->AddString( "snd_teleport" );
|
|
other->PostEvent( event, 2 );
|
|
} */
|
|
}
|
|
}
|
|
|
|
void Teleporter::Teleport(Event *ev)
|
|
{
|
|
Entity *dest;
|
|
int i;
|
|
Entity *other;
|
|
Vector mid;
|
|
Animate *fx;
|
|
Event *event;
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
if (!other || (other == world)) {
|
|
return;
|
|
}
|
|
|
|
dest = (Entity *)G_FindTarget(NULL, Target());
|
|
if (!dest) {
|
|
warning("Teleport", "Couldn't find destination\n");
|
|
return;
|
|
}
|
|
|
|
assert(dest);
|
|
|
|
// unlink to make sure it can't possibly interfere with KillBox
|
|
other->unlink();
|
|
|
|
if (other->isSubclassOf(Sentient)) {
|
|
other->origin = dest->origin + Vector(0, 0, 1);
|
|
other->velocity = vec_zero;
|
|
} else {
|
|
mid = (absmax - absmin) * 0.5;
|
|
other->origin = dest->origin + Vector(0, 0, 1);
|
|
other->origin += mid;
|
|
}
|
|
|
|
// set angles
|
|
other->setAngles(dest->angles);
|
|
|
|
if (other->client) {
|
|
client = other->client;
|
|
|
|
// clear the velocity and hold them in place briefly
|
|
client->ps.pm_time = 100;
|
|
client->ps.pm_flags |= PMF_RESPAWNED;
|
|
|
|
// cut the camera on the client
|
|
((Player *)other)->CameraCut();
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
client->ps.delta_angles[i] = ANGLE2SHORT(dest->angles[i] - client->cmd_angles[i]);
|
|
}
|
|
|
|
VectorCopy(angles, client->ps.viewangles);
|
|
}
|
|
|
|
if (dest->isSubclassOf(TeleporterDestination)) {
|
|
float len;
|
|
|
|
len = other->velocity.length();
|
|
//
|
|
// give them a bit of a push
|
|
//
|
|
if (len < 400) {
|
|
len = 400;
|
|
}
|
|
other->velocity = ((TeleporterDestination *)dest)->movedir * len;
|
|
}
|
|
|
|
// kill anything at the destination
|
|
KillBox(other);
|
|
|
|
other->setOrigin(other->origin);
|
|
other->origin.copyTo(other->edict->s.origin2);
|
|
|
|
// Run the teleport thread if set
|
|
|
|
if (teleport_label.IsSet() && other->isSubclassOf(Player)) {
|
|
teleport_label.Execute(this);
|
|
}
|
|
|
|
// Skip effects if no_effects set
|
|
|
|
if (spawnflags & NO_EFFECTS) {
|
|
Event *event = new Event(EV_Teleporter_StopTeleport);
|
|
event->AddEntity(other);
|
|
ProcessEvent(event);
|
|
return;
|
|
}
|
|
|
|
// Spawn in effect
|
|
|
|
fx = new Animate;
|
|
|
|
fx->setOrigin(other->origin);
|
|
|
|
fx->NewAnim("idle", EV_Remove);
|
|
|
|
if (other->isSubclassOf(Sentient)) {
|
|
fx->setModel("fx_bigteleport.tik");
|
|
} else {
|
|
fx->setModel("fx_teleport2.tik");
|
|
}
|
|
|
|
event = new Event(EV_Teleporter_StopTeleport);
|
|
event->AddEntity(other);
|
|
|
|
if (other->isSubclassOf(Sentient)) {
|
|
PostEvent(event, 1.75);
|
|
} else {
|
|
PostEvent(event, FRAMETIME);
|
|
}
|
|
}
|
|
|
|
void Teleporter::StopTeleport(Event *ev)
|
|
{
|
|
Entity *other;
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
if (other->isSubclassOf(Sentient)) {
|
|
other->flags &= ~FL_IMMOBILE;
|
|
other->takedamage = DAMAGE_AIM;
|
|
other->showModel();
|
|
}
|
|
|
|
//if ( !( spawnflags & NO_EFFECTS ) )
|
|
// other->Sound( "snd_teleport" );
|
|
|
|
in_use = false;
|
|
}
|
|
|
|
Teleporter::Teleporter()
|
|
{
|
|
AddWaitTill(STRING_TELEPORT);
|
|
|
|
if (LoadingSavegame) {
|
|
return;
|
|
}
|
|
|
|
if (spawnflags & 1) {
|
|
PostEvent(EV_Show, EV_POSTSPAWN);
|
|
}
|
|
|
|
//respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES );
|
|
respondto = spawnflags ^ (TRIGGER_PLAYERS);
|
|
|
|
in_use = false;
|
|
|
|
// Cache all needed stuff
|
|
|
|
if (!(spawnflags & NO_EFFECTS)) {
|
|
CacheResource("models/fx_bigteleport.tik");
|
|
//CacheResource( "sound/environment/electric/singles/dimming.wav", this );
|
|
CacheResource("fx_teleport2.tik");
|
|
//CacheResource( "snd_teleport", this );
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_teleportdest (0 0.25 0.5) (-32 -32 0) (32 32 8)
|
|
|
|
Point trigger_teleport at these.
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, TeleporterDestination, "func_teleportdest") {
|
|
{&EV_SetAngle, &TeleporterDestination::SetMoveDir},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
TeleporterDestination::TeleporterDestination()
|
|
{
|
|
movedir = G_GetMovedir(0);
|
|
}
|
|
|
|
void TeleporterDestination::SetMoveDir(Event *ev)
|
|
{
|
|
float angle;
|
|
|
|
angle = ev->GetFloat(1);
|
|
movedir = G_GetMovedir(angle);
|
|
setAngles(movedir.toAngles());
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_useanim (0 0.5 0) ? VISIBLE TOUCHABLE CONTINUOUS
|
|
|
|
This object allows you to place the player into a specific animation for the
|
|
purposes of using an object within the world->
|
|
|
|
This object should point at a func_useanimdest which contains specific
|
|
information about how the player is supposed to be posed.
|
|
|
|
"count" - how many times this should trigger (default -1, infinite)
|
|
"thread" - thread to fire when used
|
|
"triggertarget" - what to trigger when used.
|
|
"delay" - how long it takes to be re-triggered ( default 3 seconds )
|
|
"key" - item needed to activate this
|
|
|
|
VISIBLE - if this is checked the trigger itself will be visible
|
|
TOUCHABLE - if this is set we can activate the trigger by standing in it.
|
|
CONTINUOUS - if this is checked the thing will re-trigger continously, otherwise
|
|
it waits until the player has left the trigger field.
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_UseAnim_Reset
|
|
(
|
|
"_reset",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Reset's the Use Anim after it has no longer been touched.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseAnim_Thread
|
|
(
|
|
"setthread",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"label",
|
|
"Sets which thread to use when this UseAnim is triggered.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseAnim_Count
|
|
(
|
|
"count", EV_DEFAULT,
|
|
"i",
|
|
"newCount",
|
|
"Sets how many times the UseAnim can be triggered.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseAnim_TriggerTarget
|
|
(
|
|
"triggertarget",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"targetname",
|
|
"Sets what should be triggered, when this UseAnim is triggered.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseAnim_SetAnim
|
|
(
|
|
"anim",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"animName",
|
|
"set the animation to use for player.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseAnim_SetKey
|
|
(
|
|
"key",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"keyName",
|
|
"set the key needed to make this UseAnim function.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseAnim_SetState
|
|
(
|
|
"state",
|
|
EV_CHEAT,
|
|
"s",
|
|
"stateName",
|
|
"set the state to use for the player.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseAnim_SetCamera(
|
|
"camera",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"cameraPosition",
|
|
"set the camera to use when in this animation.\n"
|
|
"topdown, behind, front, side, behind_fixed, side_left, side_right",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseAnim_SetNumLoops
|
|
(
|
|
"num_loops",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"loopCount",
|
|
"set the number of times to loop an animation per use.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseAnim_SetDelay(
|
|
"delay",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"delayTime",
|
|
"how long it takes for the UseAnim to be retriggered once you leave it.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Entity, UseAnim, "func_useanim") {
|
|
{&EV_Use, NULL },
|
|
{&EV_Touch, &UseAnim::Touched },
|
|
{&EV_UseAnim_Reset, &UseAnim::Reset },
|
|
{&EV_UseAnim_Thread, &UseAnim::SetThread },
|
|
{&EV_UseAnim_TriggerTarget, &UseAnim::SetTriggerTarget},
|
|
{&EV_UseAnim_Count, &UseAnim::SetCount },
|
|
{&EV_UseAnim_SetAnim, &UseAnim::SetAnim },
|
|
{&EV_UseAnim_SetState, &UseAnim::SetState },
|
|
{&EV_UseAnim_SetKey, &UseAnim::SetKey },
|
|
{&EV_UseAnim_SetNumLoops, &UseAnim::SetNumLoops },
|
|
{&EV_UseAnim_SetCamera, &UseAnim::SetCamera },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
UseAnim::UseAnim()
|
|
{
|
|
AddWaitTill(STRING_TRIGGER);
|
|
if (LoadingSavegame) {
|
|
return;
|
|
}
|
|
|
|
setMoveType(MOVETYPE_NONE);
|
|
|
|
anim = "stand_use";
|
|
num_loops = 1;
|
|
hideModel();
|
|
|
|
//
|
|
// make it not solid unless we want it touchable
|
|
//
|
|
if (!(spawnflags & 2)) {
|
|
setSolidType(SOLID_BBOX);
|
|
setContents(CONTENTS_BODY);
|
|
} else {
|
|
setSolidType(SOLID_TRIGGER);
|
|
edict->r.svFlags |= SVF_NOCLIENT;
|
|
}
|
|
|
|
// by default this can activated infinitely
|
|
count = -1;
|
|
// clear out the triggertarget
|
|
triggertarget = "";
|
|
// clear out the thread
|
|
thread.Set("");
|
|
// set the default delay
|
|
delay = 3;
|
|
// initialize the last time the door was triggered
|
|
last_active_time = -delay;
|
|
// initially its not active
|
|
active = 0;
|
|
// set the default camera to be side view
|
|
camera = "behind";
|
|
//
|
|
// only make it visible if so desired
|
|
//
|
|
if (spawnflags & 1) {
|
|
PostEvent(EV_Show, EV_POSTSPAWN);
|
|
}
|
|
}
|
|
|
|
void UseAnim::Touched(Event *ev)
|
|
{
|
|
Entity *other;
|
|
|
|
if (active && (!(spawnflags & 4))) {
|
|
CancelEventsOfType(EV_UseAnim_Reset);
|
|
PostEvent(EV_UseAnim_Reset, 0.25f);
|
|
return;
|
|
}
|
|
|
|
// don't retrigger to soon
|
|
if (level.time < last_active_time) {
|
|
return;
|
|
}
|
|
|
|
other = ev->GetEntity(1);
|
|
if (other->isSubclassOf(Player)) {
|
|
((Player *)other)->TouchedUseAnim(this);
|
|
}
|
|
}
|
|
|
|
bool UseAnim::canBeUsed(Entity *activator)
|
|
{
|
|
Entity *dest;
|
|
|
|
// if this is no longer usable, return false
|
|
if (!count) {
|
|
return false;
|
|
}
|
|
|
|
// don't retrigger to soon
|
|
if (level.time < last_active_time) {
|
|
return false;
|
|
}
|
|
|
|
if (key.length()) {
|
|
if (!activator->isSubclassOf(Sentient)) {
|
|
return false;
|
|
}
|
|
if (!(((Sentient *)activator)->HasItem(key.c_str()))) {
|
|
qboolean setModel;
|
|
Item *item;
|
|
ClassDef *cls;
|
|
str dialog;
|
|
|
|
cls = FindClass(key.c_str(), &setModel);
|
|
if (!cls || !checkInheritance("Item", cls->classname)) {
|
|
gi.DPrintf("No item named '%s'\n", key.c_str());
|
|
return true;
|
|
}
|
|
item = (Item *)cls->newInstance();
|
|
if (setModel) {
|
|
item->setModel(key.c_str());
|
|
}
|
|
item->CancelEventsOfType(EV_Item_DropToFloor);
|
|
item->CancelEventsOfType(EV_Remove);
|
|
item->ProcessPendingEvents();
|
|
dialog = item->GetDialogNeeded();
|
|
if (dialog.length() > 0) {
|
|
activator->Sound(dialog);
|
|
} else {
|
|
gi.centerprintf(activator->edict, "You need the %s", item->getName().c_str());
|
|
}
|
|
delete item;
|
|
|
|
// don't retrigger for 5 seconds
|
|
last_active_time = level.time + 5;
|
|
return false;
|
|
} else {
|
|
return qtrue;
|
|
}
|
|
}
|
|
|
|
if (isSubclassOf(TouchAnim)) {
|
|
return true;
|
|
}
|
|
|
|
dest = (Entity *)G_FindTarget(NULL, Target());
|
|
if (!dest || !dest->isSubclassOf(UseAnimDestination)) {
|
|
warning("UseAnim", "Couldn't find destination\n");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UseAnim::GetInformation(
|
|
Entity *activator, Vector *org, Vector *angles, str *animation, int *loopcount, str *state, str *camera
|
|
)
|
|
{
|
|
Entity *dest;
|
|
UseAnimDestination *uadest;
|
|
|
|
// if this is no longer usable, return false
|
|
if (!count) {
|
|
return false;
|
|
}
|
|
|
|
dest = (Entity *)G_FindTarget(NULL, Target());
|
|
|
|
if (!dest || !dest->isSubclassOf(UseAnimDestination)) {
|
|
// grab the information from this entity instead
|
|
// set the destination origin
|
|
*org = origin;
|
|
// set the destination angles
|
|
*angles = this->angles;
|
|
// set the desination animation
|
|
*animation = anim;
|
|
// set the number of loops
|
|
*loopcount = num_loops;
|
|
// get the state if necessary
|
|
*state = this->state;
|
|
// set the camera
|
|
*camera = this->camera;
|
|
} else {
|
|
uadest = (UseAnimDestination *)dest;
|
|
|
|
// set the destination origin
|
|
*org = uadest->origin;
|
|
// set the destination angles
|
|
*angles = uadest->angles;
|
|
// set the desination animation
|
|
*animation = uadest->GetAnim();
|
|
// set the number of loops
|
|
*loopcount = uadest->GetNumLoops();
|
|
// get the state if necessary
|
|
*state = uadest->GetState();
|
|
// set the camera
|
|
*camera = this->camera;
|
|
}
|
|
|
|
// make this guy active
|
|
active = qtrue;
|
|
|
|
// if this is a TouchAnim see if it is linked to another TouchAnim
|
|
if (isSubclassOf(TouchAnim)) {
|
|
dest = NULL;
|
|
do {
|
|
dest = (Entity *)G_FindTarget(dest, Target());
|
|
if (dest) {
|
|
if (dest->isSubclassOf(UseAnim)) {
|
|
// make our linked UseAnim's active as well
|
|
((UseAnim *)dest)->active = qtrue;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
}
|
|
//
|
|
// decrement the use
|
|
//
|
|
if (count > 0) {
|
|
count--;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UseAnim::TriggerTargets(Entity *activator)
|
|
{
|
|
//
|
|
// fire off our trigger target if appropriate
|
|
//
|
|
if (triggertarget.length()) {
|
|
Event *event;
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do {
|
|
ent = (Entity *)G_FindTarget(ent, triggertarget.c_str());
|
|
if (!ent) {
|
|
break;
|
|
}
|
|
event = new Event(EV_Activate);
|
|
event->AddEntity(activator);
|
|
ent->PostEvent(event, 0);
|
|
} while (1);
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if (thread.IsSet()) {
|
|
thread.Execute(this);
|
|
}
|
|
}
|
|
|
|
void UseAnim::Reset(Event *ev)
|
|
{
|
|
//
|
|
// find out if our triggertarget is of type door and only reset if the door is closed
|
|
//
|
|
if (triggertarget.length()) {
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do {
|
|
ent = (Entity *)G_FindTarget(ent, triggertarget.c_str());
|
|
if (!ent) {
|
|
break;
|
|
}
|
|
if (ent->isSubclassOf(Door)) {
|
|
if (!((Door *)ent)->isCompletelyClosed()) {
|
|
CancelEventsOfType(EV_UseAnim_Reset);
|
|
PostEvent(EV_UseAnim_Reset, 0.25f);
|
|
//
|
|
// wait for a little bit
|
|
//
|
|
return;
|
|
}
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
active = qfalse;
|
|
last_active_time = level.time + delay;
|
|
|
|
// if this is a TouchAnim see if it is linked to another TouchAnim
|
|
if (isSubclassOf(TouchAnim)) {
|
|
Entity *dest;
|
|
|
|
dest = NULL;
|
|
do {
|
|
dest = (Entity *)G_FindTarget(dest, Target());
|
|
if (dest) {
|
|
if (dest->isSubclassOf(UseAnim)) {
|
|
// make our linked UseAnim's reset as well
|
|
((UseAnim *)dest)->active = qfalse;
|
|
((UseAnim *)dest)->last_active_time = level.time + delay;
|
|
}
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
}
|
|
}
|
|
|
|
void UseAnim::SetThread(Event *ev)
|
|
{
|
|
thread.SetThread(ev->GetValue(1));
|
|
}
|
|
|
|
void UseAnim::SetDelay(Event *ev)
|
|
{
|
|
delay = ev->GetFloat(1);
|
|
}
|
|
|
|
void UseAnim::SetTriggerTarget(Event *ev)
|
|
{
|
|
triggertarget = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnim::SetCount(Event *ev)
|
|
{
|
|
count = ev->GetInteger(1);
|
|
}
|
|
|
|
void UseAnim::SetAnim(Event *ev)
|
|
{
|
|
anim = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnim::SetState(Event *ev)
|
|
{
|
|
state = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnim::SetKey(Event *ev)
|
|
{
|
|
key = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnim::SetCamera(Event *ev)
|
|
{
|
|
camera = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnim::SetNumLoops(Event *ev)
|
|
{
|
|
num_loops = ev->GetInteger(1);
|
|
}
|
|
|
|
CLASS_DECLARATION(UseAnim, TouchAnim, "func_touchanim") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
TouchAnim::TouchAnim()
|
|
{
|
|
if (LoadingSavegame) {
|
|
return;
|
|
}
|
|
|
|
spawnflags |= 2;
|
|
|
|
if (spawnflags & 8) {
|
|
setSize(Vector("-32 -32 0"), Vector("32 32 96"));
|
|
} else {
|
|
setSize(Vector("-16 -16 0"), Vector("16 16 96"));
|
|
}
|
|
|
|
setSolidType(SOLID_TRIGGER);
|
|
edict->r.svFlags |= SVF_NOCLIENT;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/* func_useanimdest (0 0.25 0.5) (0 0 0) (0 0 0)
|
|
|
|
Point func_useanim's at these.
|
|
|
|
The player will be lerped to this position and this orientation
|
|
and placed into the specified animation
|
|
|
|
"anim" specifies the animation that the player should be in.
|
|
"state" instead of an animation, sets a state the player should go into
|
|
"camera" camera position to use when player is in animation
|
|
"num_loops" number of animation loops to play.
|
|
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, UseAnimDestination, "func_useanimdest") {
|
|
{&EV_UseAnim_SetAnim, &UseAnimDestination::SetAnim },
|
|
{&EV_UseAnim_SetState, &UseAnimDestination::SetState },
|
|
{&EV_UseAnim_SetNumLoops, &UseAnimDestination::SetNumLoops},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
UseAnimDestination::UseAnimDestination()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
//
|
|
// default animation to use
|
|
//
|
|
anim = "stand_use";
|
|
num_loops = 1;
|
|
setSolidType(SOLID_NOT);
|
|
hideModel();
|
|
}
|
|
|
|
void UseAnimDestination::SetAnim(Event *ev)
|
|
{
|
|
anim = ev->GetString(1);
|
|
}
|
|
|
|
void UseAnimDestination::SetState(Event *ev)
|
|
{
|
|
state = ev->GetString(1);
|
|
}
|
|
|
|
str UseAnimDestination::GetAnim(void)
|
|
{
|
|
return anim;
|
|
}
|
|
|
|
str UseAnimDestination::GetState(void)
|
|
{
|
|
return state;
|
|
}
|
|
|
|
void UseAnimDestination::SetNumLoops(Event *ev)
|
|
{
|
|
num_loops = ev->GetInteger(1);
|
|
}
|
|
|
|
int UseAnimDestination::GetNumLoops(void)
|
|
{
|
|
return num_loops;
|
|
}
|
|
|
|
Event EV_UseObject_MoveThread
|
|
(
|
|
"move_thread",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"label",
|
|
"Sets which move thread to use when this UseObject has finshed looping.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_StopThread
|
|
(
|
|
"stop_thread",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"label",
|
|
"Sets which stop thread to use when this UseObject is finished.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_ResetThread
|
|
(
|
|
"reset_thread",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"label",
|
|
"Sets which thread to call when resetting.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_Count
|
|
(
|
|
"count",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"newCount",
|
|
"Sets how many times the UseObject can be triggered.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_Cone
|
|
(
|
|
"cone",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"newCone",
|
|
"Sets the cone in angles of where the Useobject can be used.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_Offset
|
|
(
|
|
"offset",
|
|
EV_DEFAULT,
|
|
"v",
|
|
"newOffset",
|
|
"Sets the offset to use for this UseObject.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_YawOffset
|
|
(
|
|
"yaw_offset",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"newYawOffset",
|
|
"Sets the yaw offset to use for this UseObject.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_State
|
|
(
|
|
"state",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newState",
|
|
"Sets the state to use for this UseObject.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_StateBackwards
|
|
(
|
|
"state_backwards",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newState",
|
|
"Sets the backward state to use for this UseObject.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_TriggerTarget
|
|
(
|
|
"triggertarget",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"targetname",
|
|
"Sets what should be triggered, when this UseObject is triggered.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_ResetTime
|
|
(
|
|
"reset_time",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"newResetTime",
|
|
"Sets the time it takes for the UseObject to reset itself.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_DamageType
|
|
(
|
|
"damage_type",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newDamageType",
|
|
"Sets what kind of damage is needed to activate the trigger.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_Reset
|
|
(
|
|
"_useobject_reset",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Resets the useobject to the start state after a certain amount of time.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_Resetting
|
|
(
|
|
"_useobject_resetting",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Intermediate function for useobject reset.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_DamageTriggered
|
|
(
|
|
"_useobject_damagetriggered",
|
|
EV_DEFAULT,
|
|
"e",
|
|
"activator",
|
|
"Intermediate function for when the useobject was triggered by damage.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_Activate
|
|
(
|
|
"activate",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Allow the useobject to be used.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_Deactivate
|
|
(
|
|
"deactivate",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Do not allow the useobject to be used.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_UseObject_UseMaterial
|
|
(
|
|
"usematerial",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"nameOfUseMaterial",
|
|
"the name of the material that glows when active.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_UseObject_SetActiveState
|
|
(
|
|
"_setactivestate",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"event that sets up the proper skin for the useobject.",
|
|
EV_NORMAL
|
|
|
|
);
|
|
|
|
#define MULTI_STATE (1 << 0)
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_useobject (0 0.5 0) ? MULTI-STATE
|
|
|
|
Allows you to setup a special object that places the player into a specific state
|
|
sequence. Primarily used for levers and cranks.
|
|
|
|
Object starts out in the "start" animation, when used the following occurs:
|
|
|
|
It is determined whether or not the player is in the right position to activate
|
|
the object, if it is, the player is moved to the exact offset and angle specified
|
|
by "offset" and "yaw_offset". The right position is determined by a dot product
|
|
with "offset" and "yaw_offset". The "cone" parameter controls the cone in which the
|
|
object can be triggered. Once the player is in the right position, the player is placed
|
|
into "state" and the "move" animation is played. Once the player animation ends, the
|
|
"move_thread" will be called. If the use button is continued to be held down and count
|
|
is not finite, the animation will be continued to be played until the use key is held
|
|
down. Once the use key is let go, the "stop" animation will be played on the lever and
|
|
the "stop_thread" will be called.
|
|
|
|
"activate" - turns the useobject on
|
|
"deactivate" - turns the useobject off
|
|
"offset" - vector offset of where the player should stand
|
|
"state" - state to go into when used
|
|
"state_backwards" - what state to use when reversing the object
|
|
"yaw_offset" - what direction the player should be facing when using the object
|
|
"cone" - the cone in which the object can be used
|
|
"count" - how many times this should trigger (default -1, infinite)
|
|
"move_thread" - thread that is fired when the object has cycled one animation
|
|
"stop_thread" - thread that is fired when the object has finished animating
|
|
"reset_thread" - thread that is fired when the object is resetting itself
|
|
"reset_time" - the time it takes for the object to reset, (default 0, it doesn't)
|
|
"triggertarget" - target to trigger when finished animating, if reset_time is set, target
|
|
will be fired again when resetting
|
|
"damage_type" - if set, can be triggered by using a weapon to activate it. If set to "all",
|
|
any damage will activate it.
|
|
|
|
MULTI-STATE - the object has two different states and must be used each time to set the state
|
|
when multi state is active, the reset_thread is called instead of stop_thread. All UseObjects
|
|
have two states on and off. When reset_time is set, the object will automatically return to the
|
|
off state after a preset amount of time. When multi-state is set this must be done manually.
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Animate, UseObject, "func_useobject") {
|
|
{&EV_Use, NULL },
|
|
{&EV_UseObject_MoveThread, &UseObject::SetMoveThread },
|
|
{&EV_UseObject_StopThread, &UseObject::SetStopThread },
|
|
{&EV_UseObject_ResetThread, &UseObject::SetResetThread },
|
|
{&EV_UseObject_TriggerTarget, &UseObject::SetTriggerTarget },
|
|
{&EV_UseObject_Offset, &UseObject::SetOffset },
|
|
{&EV_UseObject_YawOffset, &UseObject::SetYawOffset },
|
|
{&EV_UseObject_Count, &UseObject::SetCount },
|
|
{&EV_UseObject_Cone, &UseObject::SetCone },
|
|
{&EV_UseObject_State, &UseObject::SetState },
|
|
{&EV_UseObject_StateBackwards, &UseObject::SetBackwardsState},
|
|
{&EV_UseObject_ResetTime, &UseObject::SetResetTime },
|
|
{&EV_UseObject_Reset, &UseObject::Reset },
|
|
{&EV_UseObject_DamageType, &UseObject::DamageType },
|
|
{&EV_UseObject_Resetting, &UseObject::Resetting },
|
|
{&EV_UseObject_DamageTriggered, &UseObject::DamageTriggered },
|
|
{&EV_Damage, &UseObject::DamageFunc },
|
|
{&EV_UseObject_Activate, &UseObject::ActivateEvent },
|
|
{&EV_UseObject_Deactivate, &UseObject::DeactivateEvent },
|
|
{&EV_UseObject_UseMaterial, &UseObject::UseMaterialEvent },
|
|
{&EV_UseObject_SetActiveState, &UseObject::SetActiveState },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
UseObject::UseObject()
|
|
{
|
|
Event *e;
|
|
|
|
AddWaitTill(STRING_RESET);
|
|
AddWaitTill(STRING_MOVE);
|
|
AddWaitTill(STRING_STOP);
|
|
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
|
|
setMoveType(MOVETYPE_NONE);
|
|
setSolidType(SOLID_BBOX);
|
|
setContents(CONTENTS_BODY);
|
|
|
|
// make sure the bounds get rotated with the object
|
|
flags |= FL_ROTATEDBOUNDS;
|
|
// by default this can activated infinitely
|
|
count = -1;
|
|
// clear out the triggertarget
|
|
triggertarget = "";
|
|
// clear out the move thread
|
|
move_thread.Set("");
|
|
// clear out the stop thread
|
|
stop_thread.Set("");
|
|
// clear out the reset thread
|
|
reset_thread.Set("");
|
|
// clear out the damage type, by default useobjects do not respond to damage
|
|
damage_type = MOD_NONE;
|
|
// turn on damage taking ability
|
|
takedamage = DAMAGE_YES;
|
|
// give it some health
|
|
health = 100;
|
|
// set the default yaw offset
|
|
yaw_offset = 0;
|
|
// set the cone
|
|
cone = cos(DEG2RAD(90));
|
|
// set the state
|
|
state = "";
|
|
// set the backwards state
|
|
state_backwards = "";
|
|
// clear out the reset_time
|
|
reset_time = 0;
|
|
// clear out the object state
|
|
objectState = 0;
|
|
// the useobject is active by default
|
|
active = qtrue;
|
|
|
|
// start off in the start animation
|
|
e = new Event(EV_SetAnim);
|
|
e->AddString("start");
|
|
PostEvent(e, 0);
|
|
PostEvent(EV_Show, 0);
|
|
// setup our skins once we are spawned
|
|
PostEvent(EV_UseObject_SetActiveState, 0);
|
|
}
|
|
|
|
void UseObject::SetActiveState(Event *ev)
|
|
{
|
|
if (!useMaterial.length()) {
|
|
return;
|
|
}
|
|
|
|
if (active && count) {
|
|
SurfaceCommand(useMaterial.c_str(), "+skin1");
|
|
} else {
|
|
SurfaceCommand(useMaterial.c_str(), "-skin1");
|
|
}
|
|
if (objectState) {
|
|
SurfaceCommand(useMaterial.c_str(), "+skin2");
|
|
} else {
|
|
SurfaceCommand(useMaterial.c_str(), "-skin2");
|
|
}
|
|
}
|
|
|
|
void UseObject::SetMoveThread(Event *ev)
|
|
{
|
|
move_thread.SetThread(ev->GetValue(1));
|
|
}
|
|
|
|
void UseObject::SetStopThread(Event *ev)
|
|
{
|
|
stop_thread.SetThread(ev->GetValue(1));
|
|
}
|
|
|
|
void UseObject::SetResetThread(Event *ev)
|
|
{
|
|
reset_thread.SetThread(ev->GetValue(1));
|
|
}
|
|
|
|
void UseObject::ActivateEvent(Event *ev)
|
|
{
|
|
active = qtrue;
|
|
PostEvent(EV_UseObject_SetActiveState, 0);
|
|
}
|
|
|
|
void UseObject::DeactivateEvent(Event *ev)
|
|
{
|
|
active = qfalse;
|
|
PostEvent(EV_UseObject_SetActiveState, 0);
|
|
}
|
|
|
|
void UseObject::SetTriggerTarget(Event *ev)
|
|
{
|
|
triggertarget = ev->GetString(1);
|
|
}
|
|
|
|
void UseObject::SetOffset(Event *ev)
|
|
{
|
|
offset = ev->GetVector(1);
|
|
}
|
|
|
|
void UseObject::SetYawOffset(Event *ev)
|
|
{
|
|
yaw_offset = ev->GetFloat(1);
|
|
}
|
|
|
|
void UseObject::SetCount(Event *ev)
|
|
{
|
|
count = ev->GetInteger(1);
|
|
}
|
|
|
|
void UseObject::SetCone(Event *ev)
|
|
{
|
|
cone = cos(DEG2RAD(ev->GetFloat(1)));
|
|
}
|
|
|
|
void UseObject::SetState(Event *ev)
|
|
{
|
|
state = ev->GetString(1);
|
|
}
|
|
|
|
void UseObject::SetBackwardsState(Event *ev)
|
|
{
|
|
state_backwards = ev->GetString(1);
|
|
}
|
|
|
|
void UseObject::UseMaterialEvent(Event *ev)
|
|
{
|
|
useMaterial = ev->GetString(1);
|
|
}
|
|
|
|
void UseObject::SetResetTime(Event *ev)
|
|
{
|
|
reset_time = ev->GetFloat(1);
|
|
}
|
|
|
|
void UseObject::Reset(Event *ev)
|
|
{
|
|
NewAnim("move_backward", EV_UseObject_Resetting);
|
|
}
|
|
|
|
void UseObject::Resetting(Event *ev)
|
|
{
|
|
SetActiveState(NULL);
|
|
NewAnim("start");
|
|
|
|
// reset the count
|
|
count = 1;
|
|
|
|
//
|
|
// fire off our trigger target if appropriate
|
|
//
|
|
if (triggertarget.length()) {
|
|
Event *event;
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do {
|
|
ent = (Entity *)G_FindTarget(ent, triggertarget.c_str());
|
|
if (!ent) {
|
|
break;
|
|
}
|
|
event = new Event(EV_Activate);
|
|
event->AddEntity(this);
|
|
ent->PostEvent(event, 0);
|
|
} while (1);
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if (reset_thread.IsSet()) {
|
|
reset_thread.Execute(this);
|
|
}
|
|
}
|
|
|
|
bool UseObject::canBeUsed(Vector org, Vector dir)
|
|
{
|
|
float dot;
|
|
Vector forward;
|
|
Vector diff;
|
|
Vector ang;
|
|
|
|
// see if it is active
|
|
if (!active) {
|
|
return false;
|
|
}
|
|
|
|
// if this is no longer usable, return false
|
|
if (!count) {
|
|
return false;
|
|
}
|
|
|
|
// convert our yawoffset to a vector
|
|
ang = vec_zero;
|
|
ang[YAW] = angles[YAW] + yaw_offset;
|
|
ang.AngleVectors(&forward);
|
|
dot = forward * dir;
|
|
if (dot < cone) {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
// convert our offset to a vector in worldspace
|
|
forward = getLocalVector( offset );
|
|
forward.normalize();
|
|
diff = org - origin;
|
|
// diff = origin - org;
|
|
diff.normalize();
|
|
dot = forward * dir;
|
|
if ( dot < cone )
|
|
{
|
|
return false;
|
|
}
|
|
*/
|
|
|
|
return true;
|
|
}
|
|
|
|
void UseObject::DamageFunc(Event *ev)
|
|
{
|
|
Event *e;
|
|
Entity *attacker;
|
|
int mod;
|
|
|
|
// if this is no longer usable, return false
|
|
if (!count) {
|
|
return;
|
|
}
|
|
|
|
// what kind of damage hit us
|
|
mod = ev->GetInteger(9);
|
|
|
|
// if we don't respond to any kind of damage, and our damage types do not match, return
|
|
if (!MOD_matches(mod, damage_type)) {
|
|
return;
|
|
}
|
|
|
|
// get the attacker
|
|
attacker = ev->GetEntity(1);
|
|
|
|
//
|
|
// decrement the use
|
|
//
|
|
if (count > 0) {
|
|
count--;
|
|
}
|
|
// setup our damage triggered event
|
|
e = new Event(EV_UseObject_DamageTriggered);
|
|
// add our attacker
|
|
e->AddEntity(attacker);
|
|
// start up the object with our special event
|
|
Start(e);
|
|
}
|
|
|
|
void UseObject::DamageTriggered(Event *ev)
|
|
{
|
|
// grab the attacker from our event
|
|
Stop(ev->GetEntity(1));
|
|
}
|
|
|
|
void UseObject::Setup(Entity *activator, Vector *org, Vector *ang, str *newstate)
|
|
{
|
|
if ((spawnflags & MULTI_STATE) && objectState) {
|
|
*newstate = state_backwards;
|
|
} else {
|
|
*newstate = state;
|
|
}
|
|
|
|
// convert our offset to a vector in worldspace
|
|
MatrixTransformVector(offset, orientation, *org);
|
|
*org += origin;
|
|
|
|
*ang = angles;
|
|
ang->y += yaw_offset;
|
|
|
|
//
|
|
// decrement the use
|
|
//
|
|
if (count > 0) {
|
|
count--;
|
|
}
|
|
}
|
|
|
|
void UseObject::Start(Event *ev)
|
|
{
|
|
//
|
|
// fire off the move_thread
|
|
//
|
|
if (move_thread.IsSet()) {
|
|
move_thread.Execute(this);
|
|
}
|
|
|
|
if ((spawnflags & MULTI_STATE) && objectState) {
|
|
NewAnim("move_backward", ev);
|
|
} else {
|
|
NewAnim("move", ev);
|
|
}
|
|
|
|
SetActiveState(NULL);
|
|
}
|
|
|
|
bool UseObject::Loop(void)
|
|
{
|
|
if (!count) {
|
|
return qfalse;
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
void UseObject::Stop(Entity *activator)
|
|
{
|
|
if ((spawnflags & MULTI_STATE) && objectState) {
|
|
NewAnim("start");
|
|
} else {
|
|
NewAnim("stop");
|
|
}
|
|
|
|
//
|
|
// fire off our trigger target if appropriate
|
|
//
|
|
if (triggertarget.length()) {
|
|
Event *event;
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do {
|
|
ent = (Entity *)G_FindTarget(ent, triggertarget.c_str());
|
|
if (!ent) {
|
|
break;
|
|
}
|
|
event = new Event(EV_Activate);
|
|
event->AddEntity(activator);
|
|
ent->PostEvent(event, 0);
|
|
} while (1);
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if ((spawnflags & MULTI_STATE) && objectState) {
|
|
if (reset_thread.IsSet()) {
|
|
reset_thread.Execute(this);
|
|
}
|
|
} else {
|
|
if (stop_thread.IsSet()) {
|
|
stop_thread.Execute(this);
|
|
}
|
|
}
|
|
|
|
// toggle the state
|
|
objectState ^= 1;
|
|
|
|
if (reset_time) {
|
|
count = 0;
|
|
PostEvent(EV_UseObject_Reset, reset_time);
|
|
}
|
|
|
|
SetActiveState(NULL);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_waypoint (0 0.5 0) (-8 -8 -8) (8 8 8)
|
|
|
|
Used as a positioning device for objects
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(SimpleArchivedEntity, Waypoint, "info_waypoint") {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
Waypoint::Waypoint()
|
|
{
|
|
entflags |= ECF_WAYPOINT;
|
|
}
|
|
|
|
CLASS_DECLARATION(SimpleArchivedEntity, TempWaypoint, NULL) {
|
|
{NULL, NULL}
|
|
};
|
|
|
|
TempWaypoint::TempWaypoint()
|
|
{
|
|
entflags |= ECF_TEMPWAYPOINT;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_vehiclepoint (0.15 0.5 0.25) (-8 -8 -8) (8 8 8) START_STOPPING START_SKIDDING STOP_SKIDDING
|
|
|
|
Like info_waypoints, but with spawnflags for vehicles.
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_VehiclePoint_SetSpawnFlags
|
|
(
|
|
"spawnflags",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"spawn_flags",
|
|
"Sets the spawn flags."
|
|
);
|
|
|
|
CLASS_DECLARATION(Waypoint, VehiclePoint, "info_vehiclepoint") {
|
|
{&EV_VehiclePoint_SetSpawnFlags, &VehiclePoint::SetSpawnFlags},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
VehiclePoint::VehiclePoint()
|
|
{
|
|
entflags |= ECF_VEHICLEPOINT;
|
|
spawnflags = 0;
|
|
}
|
|
|
|
void VehiclePoint::SetSpawnFlags(Event *ev)
|
|
{
|
|
spawnflags = ev->GetInteger(1);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_monkeybars (0.75 0.75 0.75) ?
|
|
|
|
Monkey bars
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, MonkeyBars, "func_monkeybars") {
|
|
{&EV_SetAngle, &MonkeyBars::SetAngleEvent},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
MonkeyBars::MonkeyBars()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
setMoveType(MOVETYPE_NONE);
|
|
setContents(CONTENTS_SHOOTONLY | MASK_SOLID);
|
|
PostEvent(EV_BecomeSolid, 0);
|
|
dir = 0;
|
|
}
|
|
|
|
void MonkeyBars::SetAngleEvent(Event *ev)
|
|
{
|
|
dir = ev->GetFloat(1);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_horizontalpipe (0.75 0.75 0.75) ?
|
|
|
|
Horizontal pipe that play can crawl upside down on.
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION(Entity, HorizontalPipe, "func_horizontalpipe") {
|
|
{&EV_SetAngle, &HorizontalPipe::SetAngleEvent},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
HorizontalPipe::HorizontalPipe()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
setMoveType(MOVETYPE_NONE);
|
|
setContents(CONTENTS_SHOOTONLY | MASK_SOLID);
|
|
PostEvent(EV_BecomeSolid, 0);
|
|
dir = 0;
|
|
}
|
|
|
|
void HorizontalPipe::SetAngleEvent(Event *ev)
|
|
{
|
|
dir = ev->GetFloat(1);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// TossObject
|
|
/*****************************************************************************/
|
|
|
|
Event EV_TossObject_SetBounceSound
|
|
(
|
|
"bouncesound",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"sound",
|
|
"When bouncing, what sound to play on impact",
|
|
EV_NORMAL
|
|
|
|
);
|
|
Event EV_TossObject_SetBounceSoundChance(
|
|
"bouncesoundchance",
|
|
EV_DEFAULT,
|
|
"f[0,1]",
|
|
"chance",
|
|
"When bouncing, the chance that the bounce sound will be played",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Animate, TossObject, "TossObject") {
|
|
{&EV_Touch, &TossObject::Touch },
|
|
{&EV_Stop, &TossObject::Stop },
|
|
{&EV_TossObject_SetBounceSound, &TossObject::SetBounceSound },
|
|
{&EV_TossObject_SetBounceSoundChance, &TossObject::SetBounceSoundChance},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
TossObject::TossObject()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
setMoveType(MOVETYPE_GIB);
|
|
setSolidType(SOLID_NOT);
|
|
bouncesound = "";
|
|
bouncesoundchance = 1.0f;
|
|
}
|
|
|
|
TossObject::TossObject(str model)
|
|
{
|
|
setMoveType(MOVETYPE_GIB);
|
|
setSolidType(SOLID_NOT);
|
|
bouncesound = "";
|
|
bouncesoundchance = 1.0f;
|
|
setModel(model);
|
|
}
|
|
|
|
void TossObject::SetBounceSound(str bounce)
|
|
{
|
|
bouncesound = bounce;
|
|
}
|
|
|
|
void TossObject::SetBounceSound(Event *ev)
|
|
{
|
|
bouncesound = ev->GetString(1);
|
|
}
|
|
|
|
void TossObject::SetBounceSoundChance(float chance)
|
|
{
|
|
bouncesoundchance = chance;
|
|
}
|
|
|
|
void TossObject::SetBounceSoundChance(Event *ev)
|
|
{
|
|
bouncesoundchance = ev->GetFloat(1);
|
|
}
|
|
|
|
void TossObject::Stop(Event *ev)
|
|
{
|
|
setMoveType(MOVETYPE_NONE);
|
|
setSolidType(SOLID_NOT);
|
|
// cancel the previous fade out command
|
|
CancelEventsOfType(EV_FadeOut);
|
|
PostEvent(EV_FadeOut, 7 + G_Random(5));
|
|
setAngles(Vector("0 0 0"));
|
|
NewAnim("landed");
|
|
}
|
|
|
|
void TossObject::Touch(Event *ev)
|
|
{
|
|
Entity *ent;
|
|
|
|
ent = ev->GetEntity(1);
|
|
|
|
// only touch the world
|
|
if (!ent || (ent != world)) {
|
|
return;
|
|
}
|
|
//
|
|
// every time we bounce try to go back to our nominal angles
|
|
//
|
|
setAngles(angles * 0.5f);
|
|
|
|
if (bouncesound.length()) {
|
|
if (G_Random(1) < bouncesoundchance) {
|
|
Sound(bouncesound);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TossObject::SetVelocity(float severity)
|
|
{
|
|
setSolidType(SOLID_BBOX);
|
|
velocity[0] = 100.0 * crandom();
|
|
velocity[1] = 100.0 * crandom();
|
|
velocity[2] = 200.0 + 100.0 * random();
|
|
|
|
avelocity = Vector(G_Random(600), G_Random(600), G_Random(600));
|
|
|
|
velocity *= severity;
|
|
|
|
if (velocity[0] < -400) {
|
|
velocity[0] = -400;
|
|
} else if (velocity[0] > 400) {
|
|
velocity[0] = 400;
|
|
}
|
|
if (velocity[1] < -400) {
|
|
velocity[1] = -400;
|
|
} else if (velocity[1] > 400) {
|
|
velocity[1] = 400;
|
|
}
|
|
if (velocity[2] < 200) {
|
|
velocity[2] = 200; // always some upwards
|
|
} else if (velocity[2] > 600) {
|
|
velocity[2] = 600;
|
|
}
|
|
|
|
NewAnim("idle");
|
|
|
|
// we give it 8 seconds to fall, if not it will get faded out
|
|
PostEvent(EV_FadeOut, 8);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_pushobject (0.75 0.75 0.75) ?
|
|
|
|
Pushable object
|
|
|
|
"dmg" how much damage to cause when blocked. (default 2)
|
|
"pushsound" Sound to play when object is pushed (default is none)
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_PushObject_Start
|
|
(
|
|
"start", EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Sets up the pushobject.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
Event EV_PushObject_SetDamage
|
|
(
|
|
"dmg", EV_DEFAULT,
|
|
"i",
|
|
"damage",
|
|
"Set the damage.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
Event EV_PushObject_SetPushSound
|
|
(
|
|
"pushsound", EV_DEFAULT,
|
|
"s",
|
|
"sound",
|
|
"Set the pushing sound",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Entity, PushObject, "func_pushobject") {
|
|
{&EV_PushObject_Start, &PushObject::Start },
|
|
{&EV_Blocked, &PushObject::BlockFunc },
|
|
{&EV_PushObject_SetDamage, &PushObject::SetDamage },
|
|
{&EV_PushObject_SetPushSound, &PushObject::SetPushSound},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
PushObject::PushObject()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
dmg = 2;
|
|
attack_finished = 0;
|
|
|
|
pushsound = "object_slide";
|
|
|
|
PostEvent(EV_PushObject_Start, EV_POSTSPAWN);
|
|
}
|
|
|
|
void PushObject::SetPushSound(Event *ev)
|
|
{
|
|
pushsound = ev->GetString(1);
|
|
}
|
|
|
|
void PushObject::Start(Event *ev)
|
|
{
|
|
// make sure that this touches triggers
|
|
flags |= FL_TOUCH_TRIGGERS;
|
|
edict->clipmask = MASK_SOLID;
|
|
setSolidType(SOLID_BSP);
|
|
setMoveType(MOVETYPE_PUSH);
|
|
|
|
// fix the bounding box so that the object isn't stuck in the ground
|
|
setSize(mins + Vector(1, 1, 2), maxs - Vector(1, 1, 1));
|
|
}
|
|
|
|
qboolean PushObject::canPush(Vector dir)
|
|
{
|
|
trace_t trace;
|
|
|
|
Vector end(origin.x + dir.x, origin.y + dir.y, origin.z);
|
|
|
|
trace = G_Trace(origin, mins, maxs, end, this, MASK_DEADSOLID, false, "PushObject::Push");
|
|
return (!trace.startsolid && (trace.fraction == 1.0f));
|
|
}
|
|
|
|
qboolean PushObject::Push(Entity *pusher, Vector move)
|
|
{
|
|
trace_t trace;
|
|
|
|
if (pushsound.length()) {
|
|
if (!edict->s.loopSound) {
|
|
LoopSound(pushsound);
|
|
PostEvent(EV_StopLoopSound, level.frametime * 5);
|
|
}
|
|
}
|
|
|
|
Vector end(origin.x + move.x, origin.y + move.y, origin.z);
|
|
|
|
trace = G_Trace(origin, mins, maxs, end, this, MASK_DEADSOLID, false, "PushObject::Push");
|
|
if (!trace.startsolid && (trace.fraction > 0)) {
|
|
owner = pusher;
|
|
|
|
G_PushMove(this, trace.endpos - origin, vec_zero);
|
|
|
|
if (edict->s.loopSound) {
|
|
PostEvent(EV_StopLoopSound, 0.f);
|
|
}
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
Entity *PushObject::getOwner(void)
|
|
{
|
|
return (Entity *)owner;
|
|
}
|
|
|
|
void PushObject::BlockFunc(Event *ev)
|
|
{
|
|
Entity *other;
|
|
|
|
if ((dmg != 0) && (level.time >= attack_finished)) {
|
|
attack_finished = level.time + 0.5f;
|
|
other = ev->GetEntity(1);
|
|
if (other != owner) {
|
|
other->Damage(this, this, dmg, origin, vec_zero, vec_zero, 0, 0, MOD_CRUSH);
|
|
}
|
|
}
|
|
}
|
|
|
|
void PushObject::SetDamage(Event *ev)
|
|
{
|
|
dmg = ev->GetInteger(1);
|
|
}
|
|
|
|
#define SPAWN_AUTO_RESET (1 << 0)
|
|
#define NO_RANDOMNESS (1 << 1)
|
|
#define REMOVE_ON_GROUND (1 << 2)
|
|
/*****************************************************************************/
|
|
/*QUAKED func_fallingrock (0.75 0.75 0.75) ? AUTO_RESET NO_RANDOMNESS REMOVE_ON_GROUND
|
|
|
|
Creates a rock that, when triggered, begins falling and bounces along a path
|
|
specified by targetname. Use info_waypoint for the path.
|
|
|
|
"targetname" the path to follow.
|
|
"dmg" how much damage to cause creatures it hits (default 20).
|
|
"speed" how fast to move (default 200).
|
|
"wait" how long to wait before falling when triggered (default 0).
|
|
"noise" sound to play when rock touches the world
|
|
|
|
AUTO_RESET - when done falling, automatically return to the start
|
|
NO_RANDOMNESS - don't use any randomness when making the rocks fall
|
|
REMOVE_ON_GROUND - remove the rocks when done
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_FallingRock_Bounce
|
|
(
|
|
"bounce",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"sent to entity when touched.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_Rotate
|
|
(
|
|
"rotate",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"rotates the falling rock.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_SetWait
|
|
(
|
|
"wait",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"wait",
|
|
"How long to wait before rock starts falling.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_Start
|
|
(
|
|
"start",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Starts rock falling.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_SetDmg
|
|
(
|
|
"dmg",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"dmg",
|
|
"Set the damage from the rock.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_SetSpeed
|
|
(
|
|
"speed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speed",
|
|
"Set the speed that the rock moves at.",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_FallingRock_SetBounceSound
|
|
(
|
|
"noise",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"sound",
|
|
"Set the sound to play when the rock bounces",
|
|
EV_NORMAL
|
|
|
|
);
|
|
|
|
CLASS_DECLARATION(Entity, FallingRock, "func_fallingrock") {
|
|
{&EV_Activate, &FallingRock::Activate },
|
|
{&EV_Touch, &FallingRock::Touch },
|
|
{&EV_FallingRock_Bounce, &FallingRock::Bounce },
|
|
{&EV_FallingRock_Rotate, &FallingRock::Rotate },
|
|
{&EV_FallingRock_Start, &FallingRock::StartFalling },
|
|
{&EV_FallingRock_SetWait, &FallingRock::SetWait },
|
|
{&EV_FallingRock_SetSpeed, &FallingRock::SetSpeed },
|
|
{&EV_FallingRock_SetDmg, &FallingRock::SetDmg },
|
|
{&EV_FallingRock_SetBounceSound, &FallingRock::SetBounceSound},
|
|
{NULL, NULL }
|
|
};
|
|
|
|
FallingRock::FallingRock()
|
|
{
|
|
if (LoadingSavegame) {
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
active = 0;
|
|
current = NULL;
|
|
setMoveType(MOVETYPE_NONE);
|
|
wait = 0;
|
|
dmg = 20;
|
|
speed = 200;
|
|
activator = NULL;
|
|
attack_finished = 0;
|
|
|
|
SetBounceSound("impact_rock");
|
|
}
|
|
|
|
Entity *FallingRock::SetNextBounceDir(void)
|
|
{
|
|
Entity *ent;
|
|
|
|
if (!current->target.length()) {
|
|
return NULL;
|
|
}
|
|
|
|
ent = (Entity *)G_FindTarget(NULL, current->target.c_str());
|
|
if (!ent) {
|
|
gi.Error(ERR_DROP, "FallingRock :: Entity with targetname of '%s' not found", current->target.c_str());
|
|
}
|
|
|
|
bounce_dir = ent->origin - current->origin;
|
|
bounce_dir.normalize();
|
|
|
|
return ent;
|
|
}
|
|
|
|
void FallingRock::NextBounce(void)
|
|
{
|
|
float time;
|
|
float distance;
|
|
Vector delta, xydelta;
|
|
float xy_speed;
|
|
float vertical_speed;
|
|
|
|
delta = current->origin - origin;
|
|
xydelta = delta;
|
|
xydelta.z = 0;
|
|
xy_speed = speed;
|
|
|
|
distance = xydelta.normalize();
|
|
|
|
time = distance / xy_speed;
|
|
|
|
if (!(spawnflags & NO_RANDOMNESS)) {
|
|
if (time > 1.0f) {
|
|
time = 0.75f + G_Random(1);
|
|
}
|
|
|
|
if (time < 0.4f) {
|
|
time = 0.4f;
|
|
}
|
|
}
|
|
|
|
vertical_speed = (delta.z / time) + (0.5f * gravity * sv_gravity->value * time);
|
|
if (vertical_speed < 0) {
|
|
vertical_speed = 0;
|
|
}
|
|
|
|
velocity = xydelta * xy_speed;
|
|
velocity.z = vertical_speed;
|
|
|
|
Vector ang(0, vectoyaw(delta), 0);
|
|
ang.AngleVectors(NULL, &rotateaxis);
|
|
|
|
// make sure it leaves the ground
|
|
groundentity = NULL;
|
|
}
|
|
|
|
void FallingRock::Rotate(Event *ev)
|
|
{
|
|
float mat[3][3];
|
|
float ang;
|
|
|
|
ang = 360.0f * FRAMETIME;
|
|
RotatePointAroundVector(mat[0], rotateaxis, orientation[0], ang);
|
|
RotatePointAroundVector(mat[1], rotateaxis, orientation[1], ang);
|
|
RotatePointAroundVector(mat[2], rotateaxis, orientation[2], ang);
|
|
MatrixToEulerAngles(mat, angles);
|
|
setAngles(angles);
|
|
|
|
if (velocity != vec_zero) {
|
|
PostEvent(EV_FallingRock_Rotate, FRAMETIME);
|
|
}
|
|
}
|
|
|
|
void FallingRock::SetWait(Event *ev)
|
|
{
|
|
wait = ev->GetFloat(1);
|
|
}
|
|
|
|
void FallingRock::SetSpeed(Event *ev)
|
|
{
|
|
speed = ev->GetFloat(1);
|
|
}
|
|
|
|
void FallingRock::SetDmg(Event *ev)
|
|
{
|
|
dmg = ev->GetInteger(1);
|
|
}
|
|
|
|
void FallingRock::SetBounceSound(str sound)
|
|
{
|
|
bouncesound = sound;
|
|
// cache the sound in
|
|
CacheResource(bouncesound.c_str());
|
|
}
|
|
|
|
void FallingRock::SetBounceSound(Event *ev)
|
|
{
|
|
SetBounceSound(ev->GetString(1));
|
|
}
|
|
|
|
void FallingRock::Activate(Event *ev)
|
|
{
|
|
if (active == 1) {
|
|
return;
|
|
}
|
|
|
|
if ((active == 2) && (spawnflags & SPAWN_AUTO_RESET)) {
|
|
current = NULL;
|
|
activator = NULL;
|
|
setMoveType(MOVETYPE_NONE);
|
|
NoLerpThisFrame();
|
|
setOrigin(start_origin);
|
|
}
|
|
|
|
activator = ev->GetEntity(1);
|
|
|
|
if (wait) {
|
|
PostEvent(EV_FallingRock_Start, wait);
|
|
} else {
|
|
ProcessEvent(EV_FallingRock_Start);
|
|
}
|
|
}
|
|
|
|
void FallingRock::StartFalling(Event *ev)
|
|
{
|
|
if (current) {
|
|
return;
|
|
}
|
|
|
|
if (!active) {
|
|
start_origin = origin;
|
|
}
|
|
|
|
active = 1;
|
|
setMoveType(MOVETYPE_BOUNCE);
|
|
setSolidType(SOLID_BBOX);
|
|
PostEvent(EV_FallingRock_Rotate, FRAMETIME);
|
|
edict->clipmask = MASK_SOLID | CONTENTS_BODY;
|
|
|
|
last_bounce_origin = origin;
|
|
current = this;
|
|
current = SetNextBounceDir();
|
|
if (current) {
|
|
NextBounce();
|
|
}
|
|
}
|
|
|
|
void FallingRock::Touch(Event *ev)
|
|
{
|
|
Entity *other;
|
|
|
|
other = ev->GetEntity(1);
|
|
|
|
if (other != world) {
|
|
if ((velocity != vec_zero) && (other->takedamage) && (level.time >= attack_finished)) {
|
|
other->Damage(this, activator, dmg, origin, vec_zero, vec_zero, 20, 0, MOD_THROWNOBJECT);
|
|
attack_finished = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
if (!current || (other != world)) {
|
|
return;
|
|
}
|
|
if (bouncesound.length()) {
|
|
Vector delta(origin - last_bounce_origin);
|
|
|
|
if (delta.length() > 8) {
|
|
last_bounce_origin = origin;
|
|
Sound(bouncesound.c_str(), CHAN_VOICE);
|
|
}
|
|
}
|
|
|
|
// we have to wait to set the velocity since the physics code
|
|
// will modify it when we return.
|
|
PostEvent(EV_FallingRock_Bounce, 0);
|
|
}
|
|
|
|
void FallingRock::Bounce(Event *ev)
|
|
{
|
|
Vector delta;
|
|
|
|
if (!current) {
|
|
return;
|
|
}
|
|
|
|
do {
|
|
// check if we've passed the waypoint
|
|
delta = origin - current->origin;
|
|
if ((delta * bounce_dir) >= (-2 * edict->r.radius)) {
|
|
// call any threads on the current waypoint
|
|
if (current->isSubclassOf(Trigger)) {
|
|
current->ProcessEvent(EV_Trigger_StartThread);
|
|
}
|
|
current = SetNextBounceDir();
|
|
if (!current) {
|
|
velocity = vec_zero;
|
|
if (spawnflags & SPAWN_AUTO_RESET) {
|
|
active = 2;
|
|
} else if (spawnflags & REMOVE_ON_GROUND) {
|
|
PostEvent(EV_Remove, 0);
|
|
}
|
|
break;
|
|
}
|
|
} else {
|
|
NextBounce();
|
|
break;
|
|
}
|
|
} while (1);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED func_ladder (0.75 0.75 0.75) ?
|
|
|
|
Ladder trigger volume
|
|
|
|
******************************************************************************/
|
|
|
|
Event EV_LadderSetup
|
|
(
|
|
"_ladder_setup",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Does the post spawn setup of the ladder"
|
|
);
|
|
|
|
CLASS_DECLARATION(Entity, FuncLadder, "func_ladder") {
|
|
{&EV_Use, NULL },
|
|
{&EV_Touch, NULL },
|
|
{&EV_SetAngle, &FuncLadder::SetLadderFacing},
|
|
{&EV_LadderSetup, &FuncLadder::LadderSetup },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
FuncLadder::FuncLadder()
|
|
{
|
|
if (LoadingSavegame) {
|
|
return;
|
|
}
|
|
|
|
PostEvent(EV_LadderSetup, EV_POSTSPAWN);
|
|
}
|
|
|
|
void FuncLadder::LadderSetup(Event *ev)
|
|
{
|
|
setMoveType(MOVETYPE_PUSH);
|
|
|
|
setContents(CONTENTS_LADDER);
|
|
setSolidType(SOLID_BSP);
|
|
|
|
// never send to clients
|
|
edict->r.svFlags |= SVF_NOCLIENT;
|
|
}
|
|
|
|
void FuncLadder::SetLadderFacing(Event *ev)
|
|
{
|
|
m_vFacingAngles = vec_zero;
|
|
m_vFacingAngles[1] = AngleMod(ev->GetFloat(1));
|
|
|
|
AngleVectorsLeft(m_vFacingAngles, m_vFacingDir, NULL, NULL);
|
|
}
|
|
|
|
qboolean FuncLadder::CanUseLadder(Entity *pUser)
|
|
{
|
|
int iMask;
|
|
Vector vDelta;
|
|
Vector vUserFacing;
|
|
Vector start, end;
|
|
trace_t trace;
|
|
|
|
vDelta = origin - pUser->origin;
|
|
vDelta[2] = 0;
|
|
|
|
if (vDelta.length() > 52.0f) {
|
|
return qfalse;
|
|
}
|
|
|
|
AngleVectorsLeft(pUser->angles, vUserFacing, NULL, NULL);
|
|
|
|
iMask = pUser->client ? MASK_PLAYERSOLID : MASK_MONSTERSOLID;
|
|
|
|
if (pUser->origin[2] + pUser->maxs[2] > absmax[2]) {
|
|
if (DotProduct(vUserFacing, m_vFacingDir) <= 0.15f) {
|
|
vDelta = pUser->origin - origin;
|
|
vDelta[2] = 0.0f;
|
|
|
|
if (DotProduct(m_vFacingDir, vDelta) >= 0.0f) {
|
|
start = origin + m_vFacingDir * 26.0f;
|
|
start[2] = absmax[2] + 16.0f;
|
|
|
|
end = start - Vector(0, 0, 16);
|
|
|
|
trace = G_Trace(
|
|
start, pUser->mins, pUser->maxs, end, pUser, MASK_PLAYERSOLID, qfalse, "FuncLadder::CanUseLadder"
|
|
);
|
|
|
|
start = trace.endpos;
|
|
trace = G_Trace(
|
|
start, pUser->mins, pUser->maxs, start, pUser, MASK_PLAYERSOLID, qfalse, "FuncLadder::CanUseLadder"
|
|
);
|
|
|
|
if (!trace.startsolid) {
|
|
// client can use ladder
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
vDelta = (pUser->origin - origin);
|
|
vDelta[2] = 0.0f;
|
|
|
|
if (DotProduct(vUserFacing, m_vFacingDir) < -0.15f || DotProduct(vDelta, m_vFacingDir) > 0.0f) {
|
|
return qfalse;
|
|
}
|
|
|
|
start = origin - m_vFacingDir * 29.0f;
|
|
start[2] = absmin[2] + 16.0f;
|
|
|
|
end = start;
|
|
end[2] = start[2] - 16.0f;
|
|
|
|
trace = G_Trace(start, pUser->mins, pUser->maxs, end, pUser, MASK_PLAYERSOLID, qfalse, "FuncLadder::CanUseLadder");
|
|
|
|
if (!trace.allsolid) {
|
|
// client can use ladder
|
|
return qtrue;
|
|
}
|
|
|
|
gi.DPrintf("ladder start position is blocked by a solid object\n");
|
|
return qfalse;
|
|
}
|
|
|
|
void FuncLadder::PositionOnLadder(Entity *pUser)
|
|
{
|
|
Vector vPos;
|
|
Vector vStart;
|
|
Vector vEnd;
|
|
trace_t trace;
|
|
|
|
if (pUser->origin[2] + pUser->maxs[2] > absmax[2]) {
|
|
vEnd = origin + m_vFacingDir * 26.0f;
|
|
vEnd[2] = absmax[2] + 2.0f;
|
|
} else if (absmin[2] + 32.0f > pUser->origin[2]) {
|
|
vEnd = origin - m_vFacingDir * 29.0f;
|
|
vEnd[2] = absmin[2] + 2.0f;
|
|
} else {
|
|
vEnd = origin - m_vFacingDir * 16.0f;
|
|
vEnd[2] = pUser->origin[2] + 8.0f;
|
|
}
|
|
|
|
vStart = vEnd;
|
|
|
|
trace =
|
|
G_Trace(vStart, pUser->mins, pUser->maxs, vEnd, pUser, MASK_PLAYERSOLID, qtrue, "FuncLadder::PositionOnLadder");
|
|
|
|
if (trace.allsolid) {
|
|
vStart = pUser->origin;
|
|
vStart[2] = pUser->origin[2] + 4.0f;
|
|
|
|
vEnd = origin - m_vFacingDir * 16.0f;
|
|
vEnd[2] = pUser->origin[2] + 4.0f;
|
|
|
|
trace = G_Trace(
|
|
vStart, pUser->mins, pUser->maxs, vEnd, pUser, MASK_PLAYERSOLID, qtrue, "FuncLadder::PositionOnLadder2"
|
|
);
|
|
}
|
|
|
|
pUser->setOrigin(trace.endpos);
|
|
pUser->setAngles(m_vFacingAngles);
|
|
}
|
|
|
|
void FuncLadder::AdjustPositionOnLadder(Entity *pUser)
|
|
{
|
|
float fHeight;
|
|
Vector vDelta;
|
|
Vector vPos;
|
|
Vector vStart;
|
|
trace_t trace;
|
|
|
|
vDelta = pUser->origin - origin;
|
|
vDelta[2] = 0;
|
|
|
|
fHeight = ((int)(pUser->origin[2] + 8.0f) & ~15);
|
|
|
|
if (vDelta.length() == 16.0f && pUser->origin[2] == fHeight) {
|
|
return;
|
|
}
|
|
|
|
vPos = origin - m_vFacingDir * 16.0f;
|
|
vPos[2] = fHeight;
|
|
|
|
vStart = vPos + m_vFacingDir * -16.0f;
|
|
|
|
trace = G_Trace(
|
|
vStart, pUser->mins, pUser->maxs, vPos, pUser, MASK_PLAYERSOLID, qtrue, "FuncLadder::AdjustPositionOnLadder"
|
|
);
|
|
|
|
if (trace.allsolid || trace.startsolid) {
|
|
trace = G_Trace(
|
|
pUser->origin,
|
|
pUser->mins,
|
|
pUser->maxs,
|
|
vPos,
|
|
pUser,
|
|
MASK_PLAYERSOLID,
|
|
qtrue,
|
|
"FuncLadder::AdjustPositionOnLadder"
|
|
);
|
|
}
|
|
|
|
pUser->setOrigin(trace.endpos);
|
|
}
|
|
|
|
void FuncLadder::EnsureOverLadder(Entity *pUser)
|
|
{
|
|
float fHeight = absmax[2];
|
|
Vector vStart;
|
|
Vector vPos;
|
|
trace_t trace;
|
|
|
|
if (pUser->origin[2] - fHeight >= 8.0f) {
|
|
return;
|
|
}
|
|
|
|
vPos = pUser->origin;
|
|
vPos[2] = fHeight + 16.0f;
|
|
|
|
vStart = pUser->origin + m_vFacingDir * -16.0f;
|
|
|
|
trace = G_Trace(
|
|
vStart, pUser->mins, pUser->maxs, vPos, pUser->edict, MASK_PLAYERSOLID, true, "FuncLadder::EnsureOverLadder"
|
|
);
|
|
|
|
if (trace.startsolid || trace.allsolid) {
|
|
trace = G_Trace(
|
|
origin, pUser->mins, pUser->maxs, vPos, pUser->edict, MASK_PLAYERSOLID, true, "FuncLadder::EnsureOverLadder"
|
|
);
|
|
}
|
|
|
|
pUser->setOrigin(trace.endpos);
|
|
}
|
|
|
|
void FuncLadder::EnsureForwardOffLadder(Entity *pUser)
|
|
{
|
|
int iMask;
|
|
Vector vStart;
|
|
Vector vPos;
|
|
Vector vDelta;
|
|
trace_t trace;
|
|
|
|
if (pUser->origin[2] - absmax[2] >= 8.0f) {
|
|
return;
|
|
}
|
|
|
|
iMask = pUser->client ? MASK_PLAYERSOLID : MASK_MONSTERSOLID;
|
|
|
|
vPos = pUser->origin;
|
|
vPos[2] = absmax[2] + 16.0f;
|
|
|
|
vStart = vPos + m_vFacingDir * -16.0f;
|
|
|
|
trace = G_Trace(vStart, pUser->mins, pUser->maxs, vPos, pUser, iMask, qtrue, "FuncLadder::EnsureOverLadder");
|
|
|
|
if (trace.allsolid || trace.startsolid) {
|
|
trace =
|
|
G_Trace(pUser->origin, pUser->mins, pUser->maxs, vPos, pUser, iMask, qtrue, "FuncLadder::EnsureOverLadder");
|
|
}
|
|
|
|
pUser->setOrigin(trace.endpos);
|
|
}
|
|
|
|
const Vector& FuncLadder::getFacingAngles() const
|
|
{
|
|
return m_vFacingAngles;
|
|
}
|
|
|
|
const Vector& FuncLadder::getFacingDir() const
|
|
{
|
|
return m_vFacingDir;
|
|
}
|
|
|
|
Event EV_InfoLandmark_Name
|
|
(
|
|
"landmark_name", EV_DEFAULT,
|
|
"s",
|
|
"name",
|
|
"Set the name of this landmark",
|
|
EV_NORMAL
|
|
);
|
|
Event EV_InfoLandmark_SetOrigin
|
|
(
|
|
"origin", EV_DEFAULT,
|
|
"v",
|
|
"origin",
|
|
"Set the origin of the landmark.",
|
|
EV_NORMAL
|
|
);
|
|
|
|
CLASS_DECLARATION(Listener, InfoLandmark, "info_landmark") {
|
|
{&EV_InfoLandmark_Name, &InfoLandmark::SetLandmarkName},
|
|
{&EV_InfoLandmark_SetOrigin, &InfoLandmark::SetOrigin },
|
|
{NULL, NULL }
|
|
};
|
|
|
|
InfoLandmark::InfoLandmark()
|
|
{
|
|
m_bNameSet = false;
|
|
m_bOriginSet = false;
|
|
}
|
|
|
|
void InfoLandmark::SetLandmarkName(Event *ev)
|
|
{
|
|
m_sName = ev->GetString(1);
|
|
m_bNameSet = true;
|
|
|
|
if (m_bOriginSet) {
|
|
level.AddLandmarkName(m_sName, m_vOrigin);
|
|
PostEvent(EV_Remove, EV_REMOVE);
|
|
}
|
|
}
|
|
|
|
void InfoLandmark::SetOrigin(Event *ev)
|
|
{
|
|
m_vOrigin = ev->GetVector(1);
|
|
|
|
if (m_vOrigin == Vector(99999, 99999, 99999)) {
|
|
ScriptError(
|
|
"Illegal location (%f, %f, %f) for Landmark. Please place in valid XYZ coordinates\n",
|
|
m_vOrigin[0],
|
|
m_vOrigin[1],
|
|
m_vOrigin[2]
|
|
);
|
|
}
|
|
|
|
level.AddLandmarkOrigin(m_vOrigin);
|
|
m_bOriginSet = true;
|
|
|
|
if (m_bNameSet) {
|
|
level.AddLandmarkName(m_sName, m_vOrigin);
|
|
PostEvent(EV_Remove, EV_REMOVE);
|
|
}
|
|
}
|