3075 lines
68 KiB
C++
3075 lines
68 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// $Logfile:: /EF2/Code/DLLs/game/misc.cpp $
|
|
// $Revision:: 38 $
|
|
// $Author:: Singlis $
|
|
// $Date:: 9/26/03 2:36p $
|
|
//
|
|
// Copyright (C) 1997 by Ritual Entertainment, Inc.
|
|
// All rights reserved.
|
|
//
|
|
// This source is may not be distributed and/or modified without
|
|
// expressly written permission by Ritual Entertainment, Inc.
|
|
//
|
|
//
|
|
// DESCRIPTION:
|
|
// 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 "_pch_cpp.h"
|
|
#include "entity.h"
|
|
#include "trigger.h"
|
|
#include "explosion.h"
|
|
#include "misc.h"
|
|
#include "navigate.h"
|
|
#include "specialfx.h"
|
|
#include "player.h"
|
|
#include "g_utils.h"
|
|
#include "weaputils.h"
|
|
#include <qcommon/gameplaymanager.h>
|
|
#include "mp_manager.hpp"
|
|
|
|
/*****************************************************************************/
|
|
/*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_SCRIPTONLY,
|
|
NULL,
|
|
NULL,
|
|
"Stop rotating the wall."
|
|
);
|
|
Event EV_ExplodingWall_OnGround
|
|
(
|
|
"checkonground",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Check if exploding wall is on ground."
|
|
);
|
|
Event EV_ExplodingWall_AngleSpeed
|
|
(
|
|
"anglespeed",
|
|
EV_SCRIPTONLY,
|
|
"f",
|
|
"speed",
|
|
"Set the angle speed."
|
|
);
|
|
Event EV_ExplodingWall_LandRadius
|
|
(
|
|
"land_radius",
|
|
EV_SCRIPTONLY,
|
|
"f",
|
|
"radius",
|
|
"Set the land radius."
|
|
);
|
|
Event EV_ExplodingWall_LandAngles
|
|
(
|
|
"land_angles",
|
|
EV_SCRIPTONLY,
|
|
"v",
|
|
"angles",
|
|
"Set the land angles."
|
|
);
|
|
Event EV_ExplodingWall_BaseVelocity
|
|
(
|
|
"base_velocity",
|
|
EV_SCRIPTONLY,
|
|
"v",
|
|
"velocity",
|
|
"Set the base velocity."
|
|
);
|
|
Event EV_ExplodingWall_RandomVelocity
|
|
(
|
|
"random_velocity",
|
|
EV_SCRIPTONLY,
|
|
"v",
|
|
"velocity",
|
|
"Set the amount of random variation of the base velocity."
|
|
);
|
|
Event EV_ExplodingWall_SetDmg
|
|
(
|
|
"dmg",
|
|
EV_SCRIPTONLY,
|
|
"i",
|
|
"dmg",
|
|
"Set the damage from the exploding wall."
|
|
);
|
|
Event EV_ExplodingWall_SetExplosions
|
|
(
|
|
"explosions",
|
|
EV_SCRIPTONLY,
|
|
"i",
|
|
"explosions",
|
|
"Set the number of explosions."
|
|
);
|
|
Event EV_ExplodingWall_Setup
|
|
(
|
|
"setup",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Initializes the exploding wall."
|
|
);
|
|
|
|
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 },
|
|
{ &EV_SetGameplayDamage, &ExplodingWall::setDamage },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
//--------------------------------------------------------------
|
|
//
|
|
// Name: setDamage
|
|
// Class: ExplodingWall
|
|
//
|
|
// Description: This function acts as a filter to the real function.
|
|
// It gets data from the database, and then passes it
|
|
// along to the original event. This is here as an attempt
|
|
// to sway people into using the database standard instead of
|
|
// hardcoded numbers.
|
|
//
|
|
// Parameters: Event *ev
|
|
// str -- The value keyword from the database (low, medium, high, etc).
|
|
//
|
|
// Returns: None
|
|
//
|
|
//--------------------------------------------------------------
|
|
void ExplodingWall::setDamage( Event *ev )
|
|
{
|
|
if ( ev->NumArgs() < 1 )
|
|
return;
|
|
|
|
GameplayManager *gpm = GameplayManager::getTheGameplayManager();
|
|
if ( !gpm->hasFormula("OffensiveDamage") )
|
|
return;
|
|
|
|
str damagestr = ev->GetString( 1 );
|
|
float damagemod = 1.0f;
|
|
if ( gpm->getDefine(damagestr) != "" )
|
|
damagemod = (float)atof(gpm->getDefine(damagestr));
|
|
GameplayFormulaData fd(this, 0, 0, "");
|
|
float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod);
|
|
Event *newev = new Event(EV_ExplodingWall_SetDmg);
|
|
newev->AddInteger((int)finaldamage);
|
|
ProcessEvent(newev);
|
|
}
|
|
|
|
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_NOT);
|
|
|
|
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.0f )
|
|
delta[ 0 ] -= 360.0f;
|
|
if ( delta[ 0 ] < -180.0f )
|
|
delta[ 0 ] += 360.0f;
|
|
if ( delta[ 1 ] > 180.0f )
|
|
delta[ 1 ] -= 360.0f;
|
|
if ( delta[ 1 ] < -180.0f )
|
|
delta[ 1 ] += 360.0f;
|
|
if ( delta[ 2 ] > 180.0f )
|
|
delta[ 2 ] -= 360.0f;
|
|
if ( delta[ 2 ] < -180.0f )
|
|
delta[ 2 ] += 360.0f;
|
|
most = MaxValue( delta );
|
|
if ( !angle_speed )
|
|
angle_speed = 1;
|
|
t = (int)(10.0f * most / angle_speed);
|
|
time = (float)t / 10.0f;
|
|
delta = delta * (1.0f / time);
|
|
avelocity = delta;
|
|
PostEvent( EV_ExplodingWall_StopRotating, time );
|
|
state = 2;
|
|
}
|
|
|
|
ActivateTargets( ev );
|
|
|
|
if ( land_radius > 0.0f )
|
|
{
|
|
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;
|
|
float damage;
|
|
|
|
if ( takedamage == DAMAGE_NO )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( on_ground )
|
|
{
|
|
GroundDamage( ev );
|
|
return;
|
|
}
|
|
|
|
damage = ev->GetFloat( 1 );
|
|
//inflictor = ev->GetEntity( 2 );
|
|
attacker = ev->GetEntity( 3 );
|
|
|
|
if ( spawnflags & ACCUMULATIVE )
|
|
{
|
|
health -= damage;
|
|
if ( health > 0.0f )
|
|
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.0f )
|
|
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();
|
|
BroadcastSound();
|
|
PostEvent( EV_Remove, 0.0f );
|
|
}
|
|
|
|
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;
|
|
float t;
|
|
|
|
delta = land_angles - angles;
|
|
if ( delta.length() > 1.0f )
|
|
{
|
|
if ( delta[ 0 ] > 180.0f )
|
|
delta[ 0 ] -= 360.0f;
|
|
if ( delta[ 0 ] < -180.0f )
|
|
delta[ 0 ] += 360.0f;
|
|
if ( delta[ 1 ] > 180.0f )
|
|
delta[ 1 ] -= 360.0f;
|
|
if ( delta[ 1 ] < -180.0f )
|
|
delta[ 1 ] += 360.0f;
|
|
if ( delta[ 2 ] > 180.0f )
|
|
delta[ 2 ] -= 360.0f;
|
|
if ( delta[ 2 ] < -180.0f )
|
|
delta[ 2 ] += 360.0f;
|
|
most = MaxValue( delta );
|
|
if ( angle_speed > 3.0f )
|
|
t = 10.0f * most / ( angle_speed / 3.0f );
|
|
else
|
|
t = 10.0f * most;
|
|
time = t / 10.0f;
|
|
delta = delta * ( 1.0f / 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();
|
|
BroadcastSound();
|
|
PostEvent( EV_Remove, 0.0f );
|
|
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.0f, 0.0f, 280.0f );
|
|
random_velocity = Vector( 140.0f, 140.0f, 140.0f );
|
|
|
|
orig_mins = mins;
|
|
orig_maxs = maxs;
|
|
|
|
respondto = spawnflags ^ TRIGGER_PLAYERS;
|
|
|
|
attack_finished = false;
|
|
|
|
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 FAST_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
|
|
If FAST_EFFECTS is set, the teleport will be instant, and the effects will appear
|
|
******************************************************************************/
|
|
|
|
#define NO_EFFECTS ( 1 << 5 )
|
|
#define FAST_EFFECTS ( 1 << 6 )
|
|
|
|
Event EV_Teleporter_Teleport
|
|
(
|
|
"teleport",
|
|
EV_CODEONLY,
|
|
"e",
|
|
"entity",
|
|
"Teleports the entity to destination."
|
|
);
|
|
Event EV_Teleporter_StopTeleport
|
|
(
|
|
"stopteleport",
|
|
EV_CODEONLY,
|
|
"e",
|
|
"entity",
|
|
"Releases the entity at the end of the teleport."
|
|
);
|
|
Event EV_Teleporter_SetThread
|
|
(
|
|
"teleportthread",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"thread_name",
|
|
"Sets the thread to run when the player is teleported."
|
|
);
|
|
|
|
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_thread = ev->GetString( 1 );
|
|
}
|
|
|
|
void Teleporter::StartTeleport( Event *ev )
|
|
{
|
|
//Entity *fx;
|
|
Entity *other;
|
|
Event *event;
|
|
qboolean is_sentient;
|
|
//Vector new_position;
|
|
|
|
|
|
if ( in_use )
|
|
return;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
if ( !other || other->isSubclassOf( Projectile ) )
|
|
return;
|
|
|
|
in_use = true;
|
|
|
|
if ( doFullTeleport( other ) )
|
|
{
|
|
Sound( "snd_teleport", CHAN_AUTO, 1, 400 );
|
|
}
|
|
|
|
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 );
|
|
} */
|
|
|
|
if ( doFullTeleport( other ) )
|
|
{
|
|
Event *newEvent = new Event( EV_DisplayEffect );
|
|
newEvent->AddString( "TransportOut" );
|
|
newEvent->AddString( "Multiplayer" );
|
|
other->PostEvent( newEvent, 0.0f );
|
|
}
|
|
|
|
// Create the teleport special effect
|
|
|
|
/* fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE );
|
|
fx->setOrigin( other->origin );
|
|
|
|
if ( is_sentient )
|
|
{
|
|
fx->setModel( "fx_bigteleport.tik" );
|
|
//fx->Sound( "sound/environment/electric/singles/dimming.wav" );
|
|
}
|
|
else
|
|
{
|
|
fx->setModel( "fx_teleport2.tik" );
|
|
}
|
|
|
|
fx->animate->RandomAnimate( "idle", EV_Remove );
|
|
|
|
if ( is_sentient && !( spawnflags & FAST_EFFECTS ) )
|
|
{
|
|
// 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 && !( spawnflags & FAST_EFFECTS ) )
|
|
{
|
|
PostEvent( event, 4.0f );
|
|
other->PostEvent( EV_Hide, 2.0f );
|
|
}
|
|
else
|
|
PostEvent( event, 0.0f );
|
|
}
|
|
|
|
void Teleporter::Teleport( Event *ev )
|
|
{
|
|
Entity *dest;
|
|
int i;
|
|
Entity *other;
|
|
Vector mid;
|
|
//Entity *fx;
|
|
//Event *event;
|
|
Event *newEvent;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
if ( !other || ( other == world ) )
|
|
{
|
|
in_use = false;
|
|
return;
|
|
}
|
|
|
|
dest = 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 ) )
|
|
{
|
|
thePathManager.Teleport( other, other->origin, dest->origin );
|
|
other->origin = dest->origin + Vector( 0.0f, 0.0f, 1.0f );
|
|
other->velocity = vec_zero;
|
|
other->origin.copyTo( other->edict->s.origin2 );
|
|
other->NoLerpThisFrame();
|
|
}
|
|
else
|
|
{
|
|
mid = ( absmax - absmin ) * 0.5f;
|
|
other->origin = dest->origin + Vector( 0.0f, 0.0f, 1.0f );
|
|
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_TIME_TELEPORT;
|
|
|
|
// 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.0f )
|
|
len = 400.0f;
|
|
other->velocity = ( ( TeleporterDestination * )dest )->movedir * len;
|
|
}
|
|
|
|
// kill anything at the destination
|
|
//don't do this if spectator
|
|
|
|
if ( doFullTeleport( other ) )
|
|
{
|
|
KillBox( other );
|
|
|
|
dest->Sound( "snd_teleport", CHAN_AUTO, 1, 400 );
|
|
}
|
|
|
|
other->setOrigin( other->origin );
|
|
other->origin.copyTo( other->edict->s.origin2 );
|
|
|
|
// Run the teleport thread if set
|
|
|
|
if ( teleport_thread.length() && other->isSubclassOf( Player ) )
|
|
{
|
|
if ( !ExecuteThread( teleport_thread.c_str() ) )
|
|
warning( "RunThread", "could not process thread" );
|
|
}
|
|
|
|
// Skip effects if no_effects set
|
|
|
|
if ( spawnflags & NO_EFFECTS )
|
|
{
|
|
Event *event = new Event( EV_Teleporter_StopTeleport );
|
|
event->AddEntity( other );
|
|
ProcessEvent( event );
|
|
return;
|
|
}
|
|
|
|
// Add teleport effect
|
|
|
|
if ( doFullTeleport( other ) )
|
|
{
|
|
newEvent = new Event( EV_DisplayEffect );
|
|
newEvent->AddString( "TransportIn" );
|
|
newEvent->AddString( "Multiplayer" );
|
|
other->PostEvent( newEvent, 0.0f );
|
|
}
|
|
|
|
// Always turn off teleport instantly
|
|
|
|
newEvent = new Event( EV_Teleporter_StopTeleport );
|
|
newEvent->AddEntity( other );
|
|
ProcessEvent( newEvent );
|
|
|
|
/* // Spawn in effect
|
|
|
|
fx = new Entity( ENTITY_CREATE_FLAG_ANIMATE );
|
|
|
|
fx->setOrigin( other->origin );
|
|
|
|
if ( other->isSubclassOf( Sentient ) )
|
|
{
|
|
fx->setModel( "fx_bigteleport.tik" );
|
|
//fx->Sound( "sound/environment/electric/singles/dimming.wav" );
|
|
}
|
|
else
|
|
{
|
|
fx->setModel( "fx_teleport2.tik" );
|
|
}
|
|
|
|
fx->animate->RandomAnimate( "idle", EV_Remove );
|
|
|
|
event = new Event( EV_Teleporter_StopTeleport );
|
|
event->AddEntity( other );
|
|
|
|
if ( other->isSubclassOf( Sentient ) )
|
|
PostEvent( event, 1.75f );
|
|
else
|
|
PostEvent( event, FRAMETIME ); */
|
|
}
|
|
|
|
void Teleporter::StopTeleport( Event *ev )
|
|
{
|
|
Entity *other;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
if ( other && other->isSubclassOf( Sentient ) )
|
|
{
|
|
if ( doFullTeleport( other ) )
|
|
{
|
|
other->flags &= ~FL_IMMOBILE;
|
|
other->takedamage = DAMAGE_AIM;
|
|
other->showModel();
|
|
}
|
|
}
|
|
|
|
//if ( !( spawnflags & NO_EFFECTS ) )
|
|
// other->Sound( "snd_teleport" );
|
|
|
|
in_use = false;
|
|
}
|
|
|
|
bool Teleporter::doFullTeleport( Entity *entity )
|
|
{
|
|
if ( entity && entity->isSubclassOf( Player ) )
|
|
{
|
|
Player *player = (Player *)entity;
|
|
|
|
if ( multiplayerManager.inMultiplayer() && multiplayerManager.isPlayerSpectator( player ) )
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Teleporter::Teleporter()
|
|
{
|
|
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", this );
|
|
//CacheResource( "sound/environment/electric/singles/dimming.wav", this );
|
|
//CacheResource( "fx_teleport2.tik", this );
|
|
//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.0f );
|
|
}
|
|
|
|
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."
|
|
);
|
|
Event EV_UseAnim_Thread
|
|
(
|
|
"thread",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"label",
|
|
"Sets which thread to use when this UseAnim is triggered."
|
|
);
|
|
Event EV_UseAnim_Count
|
|
(
|
|
"count",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"newCount",
|
|
"Sets how many times the UseAnim can be triggered."
|
|
);
|
|
Event EV_UseAnim_TriggerTarget
|
|
(
|
|
"triggertarget",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"targetname",
|
|
"Sets what should be triggered, when this UseAnim is triggered."
|
|
);
|
|
Event EV_UseAnim_SetAnim
|
|
(
|
|
"anim",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"animName",
|
|
"set the animation to use for player."
|
|
);
|
|
Event EV_UseAnim_SetKey
|
|
(
|
|
"key",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"keyName",
|
|
"set the key needed to make this UseAnim function."
|
|
);
|
|
Event EV_UseAnim_SetState
|
|
(
|
|
"state",
|
|
EV_CHEAT,
|
|
"s",
|
|
"stateName",
|
|
"set the state to use for the player."
|
|
);
|
|
Event EV_UseAnim_SetCamera
|
|
(
|
|
"camera",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"cameraPosition",
|
|
"set the camera to use when in this animation.\n"
|
|
"topdown, behind, front, side, behind_fixed, side_left, side_right"
|
|
);
|
|
Event EV_UseAnim_SetNumLoops
|
|
(
|
|
"num_loops",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"loopCount",
|
|
"set the number of times to loop an animation per use."
|
|
);
|
|
Event EV_UseAnim_SetDelay
|
|
(
|
|
"delay",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"delayTime",
|
|
"how long it takes for the UseAnim to be retriggered once you leave it."
|
|
);
|
|
Event EV_UseAnim_SetActionType
|
|
(
|
|
"action_type",
|
|
EV_DEFAULT,
|
|
"S",
|
|
"actionType",
|
|
"Action type (kick, ignite, etc)"
|
|
);
|
|
|
|
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 },
|
|
{ &EV_UseAnim_SetActionType, &UseAnim::SetActionType },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
UseAnim::UseAnim()
|
|
{
|
|
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->svflags |= SVF_NOCLIENT;
|
|
}
|
|
|
|
// by default this can activated infinitely
|
|
count = -1;
|
|
// clear out the triggertarget
|
|
triggertarget = "";
|
|
// clear out the thread
|
|
thread = "";
|
|
// 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.WDPrintf( "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, CENTERPRINT_IMPORTANCE_NORMAL, "$$ItemNeeded$$%s", item->getName().c_str() );
|
|
}
|
|
delete item;
|
|
|
|
// don't retrigger for 5 seconds
|
|
last_active_time = level.time + 5.0f;
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( isSubclassOf( TouchAnim ) )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
dest = G_FindTarget( NULL, Target() );
|
|
if ( !dest || !dest->isSubclassOf( UseAnimDestination ) )
|
|
{
|
|
warning( "UseAnim", "Couldn't find destination\n" );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool UseAnim::GetInformation( const 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 = 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 = true;
|
|
|
|
// if this is a TouchAnim see if it is linked to another TouchAnim
|
|
if ( isSubclassOf( TouchAnim ) )
|
|
{
|
|
dest = NULL;
|
|
do {
|
|
dest = G_FindTarget( dest, Target() );
|
|
if ( dest )
|
|
{
|
|
if ( dest->isSubclassOf( UseAnim ) )
|
|
{
|
|
// make our linked UseAnim's active as well
|
|
( ( UseAnim * )dest )->active = true;
|
|
}
|
|
}
|
|
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 = G_FindTarget( ent, triggertarget.c_str() );
|
|
if ( !ent )
|
|
{
|
|
break;
|
|
}
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( activator );
|
|
ent->PostEvent( event, 0.0f );
|
|
}
|
|
while ( 1 );
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if ( thread.length() )
|
|
{
|
|
if ( !ExecuteThread( thread ) )
|
|
{
|
|
warning( "TriggerTargets", "Null game script" );
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = 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 = false;
|
|
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 = G_FindTarget( dest, Target() );
|
|
if ( dest )
|
|
{
|
|
if ( dest->isSubclassOf( UseAnim ) )
|
|
{
|
|
// make our linked UseAnim's reset as well
|
|
( ( UseAnim * )dest )->active = false;
|
|
( ( UseAnim * )dest )->last_active_time = level.time + delay;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
while( 1 );
|
|
}
|
|
}
|
|
|
|
void UseAnim::SetThread( Event *ev )
|
|
{
|
|
thread = ev->GetString( 1 );
|
|
}
|
|
|
|
void UseAnim::SetActionType( Event *ev )
|
|
{
|
|
action_type = ev->GetString( 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->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_SCRIPTONLY,
|
|
"s",
|
|
"label",
|
|
"Sets which move thread to use when this UseObject has finshed looping."
|
|
);
|
|
Event EV_UseObject_StopThread
|
|
(
|
|
"stop_thread",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"label",
|
|
"Sets which stop thread to use when this UseObject is finished."
|
|
);
|
|
Event EV_UseObject_ResetThread
|
|
(
|
|
"reset_thread",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"label",
|
|
"Sets which thread to call when resetting."
|
|
);
|
|
Event EV_UseObject_Count
|
|
(
|
|
"count",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"newCount",
|
|
"Sets how many times the UseObject can be triggered."
|
|
);
|
|
Event EV_UseObject_Cone
|
|
(
|
|
"cone",
|
|
EV_SCRIPTONLY,
|
|
"f",
|
|
"newCone",
|
|
"Sets the cone in angles of where the Useobject can be used."
|
|
);
|
|
Event EV_UseObject_Offset
|
|
(
|
|
"offset",
|
|
EV_DEFAULT,
|
|
"v",
|
|
"newOffset",
|
|
"Sets the offset to use for this UseObject."
|
|
);
|
|
Event EV_UseObject_YawOffset
|
|
(
|
|
"yaw_offset",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"newYawOffset",
|
|
"Sets the yaw offset to use for this UseObject."
|
|
);
|
|
Event EV_UseObject_State
|
|
(
|
|
"state",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newState",
|
|
"Sets the state to use for this UseObject."
|
|
);
|
|
Event EV_UseObject_StateBackwards
|
|
(
|
|
"state_backwards",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newState",
|
|
"Sets the backward state to use for this UseObject."
|
|
);
|
|
Event EV_UseObject_TriggerTarget
|
|
(
|
|
"triggertarget",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"targetname",
|
|
"Sets what should be triggered, when this UseObject is triggered."
|
|
);
|
|
Event EV_UseObject_ResetTime
|
|
(
|
|
"reset_time",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"newResetTime",
|
|
"Sets the time it takes for the UseObject to reset itself."
|
|
);
|
|
Event EV_UseObject_DamageType
|
|
(
|
|
"damage_type",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"newDamageType",
|
|
"Sets what kind of damage is needed to activate the trigger."
|
|
);
|
|
Event EV_UseObject_Reset
|
|
(
|
|
"_useobject_reset",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Resets the useobject to the start state after a certain amount of time."
|
|
);
|
|
Event EV_UseObject_Resetting
|
|
(
|
|
"_useobject_resetting",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"Intermediate function for useobject reset."
|
|
);
|
|
Event EV_UseObject_DamageTriggered
|
|
(
|
|
"_useobject_damagetriggered",
|
|
EV_CODEONLY,
|
|
"e",
|
|
"activator",
|
|
"Intermediate function for when the useobject was triggered by damage."
|
|
);
|
|
Event EV_UseObject_Activate
|
|
(
|
|
"activate",
|
|
EV_SCRIPTONLY,
|
|
NULL,
|
|
NULL,
|
|
"Allow the useobject to be used."
|
|
);
|
|
Event EV_UseObject_Deactivate
|
|
(
|
|
"deactivate",
|
|
EV_SCRIPTONLY,
|
|
NULL,
|
|
NULL,
|
|
"Do not allow the useobject to be used."
|
|
);
|
|
Event EV_UseObject_UseMaterial
|
|
(
|
|
"usematerial",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"nameOfUseMaterial",
|
|
"the name of the material that glows when active."
|
|
);
|
|
Event EV_UseObject_SetActiveState
|
|
(
|
|
"_setactivestate",
|
|
EV_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"event that sets up the proper skin for the useobject."
|
|
);
|
|
Event EV_UseObject_SetActionType
|
|
(
|
|
"action_type",
|
|
EV_DEFAULT,
|
|
"S",
|
|
"actionType",
|
|
"The type of action (kick, ignite, etc)"
|
|
);
|
|
Event EV_UseObject_MoveThePlayer
|
|
(
|
|
"movetheplayer",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Attempt to move the player to the UseObject location."
|
|
);
|
|
|
|
#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.
|
|
"action_type" - sets the type of action to perform (for icon display).
|
|
|
|
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( Entity, 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 },
|
|
{ &EV_UseObject_SetActionType, &UseObject::SetActionType },
|
|
{ &EV_UseObject_MoveThePlayer, &UseObject::MoveThePlayer },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
UseObject::UseObject()
|
|
{
|
|
Event * e;
|
|
|
|
animate = new Animate( this );
|
|
|
|
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 = "";
|
|
// clear out the stop thread
|
|
stop_thread = "";
|
|
// clear out the reset thread
|
|
reset_thread = "";
|
|
// 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.0f;
|
|
// set the default yaw offset
|
|
yaw_offset = 0.0f;
|
|
// set the cone
|
|
cone = (float)cos( DEG2RAD( 90.0f ) );
|
|
// 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 = true;
|
|
movetheplayer = false;
|
|
|
|
// start off in the start animation
|
|
e = new Event( EV_Anim );
|
|
e->AddString( "start" );
|
|
PostEvent( e, 0.0f );
|
|
PostEvent( EV_Show, 0.0f );
|
|
// setup our skins once we are spawned
|
|
PostEvent( EV_UseObject_SetActiveState, 0.0f );
|
|
|
|
look_at_me = true;
|
|
}
|
|
|
|
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 = ev->GetString( 1 );
|
|
}
|
|
|
|
void UseObject::MoveThePlayer( Event *ev)
|
|
{
|
|
movetheplayer = true;
|
|
}
|
|
|
|
void UseObject::SetStopThread( Event *ev )
|
|
{
|
|
stop_thread = ev->GetString( 1 );
|
|
}
|
|
|
|
void UseObject::SetResetThread( Event *ev )
|
|
{
|
|
reset_thread = ev->GetString( 1 );
|
|
}
|
|
|
|
void UseObject::ActivateEvent( Event *ev )
|
|
{
|
|
active = true;
|
|
PostEvent( EV_UseObject_SetActiveState, 0.0f );
|
|
}
|
|
|
|
void UseObject::DeactivateEvent( Event *ev )
|
|
{
|
|
active = false;
|
|
PostEvent( EV_UseObject_SetActiveState, 0.0f );
|
|
}
|
|
|
|
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 = (float)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 )
|
|
{
|
|
animate->RandomAnimate( "move_backward", EV_UseObject_Resetting );
|
|
}
|
|
|
|
void UseObject::Resetting( Event *ev )
|
|
{
|
|
SetActiveState( NULL );
|
|
animate->RandomAnimate( "start" );
|
|
|
|
setSolidType( SOLID_BBOX );
|
|
setContents( CONTENTS_BODY );
|
|
|
|
// reset the count
|
|
count = 1;
|
|
|
|
//
|
|
// fire off our trigger target if appropriate
|
|
//
|
|
if ( triggertarget.length() )
|
|
{
|
|
Event *event;
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do
|
|
{
|
|
ent = G_FindTarget( ent, triggertarget.c_str() );
|
|
if ( !ent )
|
|
{
|
|
break;
|
|
}
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( this );
|
|
ent->PostEvent( event, 0.0f );
|
|
}
|
|
while ( 1 );
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if ( reset_thread.length() )
|
|
{
|
|
if ( !ExecuteThread( reset_thread ) )
|
|
{
|
|
warning( "Resetting", "Null game script" );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UseObject::canBeUsed( const Vector &org, const 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( 3 );
|
|
|
|
//
|
|
// decrement the use
|
|
//
|
|
if ( count > 0 )
|
|
{
|
|
count--;
|
|
if ( !count )
|
|
{
|
|
setSolidType( SOLID_NOT );
|
|
setContents( 0 );
|
|
}
|
|
}
|
|
// 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( const 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--;
|
|
if ( !count )
|
|
{
|
|
setSolidType( SOLID_NOT );
|
|
setContents( 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void UseObject::Start( Event * ev )
|
|
{
|
|
//
|
|
// fire off the move_thread
|
|
//
|
|
if ( move_thread.length() )
|
|
{
|
|
if ( !ExecuteThread( move_thread ) )
|
|
{
|
|
warning( "Start", "Null game script" );
|
|
}
|
|
}
|
|
|
|
if ( ( spawnflags & MULTI_STATE ) && objectState )
|
|
{
|
|
animate->RandomAnimate( "move_backward", ev );
|
|
}
|
|
else
|
|
{
|
|
animate->RandomAnimate( "move", ev );
|
|
}
|
|
SetActiveState( NULL );
|
|
}
|
|
|
|
bool UseObject::Loop( void )
|
|
{
|
|
if ( !count )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void UseObject::Stop( Entity *activator )
|
|
{
|
|
if ( ( spawnflags & MULTI_STATE ) && objectState )
|
|
{
|
|
animate->RandomAnimate( "start" );
|
|
}
|
|
else
|
|
{
|
|
animate->RandomAnimate( "stop" );
|
|
}
|
|
|
|
//
|
|
// fire off our trigger target if appropriate
|
|
//
|
|
if ( triggertarget.length() )
|
|
{
|
|
Event *event;
|
|
Entity *ent;
|
|
|
|
ent = NULL;
|
|
do
|
|
{
|
|
ent = G_FindTarget( ent, triggertarget.c_str() );
|
|
if ( !ent )
|
|
{
|
|
break;
|
|
}
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( activator );
|
|
ent->PostEvent( event, 0.0f );
|
|
}
|
|
while ( 1 );
|
|
}
|
|
|
|
//
|
|
// fire off a thread if necessary
|
|
//
|
|
if ( ( spawnflags & MULTI_STATE ) && objectState )
|
|
{
|
|
if ( reset_thread.length() )
|
|
{
|
|
if ( !ExecuteThread( reset_thread ) )
|
|
{
|
|
warning( "Stop", "Null game script" );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( stop_thread.length() )
|
|
{
|
|
if ( !ExecuteThread( stop_thread ) )
|
|
{
|
|
warning( "Stop", "Null game script" );
|
|
}
|
|
}
|
|
}
|
|
|
|
// toggle the state
|
|
objectState ^= 1;
|
|
|
|
if ( reset_time )
|
|
{
|
|
count = 0;
|
|
PostEvent( EV_UseObject_Reset, reset_time );
|
|
}
|
|
|
|
SetActiveState( NULL );
|
|
}
|
|
|
|
void UseObject::SetActionType( Event *ev )
|
|
{
|
|
action_type = ev->GetString( 1 );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*QUAKED info_waypoint (0 0.5 0) (-8 -8 -8) (8 8 8)
|
|
|
|
Used as a positioning device for objects
|
|
|
|
******************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, Waypoint, "info_waypoint" )
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
|
|
/*****************************************************************************/
|
|
// TossObject
|
|
/*****************************************************************************/
|
|
|
|
Event EV_TossObject_SetBounceSound
|
|
(
|
|
"bouncesound",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"sound",
|
|
"When bouncing, what sound to play on impact"
|
|
);
|
|
Event EV_TossObject_SetBounceSoundChance
|
|
(
|
|
"bouncesoundchance",
|
|
EV_DEFAULT,
|
|
"f[0,1]",
|
|
"chance",
|
|
"When bouncing, the chance that the bounce sound will be played"
|
|
);
|
|
|
|
CLASS_DECLARATION( Entity, TossObject, "TossObject" )
|
|
{
|
|
{ &EV_Touch, &TossObject::Touch },
|
|
{ &EV_Stop, &TossObject::Stop },
|
|
{ &EV_TossObject_SetBounceSound, &TossObject::SetBounceSound },
|
|
{ &EV_TossObject_SetBounceSoundChance, &TossObject::SetBounceSoundChance },
|
|
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
TossObject::TossObject()
|
|
{
|
|
animate = new Animate( this );
|
|
|
|
if ( LoadingSavegame )
|
|
{
|
|
// Archive function will setup all necessary data
|
|
return;
|
|
}
|
|
setMoveType( MOVETYPE_GIB );
|
|
setSolidType( SOLID_NOT );
|
|
bouncesound = "";
|
|
bouncesoundchance = 1.0f;
|
|
}
|
|
|
|
TossObject::TossObject( const str &model )
|
|
{
|
|
animate = new Animate( this );
|
|
|
|
setMoveType( MOVETYPE_GIB );
|
|
setSolidType( SOLID_NOT );
|
|
bouncesound = "";
|
|
bouncesoundchance = 1.0f;
|
|
setModel( model );
|
|
}
|
|
|
|
void TossObject::SetBounceSound( const 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.0f + G_Random( 5.0f ) );
|
|
setAngles( Vector(0, 0, 0) );
|
|
animate->RandomAnimate( "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.0f ) < bouncesoundchance )
|
|
{
|
|
Sound( bouncesound );
|
|
}
|
|
}
|
|
}
|
|
|
|
void TossObject::SetVelocity( float severity )
|
|
{
|
|
setSolidType( SOLID_BBOX );
|
|
velocity[0] = 100.0f * crandom();
|
|
velocity[1] = 100.0f * crandom();
|
|
velocity[2] = 200.0f + 100.0f * random();
|
|
|
|
avelocity = Vector( G_Random( 600.0f ), G_Random( 600.0f ), G_Random( 600.0f ) );
|
|
|
|
velocity *= severity;
|
|
|
|
if (velocity[0] < -400.0f)
|
|
velocity[0] = -400.0f;
|
|
else if (velocity[0] > 400.0f)
|
|
velocity[0] = 400.0f;
|
|
if (velocity[1] < -400.0f)
|
|
velocity[1] = -400.0f;
|
|
else if (velocity[1] > 400.0f)
|
|
velocity[1] = 400.0f;
|
|
if (velocity[2] < 200.0f)
|
|
velocity[2] = 200.0f; // always some upwards
|
|
else if (velocity[2] > 600.0f)
|
|
velocity[2] = 600.0f;
|
|
animate->RandomAnimate( "idle" );
|
|
// we give it 8 seconds to fall, if not it will get faded out
|
|
PostEvent( EV_FadeOut, 8.0f );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*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."
|
|
);
|
|
Event EV_PushObject_SetDamage
|
|
(
|
|
"dmg",
|
|
EV_DEFAULT,
|
|
"i",
|
|
"damage",
|
|
"Set the damage."
|
|
);
|
|
Event EV_PushObject_SetPushSound
|
|
(
|
|
"pushsound",
|
|
EV_DEFAULT,
|
|
"s",
|
|
"sound",
|
|
"Set the pushing sound"
|
|
);
|
|
|
|
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 },
|
|
{ &EV_SetGameplayDamage, &PushObject::setDamage },
|
|
|
|
{ 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 );
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
//
|
|
// Name: setDamage
|
|
// Class: PushObject
|
|
//
|
|
// Description: This function acts as a filter to the real function.
|
|
// It gets data from the database, and then passes it
|
|
// along to the original event. This is here as an attempt
|
|
// to sway people into using the database standard instead of
|
|
// hardcoded numbers.
|
|
//
|
|
// Parameters: Event *ev
|
|
// str -- The value keyword from the database (low, medium, high, etc).
|
|
//
|
|
// Returns: None
|
|
//
|
|
//--------------------------------------------------------------
|
|
void PushObject::setDamage( Event *ev )
|
|
{
|
|
if ( ev->NumArgs() < 1 )
|
|
return;
|
|
|
|
GameplayManager *gpm = GameplayManager::getTheGameplayManager();
|
|
if ( !gpm->hasFormula("OffensiveDamage") )
|
|
return;
|
|
|
|
str damagestr = ev->GetString( 1 );
|
|
float damagemod = 1.0f;
|
|
if ( gpm->getDefine(damagestr) != "" )
|
|
damagemod = (float)atof(gpm->getDefine(damagestr));
|
|
GameplayFormulaData fd(this, 0, 0, "");
|
|
float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod);
|
|
Event *newev = new Event(EV_PushObject_SetDamage);
|
|
newev->AddInteger((int)finaldamage);
|
|
ProcessEvent(newev);
|
|
}
|
|
|
|
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.0f, 1.0f, 2.0f ), maxs - Vector( 1.0f, 1.0f, 1.0f ) );
|
|
}
|
|
|
|
qboolean PushObject::canPush( const 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, const Vector &move )
|
|
{
|
|
trace_t trace;
|
|
|
|
if ( pushsound.length() )
|
|
{
|
|
if ( !edict->s.loopSound )
|
|
{
|
|
LoopSound( pushsound );
|
|
PostEvent( EV_StopLoopSound, level.frametime * 5.0f );
|
|
}
|
|
}
|
|
|
|
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.0f ) )
|
|
{
|
|
owner = pusher;
|
|
|
|
G_PushMove( this, trace.endpos - origin, vec_zero );
|
|
|
|
if ( edict->s.loopSound )
|
|
PostponeEvent( EV_StopLoopSound, level.frametime );
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
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_CODEONLY,
|
|
NULL,
|
|
NULL,
|
|
"sent to entity when touched."
|
|
);
|
|
Event EV_FallingRock_Rotate
|
|
(
|
|
"rotate",
|
|
EV_SCRIPTONLY,
|
|
NULL,
|
|
NULL,
|
|
"rotates the falling rock."
|
|
);
|
|
Event EV_FallingRock_SetWait
|
|
(
|
|
"wait",
|
|
EV_SCRIPTONLY,
|
|
"f",
|
|
"wait",
|
|
"How long to wait before rock starts falling."
|
|
);
|
|
Event EV_FallingRock_Start
|
|
(
|
|
"start",
|
|
EV_DEFAULT,
|
|
NULL,
|
|
NULL,
|
|
"Starts rock falling."
|
|
);
|
|
Event EV_FallingRock_SetDmg
|
|
(
|
|
"dmg",
|
|
EV_SCRIPTONLY,
|
|
"i",
|
|
"dmg",
|
|
"Set the damage from the rock."
|
|
);
|
|
Event EV_FallingRock_SetSpeed
|
|
(
|
|
"speed",
|
|
EV_DEFAULT,
|
|
"f",
|
|
"speed",
|
|
"Set the speed that the rock moves at."
|
|
);
|
|
Event EV_FallingRock_SetBounceSound
|
|
(
|
|
"noise",
|
|
EV_SCRIPTONLY,
|
|
"s",
|
|
"sound",
|
|
"Set the sound to play when the rock bounces"
|
|
);
|
|
|
|
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 },
|
|
{ &EV_SetGameplayDamage, &FallingRock::setDamage },
|
|
|
|
{ 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" );
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
//
|
|
// Name: setDamage
|
|
// Class: FallingRock
|
|
//
|
|
// Description: This function acts as a filter to the real function.
|
|
// It gets data from the database, and then passes it
|
|
// along to the original event. This is here as an attempt
|
|
// to sway people into using the database standard instead of
|
|
// hardcoded numbers.
|
|
//
|
|
// Parameters: Event *ev
|
|
// str -- The value keyword from the database (low, medium, high, etc).
|
|
//
|
|
// Returns: None
|
|
//
|
|
//--------------------------------------------------------------
|
|
void FallingRock::setDamage( Event *ev )
|
|
{
|
|
if ( ev->NumArgs() < 1 )
|
|
return;
|
|
|
|
GameplayManager *gpm = GameplayManager::getTheGameplayManager();
|
|
if ( !gpm->hasFormula("OffensiveDamage") )
|
|
return;
|
|
|
|
str damagestr = ev->GetString( 1 );
|
|
float damagemod = 1.0f;
|
|
if ( gpm->getDefine(damagestr) != "" )
|
|
damagemod = (float)atof(gpm->getDefine(damagestr));
|
|
GameplayFormulaData fd(this, 0, 0, "");
|
|
float finaldamage = gpm->calculate("OffensiveDamage", fd, damagemod);
|
|
Event *newev = new Event(EV_FallingRock_SetDmg);
|
|
newev->AddInteger((int)finaldamage);
|
|
ProcessEvent(newev);
|
|
}
|
|
|
|
|
|
Entity *FallingRock::SetNextBounceDir( void )
|
|
{
|
|
Entity *ent;
|
|
|
|
if ( !current->target.length() )
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
ent = 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.0f );
|
|
}
|
|
|
|
if ( time < 0.4f )
|
|
{
|
|
time = 0.4f;
|
|
}
|
|
}
|
|
|
|
vertical_speed = ( delta.z / time ) + ( 0.5f * gravity * sv_currentGravity->value * time );
|
|
if ( vertical_speed < 0.0f )
|
|
{
|
|
vertical_speed = 0.0f;
|
|
}
|
|
|
|
velocity = xydelta * xy_speed;
|
|
velocity.z = vertical_speed;
|
|
|
|
Vector ang( 0.0f, vectoyaw( delta ), 0.0f );
|
|
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( const str &sound )
|
|
{
|
|
bouncesound = sound;
|
|
// cache the sound in
|
|
CacheResource( bouncesound.c_str(), this );
|
|
}
|
|
|
|
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.0f )
|
|
{
|
|
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.0f );
|
|
}
|
|
|
|
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.0f * edict->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.0f );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NextBounce();
|
|
break;
|
|
}
|
|
}
|
|
while( 1 );
|
|
}
|