1681 lines
42 KiB
C++
1681 lines
42 KiB
C++
// 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 "g_local.h"
|
|
#include "entity.h"
|
|
#include "trigger.h"
|
|
#include "explosion.h"
|
|
#include "areaportal.h"
|
|
#include "misc.h"
|
|
#include "navigate.h"
|
|
#include "deadbody.h"
|
|
#include "specialfx.h"
|
|
#include "player.h"
|
|
|
|
/*
|
|
================
|
|
SendDialog
|
|
================
|
|
*/
|
|
void SendDialog
|
|
(
|
|
const char *icon_name,
|
|
const char *dialog_text
|
|
)
|
|
{
|
|
char imageindex;
|
|
char temp[ 1024 ];
|
|
|
|
imageindex = gi.imageindex( icon_name );
|
|
Com_sprintf( temp, sizeof( temp ), "dia %d \"%s\"", imageindex, dialog_text );
|
|
gi.WriteByte( svc_console_command );
|
|
gi.WriteString( temp );
|
|
gi.multicast( vec3_origin, MULTICAST_ALL );
|
|
}
|
|
|
|
/*
|
|
================
|
|
SendOverlay
|
|
================
|
|
*/
|
|
void SendOverlay
|
|
(
|
|
Entity *ent,
|
|
str overlayname
|
|
)
|
|
{
|
|
if ( ent )
|
|
{
|
|
gi.WriteByte( svc_console_command );
|
|
gi.WriteString( va( "lo %s",overlayname.c_str() ) );
|
|
gi.unicast ( ent->edict, true);
|
|
}
|
|
else
|
|
{
|
|
gi.WriteByte( svc_console_command );
|
|
gi.WriteString( va( "lo %s",overlayname.c_str() ) );
|
|
gi.multicast( NULL, MULTICAST_ALL );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SendIntermission
|
|
================
|
|
*/
|
|
void SendIntermission
|
|
(
|
|
Entity *ent,
|
|
str intermissionname
|
|
)
|
|
{
|
|
if ( ent )
|
|
{
|
|
gi.WriteByte( svc_console_command );
|
|
gi.WriteString( va( "imf %s",intermissionname.c_str() ) );
|
|
gi.unicast ( ent->edict, true);
|
|
}
|
|
else
|
|
{
|
|
gi.WriteByte( svc_console_command );
|
|
gi.WriteString( va( "imf %s",intermissionname.c_str() ) );
|
|
gi.multicast( NULL, MULTICAST_ALL );
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_group (0 0 0) ?
|
|
|
|
Used to group brushes together just for editor convenience.
|
|
|
|
/*****************************************************************************/
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_remove (0 0.5 0) ?
|
|
|
|
Used for lighting and such
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, FuncRemove, "func_remove" );
|
|
|
|
ResponseDef FuncRemove::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
FuncRemove::FuncRemove()
|
|
{
|
|
ProcessEvent( EV_Remove );
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/*SINED info_null (0 0.5 0) (-4 -4 -4) (4 4 4)
|
|
|
|
Used as a positional target for spotlights, etc.
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, InfoNull, "info_null" );
|
|
|
|
ResponseDef InfoNull::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
InfoNull::InfoNull()
|
|
{
|
|
ProcessEvent( EV_Remove );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED 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" );
|
|
|
|
ResponseDef InfoNotNull::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_electrocute (0 .5 .8) ?
|
|
"radius" - range of the effect (Default is 500)
|
|
"key" The item needed to activate this. (default nothing)
|
|
Electrocutes everything it can see if it is in the water
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Trigger, Electrocute, "func_electrocute" );
|
|
|
|
ResponseDef Electrocute::Responses[] =
|
|
{
|
|
{ &EV_Trigger_Effect, ( Response )Electrocute::KillSight },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Electrocute::Electrocute()
|
|
{
|
|
setOrigin( origin );
|
|
setSolidType( SOLID_NOT );
|
|
setMoveType( MOVETYPE_NONE );
|
|
|
|
radius = G_GetFloatArg("radius", 500);
|
|
}
|
|
|
|
void Electrocute::KillSight(Event *ev)
|
|
{
|
|
Entity *other = ev->GetEntity( 1 );
|
|
Entity *ent;
|
|
|
|
ent = findradius( NULL, worldorigin, radius );
|
|
while( ent )
|
|
{
|
|
if ( ( ent != this ) && ( !ent->deadflag ) )
|
|
{
|
|
if (ent->waterlevel)
|
|
{
|
|
ent->Damage( this, other, ent->health, ent->worldorigin, vec_zero, vec_zero, 0, DAMAGE_NO_ARMOR, MOD_ELECTRIC, -1, -1, 1.0f );
|
|
}
|
|
}
|
|
ent = findradius( ent, worldorigin, radius );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_spawn(0 .5 .8) (-8 -8 -8) (8 8 8)
|
|
"modelname" The name of the .def file you wish to spawn. (Required)
|
|
"spawntargetname" This will be the targetname of the spawned model. (default is null)
|
|
"key" The item needed to activate this. (default nothing)
|
|
"attackmode" Attacking mode of the spawned actor (default 0)
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, Spawn, "func_spawn" );
|
|
|
|
ResponseDef Spawn::Responses[] =
|
|
{
|
|
{ &EV_Activate, ( Response )Spawn::DoSpawn },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Spawn::Spawn()
|
|
{
|
|
setSolidType( SOLID_NOT );
|
|
setMoveType( MOVETYPE_NONE );
|
|
hideModel();
|
|
modelname = G_GetStringArg( "modelname", NULL );
|
|
angles = Vector( va( "0 %f 0", G_GetFloatArg( "angle", 0 ) ) );
|
|
|
|
if ( !modelname.length() )
|
|
warning("Spawn", "modelname not set" );
|
|
|
|
spawntargetname = G_GetStringArg( "spawntargetname", NULL );
|
|
attackmode = G_GetIntArg( "attackmode", 0 );
|
|
}
|
|
|
|
void Spawn::DoSpawn( Event *ev )
|
|
{
|
|
char temp[ 128 ];
|
|
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
|
|
sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] );
|
|
G_SetSpawnArg( "origin", temp );
|
|
sprintf( temp, "%f", angles[ 1 ] );
|
|
G_SetSpawnArg( "angle", temp );
|
|
G_SetSpawnArg( "model", modelname.c_str() );
|
|
G_SetSpawnArg( "targetname", spawntargetname.c_str() );
|
|
G_SetSpawnArg( "attackmode", va( "%i",attackmode ) );
|
|
G_SetSpawnArg("gravityaxis", va("%i",gravaxis)); //###
|
|
|
|
G_CallSpawn();
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_respawn(0 .5 .8) (-8 -8 -8) (8 8 8)
|
|
When the thing that is spawned is killed, this func_respawn will get
|
|
triggered.
|
|
"modelname" The name of the .def file you wish to spawn. (Required)
|
|
"spawntargetname" This will be the targetname of the spawned model. (default is null)
|
|
"key" The item needed to activate this. (default nothing)
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Spawn, ReSpawn, "func_respawn" );
|
|
|
|
ResponseDef ReSpawn::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
void ReSpawn::DoSpawn( Event *ev )
|
|
{
|
|
char temp[ 128 ];
|
|
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
|
|
sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] );
|
|
G_SetSpawnArg( "origin", temp );
|
|
sprintf( temp, "%f", angles[ 1 ] );
|
|
G_SetSpawnArg( "angle", temp );
|
|
G_SetSpawnArg( "model", modelname.c_str() );
|
|
|
|
// This will trigger the func_respawn when the thing dies
|
|
G_SetSpawnArg( "targetname", TargetName() );
|
|
G_SetSpawnArg( "target", TargetName() );
|
|
|
|
G_CallSpawn();
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_spawnoutofsight(0 .5 .8) (-8 -8 -8) (8 8 8)
|
|
Will only spawn something out of sight of its targets.
|
|
"modelname" The name of the .def file you wish to spawn. (Required)
|
|
"spawntargetname" This will be the targetname of the spawned model. (default is null)
|
|
"key" The item needed to activate this. (default nothing)
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Spawn, SpawnOutOfSight, "func_spawnoutofsight" );
|
|
|
|
ResponseDef SpawnOutOfSight::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
void SpawnOutOfSight::DoSpawn
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
char temp[ 128 ];
|
|
int i;
|
|
Entity *ent;
|
|
edict_t *ed;
|
|
trace_t trace;
|
|
qboolean seen = false;
|
|
|
|
// Check to see if I can see any players before spawning
|
|
for( i = 0; i < game.maxclients; i++ )
|
|
{
|
|
ed = &g_edicts[ 1 + i ];
|
|
if ( !ed->inuse || !ed->entity )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ent = ed->entity;
|
|
if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->centroid, this, MASK_OPAQUE, "SpawnOutOfSight::DoSpawn" );
|
|
if ( trace.fraction == 1.0 )
|
|
{
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( seen )
|
|
return;
|
|
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
|
|
sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] );
|
|
G_SetSpawnArg( "origin", temp );
|
|
sprintf( temp, "%f", angles[ 1 ] );
|
|
G_SetSpawnArg( "angle", temp );
|
|
G_SetSpawnArg( "model", modelname.c_str() );
|
|
G_SetSpawnArg( "targetname", spawntargetname.c_str() );
|
|
|
|
G_CallSpawn();
|
|
// Clear the spawn args
|
|
G_InitSpawnArguments();
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_spawnchain(0 .5 .8) (-8 -8 -8) (8 8 8)
|
|
Tries to spawn something out of the sight of players. If it fails, it will
|
|
trigger its targets.
|
|
"modelname" The name of the .def file you wish to spawn. (Required)
|
|
"spawntargetname" This will be the targetname of the spawned model. (default is null)
|
|
"key" The item needed to activate this. (default nothing)
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Spawn, SpawnChain, "func_spawnchain" );
|
|
|
|
ResponseDef SpawnChain::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
void SpawnChain::DoSpawn
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
char temp[ 128 ];
|
|
int i,num;
|
|
Entity *ent;
|
|
edict_t *ed;
|
|
trace_t trace;
|
|
qboolean seen = false;
|
|
const char *name;
|
|
Event *event;
|
|
|
|
// Check to see if this can see any players before spawning
|
|
for( i = 0; i < game.maxclients; i++ )
|
|
{
|
|
ed = &g_edicts[ 1 + i ];
|
|
if ( !ed->inuse || !ed->entity )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ent = ed->entity;
|
|
if ( ( ent->health < 0 ) || ( ent->flags & FL_NOTARGET ) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
trace = G_Trace( worldorigin, vec_zero, vec_zero, ent->centroid, this, MASK_OPAQUE, "SpawnChain::DoSpawn" );
|
|
if ( trace.fraction == 1.0 )
|
|
{
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Couldn't spawn anything, so activate targets
|
|
if ( seen )
|
|
{
|
|
name = Target();
|
|
if ( name && strcmp( name, "" ) )
|
|
{
|
|
num = 0;
|
|
do
|
|
{
|
|
num = G_FindTarget( num, name );
|
|
if ( !num )
|
|
{
|
|
break;
|
|
}
|
|
ent = G_GetEntity( num );
|
|
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( world );
|
|
ent->PostEvent( event, 0 );
|
|
} while ( 1 );
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Can't see the player, so do the spawn
|
|
G_InitSpawnArguments();
|
|
sprintf( temp, "%f %f %f", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] );
|
|
G_SetSpawnArg( "origin", temp );
|
|
sprintf( temp, "%f", angles[ 1 ] );
|
|
G_SetSpawnArg( "angle", temp );
|
|
G_SetSpawnArg( "model", modelname.c_str() );
|
|
G_SetSpawnArg( "targetname", spawntargetname.c_str() );
|
|
G_CallSpawn();
|
|
G_InitSpawnArguments();
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_wall (0 .5 .8) ?
|
|
|
|
This is just a solid wall if not inhibitted
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, Wall, "func_wall" );
|
|
|
|
ResponseDef Wall::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Wall::Wall()
|
|
{
|
|
setOrigin( origin );
|
|
setSolidType( SOLID_BSP );
|
|
setMoveType( MOVETYPE_PUSH );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_illusionary (0 .5 .8) ?
|
|
|
|
A simple entity that looks solid but lets you walk through it.
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, IllusionaryWall, "func_illusionary" );
|
|
|
|
ResponseDef IllusionaryWall::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
IllusionaryWall::IllusionaryWall()
|
|
{
|
|
setSolidType( SOLID_NOT );
|
|
setMoveType( MOVETYPE_NONE );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_breakawaywall (0 .5 .8) ? x x NOT_PLAYERS MONSTERS PROJECTILES
|
|
|
|
Special walltype that removes itself when triggered. Will also trigger
|
|
any func_areaportals that it targets.
|
|
|
|
"key" The item needed to activate this. (default nothing)
|
|
|
|
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.)
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( TriggerOnce, BreakawayWall, "func_breakawaywall" );
|
|
|
|
Event EV_BreakawayWall_Setup( "BreakawayWall_Setup" );
|
|
|
|
ResponseDef BreakawayWall::Responses[] =
|
|
{
|
|
{ &EV_Touch, NULL },
|
|
{ &EV_Trigger_Effect, ( Response )BreakawayWall::BreakWall },
|
|
{ &EV_BreakawayWall_Setup, ( Response )BreakawayWall::Setup },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
void BreakawayWall::BreakWall
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
SetAreaPortals( Target(), true );
|
|
ActivateTargets( ev );
|
|
}
|
|
|
|
void BreakawayWall::Setup
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
SetAreaPortals( Target(), false );
|
|
}
|
|
|
|
BreakawayWall::BreakawayWall()
|
|
{
|
|
showModel();
|
|
setMoveType( MOVETYPE_PUSH );
|
|
setSolidType( SOLID_BSP );
|
|
PostEvent( EV_BreakawayWall_Setup, 0.1 );
|
|
respondto = spawnflags ^ TRIGGER_PLAYERS;
|
|
};
|
|
|
|
//### added explosion scale control
|
|
/*****************************************************************************/
|
|
/*SINED func_explodingwall (0 .5 .8) ? 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 0 )
|
|
"anglespeed" Speed at which pieces rotate ( default 100 ) \
|
|
if RANDOMANGLES ( default is 600 )
|
|
"key" The item needed to activate this. (default nothing)
|
|
"scale" is the scale of the explosions to make. Default is 1.
|
|
|
|
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 )
|
|
|
|
CLASS_DECLARATION( Trigger, ExplodingWall, "func_explodingwall" );
|
|
|
|
Event EV_ExplodingWall_StopRotating( "stoprotating" );
|
|
Event EV_ExplodingWall_OnGround( "checkonground" );
|
|
|
|
ResponseDef ExplodingWall::Responses[] =
|
|
{
|
|
{ &EV_Trigger_Effect, ( Response )Explode },
|
|
{ &EV_Damage, ( Response )DamageEvent },
|
|
{ &EV_Touch, ( Response )TouchFunc },
|
|
{ &EV_ExplodingWall_StopRotating, ( Response )StopRotating },
|
|
{ &EV_ExplodingWall_OnGround, ( Response )CheckOnGround },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
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, 1.0f, true, this, other, this );
|
|
CreateExplosion( pos, dmg, explosionscale, true, this, other, this );
|
|
//###
|
|
}
|
|
|
|
// throw itself
|
|
state = 1;
|
|
on_ground = false;
|
|
PostEvent( EV_ExplodingWall_OnGround, 0.1f );
|
|
velocity[ 0 ] = G_CRandom( 70 );
|
|
velocity[ 1 ] = G_CRandom( 70 );
|
|
velocity[ 2 ] = 140 + G_Random( 70 );
|
|
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 - worldangles;
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( attacker );
|
|
ProcessEvent( event );
|
|
}
|
|
|
|
void ExplodingWall::GroundDamage
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Vector dir;
|
|
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, 1.0f, true, this, attacker, this );
|
|
CreateExplosion( pos, damage, explosionscale, true, this, attacker, this );
|
|
//###
|
|
}
|
|
takedamage = DAMAGE_NO;
|
|
hideModel();
|
|
dir = worldorigin - attacker->worldorigin;
|
|
TesselateModel
|
|
(
|
|
this,
|
|
tess_min_size,
|
|
tess_max_size,
|
|
dir,
|
|
damage,
|
|
tess_percentage,
|
|
tess_thickness,
|
|
vec3_origin
|
|
);
|
|
ProcessEvent( EV_BreakingSound );
|
|
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 - worldangles;
|
|
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, 0.1f );
|
|
}
|
|
|
|
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, 1.0f, true, this, other, this );
|
|
CreateExplosion( pos, dmg, explosionscale, true, this, other, this );
|
|
//###
|
|
}
|
|
hideModel();
|
|
TesselateModel
|
|
(
|
|
this,
|
|
tess_min_size,
|
|
tess_max_size,
|
|
vec_zero,
|
|
100,
|
|
tess_percentage,
|
|
tess_thickness,
|
|
vec3_origin
|
|
);
|
|
ProcessEvent( EV_BreakingSound );
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
if ( other->takedamage )
|
|
{
|
|
other->Damage( this, activator, dmg, worldorigin, vec_zero, vec_zero, 20, 0, MOD_EXPLODEWALL, -1, -1, 1.0f );
|
|
RandomGlobalSound( "debris_generic", 1, CHAN_WEAPON, ATTN_NORM );
|
|
attack_finished = level.time + 0.1;
|
|
}
|
|
}
|
|
|
|
ExplodingWall::ExplodingWall()
|
|
{
|
|
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( origin );
|
|
|
|
health = G_GetFloatArg( "health", 60 );
|
|
max_health = health;
|
|
on_ground = false;
|
|
|
|
state = 0;
|
|
if ( spawnflags & RANDOMANGLES )
|
|
angle_speed = G_GetFloatArg( "anglespeed", 600 );
|
|
else
|
|
angle_speed = G_GetFloatArg( "anglespeed", 100 );
|
|
|
|
land_radius = G_GetFloatArg( "land_radius", 0 );
|
|
land_angles = G_GetVectorArg( "land_angles" );
|
|
dmg = G_GetIntArg( "dmg", 10 );
|
|
explosions = G_GetIntArg( "explosions", 1 );
|
|
|
|
explosionscale = G_GetFloatArg("scale", 1); //###
|
|
|
|
orig_mins = mins;
|
|
orig_maxs = maxs;
|
|
|
|
respondto = spawnflags ^ TRIGGER_PLAYERS;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED detail (0.5 0 1.0) ?
|
|
|
|
Used to fake details before the Quake 2 merge.
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, Detail, "detail" );
|
|
|
|
ResponseDef Detail::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Detail::Detail()
|
|
{
|
|
// Be an asshole to the level designers so that they make the change asap.
|
|
gi.dprintf( "Detail brushes are no longer needed. Use Surface attributes.\n" );
|
|
|
|
if ( !G_GetSpawnArg( "model" ) )
|
|
{
|
|
gi.dprintf( "Detail brush with NULL model removed!!!\n" );
|
|
ProcessEvent( EV_Remove );
|
|
}
|
|
else
|
|
{
|
|
ProcessEvent( EV_Remove );
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED misc_oxygen (1 0 0) ? VISIBLE
|
|
|
|
Touching this entity will reset the drowning time - only
|
|
responds to players.
|
|
|
|
"key" The item needed to activate this. (default nothing)
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Trigger, Oxygenator, "misc_oxygen" );
|
|
|
|
ResponseDef Oxygenator::Responses[] =
|
|
{
|
|
{ &EV_Trigger_Effect, ( Response )Oxygenator::Oxygenate },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Oxygenator::Oxygenator()
|
|
{
|
|
if ( spawnflags & 1 )
|
|
{
|
|
showModel();
|
|
}
|
|
|
|
time = 20;
|
|
respondto = TRIGGER_PLAYERS;
|
|
}
|
|
|
|
EXPORT_FROM_DLL void Oxygenator::Oxygenate
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *other;
|
|
Player *player;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
if ( !other )
|
|
return;
|
|
|
|
player = ( Player * )( Sentient * )other;
|
|
player->GiveOxygen( time );
|
|
}
|
|
|
|
//### added stuff for hoverbikes
|
|
#define NOEFFECTS 64
|
|
#define OFFSET_MOVE 128
|
|
/*****************************************************************************/
|
|
/*SINED misc_teleporter (1 0 0) ? VISIBLE x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES x NOEFFECTS OFFSET_MOVE
|
|
|
|
Touching this entity will teleport players to the targeted object.
|
|
|
|
"key" The item needed to activate this. (default nothing)
|
|
|
|
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 NOEFFECTS is set, it will not make a teleporting sound or particles.
|
|
If OFFSET_MOVE is set, it will "offset" the player to the destination, not actually teleport him.
|
|
|
|
"gravityaxis" now sets player's axis of gravity when teleported. Valid values are 0 to 5. Here's list of what orientation goes with which value.
|
|
0: upright
|
|
1: South is down
|
|
2: East is down
|
|
3: upsidedown
|
|
4: North is down
|
|
5: West is down
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Trigger, Teleporter, "misc_teleporter" );
|
|
|
|
ResponseDef Teleporter::Responses[] =
|
|
{
|
|
{ &EV_Trigger_Effect, ( Response )Teleporter::Teleport },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
EXPORT_FROM_DLL void Teleporter::Teleport
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
gclient_t *client;
|
|
Entity *dest;
|
|
int num;
|
|
int i;
|
|
Entity *other;
|
|
Vector mid;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
if ( !other || ( other == world ) )
|
|
return;
|
|
|
|
num = G_FindTarget( 0, Target() );
|
|
if ( !num )
|
|
{
|
|
warning( "Teleport", "Couldn't find destination\n" );
|
|
return;
|
|
}
|
|
|
|
dest = G_GetEntity( num );
|
|
assert( dest );
|
|
|
|
//### spawnflag that disables the teleporting effects
|
|
if(!(spawnflags & NOEFFECTS))
|
|
{
|
|
other->RandomGlobalSound( "snd_teleport" );
|
|
}
|
|
//###
|
|
|
|
// unlink to make sure it can't possibly interfere with KillBox
|
|
other->unlink();
|
|
|
|
//### added OFFSET_MOVE type of teleporting
|
|
if(spawnflags & OFFSET_MOVE)
|
|
{
|
|
mid = (absmax + absmin) * 0.5;
|
|
mid[2] = absmin[2];
|
|
other->origin -= mid;
|
|
other->origin += dest->origin;
|
|
}
|
|
// if ( other->isSubclassOf( Sentient ) )
|
|
else if ( other->isSubclassOf( Sentient ) )
|
|
//###
|
|
{
|
|
PathManager.Teleport( other, other->worldorigin, dest->worldorigin );
|
|
other->worldorigin = dest->worldorigin + Vector( 0, 0, 1 );
|
|
other->velocity = vec_zero;
|
|
//### raise player up a bit if on a hoverbike
|
|
if(((Sentient *)other)->IsOnBike())
|
|
{
|
|
other->origin[gravity_axis[gravaxis].z] += 24*gravity_axis[gravaxis].sign;
|
|
}
|
|
//###
|
|
}
|
|
else
|
|
{
|
|
mid = ( absmax - absmin ) * 0.5;
|
|
other->worldorigin = dest->worldorigin + Vector( 0, 0, 1 );
|
|
other->origin += mid;
|
|
}
|
|
|
|
// draw the teleport splash at the destination
|
|
//other->edict->s.event = EV_PLAYER_TELEPORT;
|
|
|
|
// set angles
|
|
//### added OFFSET_MOVE type of teleporting
|
|
if(!(spawnflags & OFFSET_MOVE))
|
|
{
|
|
// set angles
|
|
other->setAngles( dest->angles );
|
|
}
|
|
//###
|
|
|
|
// set their gravity axis
|
|
other->SetGravityAxis( gravaxis );
|
|
|
|
if ( other->client )
|
|
{
|
|
Event * ev;
|
|
client = other->client;
|
|
|
|
if ( !gravaxis )
|
|
{
|
|
// clear the velocity and hold them in place briefly
|
|
client->ps.pmove.pm_time = 100;
|
|
client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
|
|
|
|
//### spawnflag that disables the teleporting effects
|
|
if(!(spawnflags & NOEFFECTS))
|
|
{
|
|
ev = new Event( EV_Player_SaveFov );
|
|
other->ProcessEvent( ev );
|
|
|
|
ev = new Event( EV_Player_Fov );
|
|
ev->AddFloat( 180 );
|
|
other->ProcessEvent( ev );
|
|
}
|
|
//###
|
|
}
|
|
|
|
/*
|
|
if ( gravaxis )
|
|
{
|
|
ev = new Event( EV_Player_RestoreFov );
|
|
other->PostEvent( ev, 0.1f );
|
|
}
|
|
*/
|
|
|
|
//### added OFFSET_MOVE type of teleporting
|
|
if(!(spawnflags & OFFSET_MOVE))
|
|
{
|
|
for( i = 0; i < 3; i++ )
|
|
{
|
|
client->ps.pmove.delta_angles[ i ] = ANGLE2SHORT( dest->angles[ i ] - client->resp.cmd_angles[ i ] );
|
|
}
|
|
|
|
VectorCopy( angles.vec3(), client->ps.viewangles );
|
|
}
|
|
//###
|
|
}
|
|
|
|
if ( dest->isSubclassOf( TeleporterDestination ) && !gravaxis )
|
|
{
|
|
//### added OFFSET_MOVE type of teleporting
|
|
if(!(spawnflags & OFFSET_MOVE))
|
|
{
|
|
float len;
|
|
|
|
len = other->velocity.length();
|
|
//
|
|
// give them a bit of a push
|
|
//
|
|
if ( len < 400 )
|
|
len = 400;
|
|
//### corrected for different gravityaxies
|
|
// other->velocity = ( ( TeleporterDestination * )dest )->movedir * len;
|
|
mid = ( ( TeleporterDestination * )dest )->movedir * len;
|
|
other->velocity[0] = mid[gravity_axis[other->gravaxis].x];
|
|
other->velocity[1] = mid[gravity_axis[other->gravaxis].y]*gravity_axis[other->gravaxis].sign;
|
|
other->velocity[2] = mid[gravity_axis[other->gravaxis].z]*gravity_axis[other->gravaxis].sign;
|
|
}
|
|
//###
|
|
}
|
|
|
|
// kill anything at the destination
|
|
KillBox( other );
|
|
|
|
other->setOrigin( other->worldorigin );
|
|
other->worldorigin.copyTo( other->edict->s.old_origin );
|
|
|
|
//### check if teleporting a player on a hoverbike
|
|
if(other->isClient())
|
|
{
|
|
Hoverbike *bike;
|
|
Player *rider;
|
|
|
|
rider = (Player *)other;
|
|
bike = rider->GetHoverbike();
|
|
// he's on a bike, so move the bike too
|
|
if(bike)
|
|
{
|
|
bike->SetGravityAxis( gravaxis );
|
|
|
|
bike->setOrigin(other->origin);
|
|
bike->worldorigin.copyTo(bike->edict->s.old_origin);
|
|
|
|
if(dest->isSubclassOf(TeleporterDestination))
|
|
{
|
|
float len;
|
|
|
|
if(!(spawnflags & OFFSET_MOVE))
|
|
{
|
|
len = bike->velocity.length();
|
|
// rider velocity has already been adjusted
|
|
bike->velocity = rider->velocity;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
//###
|
|
}
|
|
|
|
Teleporter::Teleporter()
|
|
{
|
|
if ( !Target() )
|
|
{
|
|
gi.dprintf( "teleporter without a target.\n" );
|
|
ProcessEvent( EV_Remove );
|
|
return;
|
|
}
|
|
|
|
if ( spawnflags & 1 )
|
|
{
|
|
showModel();
|
|
}
|
|
|
|
//### for hoverbikes
|
|
// respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES );
|
|
respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES | TRIGGER_HOVERBIKES);
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED misc_teleporter_dest (1 0 0) (-32 -32 0) (32 32 8)
|
|
|
|
Point teleporters at these.
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, TeleporterDestination, "misc_teleporter_dest" );
|
|
|
|
ResponseDef TeleporterDestination::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
TeleporterDestination::TeleporterDestination()
|
|
{
|
|
movedir = G_GetMovedir();
|
|
setAngles( movedir.toAngles() );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED waypoint (0 0.5 0) (-8 -8 -8) (8 8 8)
|
|
|
|
Used as a positioning device for objects
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, Waypoint, "waypoint" );
|
|
|
|
ResponseDef Waypoint::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_shatter (0 .5 .8) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES HURT_SHATTER THRESHOLD
|
|
|
|
For shattering objects. Triggers only when a threshold of damage is exceeded.
|
|
Will also trigger any targeted func_areaportals when not invisible.
|
|
|
|
"health" specifies how much damage must occur before trigger fires. Default is 20.
|
|
"percentage" specifies how much of the thing to shatter. Default is 50
|
|
"minsize" specifies minsize for tesselation, default based off size
|
|
"maxsize" specifies maxsize for tesselation, default based off size
|
|
"thickness" specifies thickness for tesselation, default same as minsize
|
|
"key" The item needed to activate this. (default nothing)
|
|
"noise" sound to play when shattered, defaults to nothing
|
|
|
|
HURT_SHATTER - when the thing gets hurt, spawn pieces of itself
|
|
THRESHOLD - damage threshold behavior
|
|
|
|
If NOT_PLAYERS is set, the trigger does not respond to damage caused by players
|
|
If NOT_MONSTERS is set, the trigger does not respond to damage caused by monsters
|
|
If NOT_PROJECTILES is set, the trigger does not respond to damage caused by projectiles
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Trigger, Shatter, "func_shatter" );
|
|
|
|
ResponseDef Shatter::Responses[] =
|
|
{
|
|
{ &EV_Trigger_Effect, ( Response )Shatter ::DoShatter },
|
|
{ &EV_Damage, ( Response )Shatter ::DamageEvent },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
void Shatter::DamageEvent
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Event *event;
|
|
Entity *inflictor;
|
|
Entity *attacker;
|
|
int damage;
|
|
|
|
if ( takedamage == DAMAGE_NO )
|
|
{
|
|
return;
|
|
}
|
|
|
|
damage = ev->GetInteger( 1 );
|
|
inflictor = ev->GetEntity( 2 );
|
|
attacker = ev->GetEntity( 3 );
|
|
|
|
if ( threshold && damage < health )
|
|
{
|
|
return;
|
|
}
|
|
else if ( !threshold )
|
|
{
|
|
health -= damage;
|
|
if (health > 0 )
|
|
{
|
|
damage_taken += damage;
|
|
return;
|
|
}
|
|
}
|
|
|
|
damage_taken += damage;
|
|
|
|
if ( attacker )
|
|
{
|
|
event = new Event( EV_Activate );
|
|
event->AddEntity( attacker );
|
|
ProcessEvent( event );
|
|
}
|
|
else
|
|
{
|
|
warning("Damage", "Attacker is null\n" );
|
|
}
|
|
}
|
|
|
|
void Shatter::DoShatter
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Entity *other;
|
|
Event *event;
|
|
Vector dir;
|
|
|
|
if ( takedamage == DAMAGE_NO )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( noise.length() > 1 )
|
|
{
|
|
sound( noise.c_str(), 1, CHAN_VOICE + CHAN_NO_PHS_ADD );
|
|
}
|
|
|
|
other = ev->GetEntity( 1 );
|
|
|
|
takedamage = DAMAGE_NO;
|
|
|
|
dir = worldorigin - other->worldorigin;
|
|
|
|
TesselateModel
|
|
(
|
|
this,
|
|
tess_min_size,
|
|
tess_max_size,
|
|
dir,
|
|
damage_taken,
|
|
tess_percentage,
|
|
tess_thickness,
|
|
vec3_origin
|
|
);
|
|
|
|
SetAreaPortals( Target(), true );
|
|
|
|
event = new Event( EV_Trigger_ActivateTargets );
|
|
event->AddEntity( other );
|
|
ProcessEvent( event );
|
|
|
|
ProcessEvent( EV_BreakingSound );
|
|
|
|
PostEvent( EV_Remove, 0 );
|
|
}
|
|
|
|
Shatter::Shatter()
|
|
{
|
|
//
|
|
// Can only be used once
|
|
//
|
|
count = -1;
|
|
threshold = false;
|
|
|
|
// Since we're a subclass of DamageThreshold, override the invisible behaviour
|
|
showModel();
|
|
PostEvent( Event( "setup" ), 0 );
|
|
|
|
tess_percentage = G_GetFloatArg( "percentage", tess_percentage*100 ) / 100.0f;
|
|
tess_min_size = G_GetIntArg( "minsize", tess_min_size );
|
|
tess_max_size = G_GetIntArg( "maxsize", tess_max_size );
|
|
tess_thickness = G_GetIntArg( "thickness", tess_thickness );
|
|
|
|
health = G_GetFloatArg( "health", 20 );
|
|
max_health = health;
|
|
|
|
noise = str( G_GetSpawnArg( "noise", "" ) );
|
|
|
|
if ( spawnflags & (1<<5) )
|
|
flags |= FL_TESSELATE;
|
|
|
|
if ( spawnflags & (1<<6) )
|
|
threshold = true;
|
|
|
|
tess_thickness = 10;
|
|
|
|
respondto = spawnflags ^ ( TRIGGER_PLAYERS | TRIGGER_MONSTERS | TRIGGER_PROJECTILES );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_glass (0 .5 .8) ? x x NOT_PLAYERS NOT_MONSTERS NOT_PROJECTILES HURT_SHATTER THRESHOLD
|
|
|
|
For glass objects. Shatters when the accumulated damage is exceeded, or when activated
|
|
|
|
"health" specifies how much damage must occur before the glass shatters. Default is 60.
|
|
"percentage" specifies how much of the thing to shatter. Default is 50
|
|
"minsize" specifies minsize for tesselation, default based off size
|
|
"maxsize" specifies maxsize for tesselation, default based off size
|
|
"thickness" specifies thickness for tesselation, default same as minsize
|
|
"key" The item needed to activate this. (default nothing)
|
|
"noise" sound to play when shattered, defaults to glass breaking
|
|
|
|
If NOT_PLAYERS is set, the trigger does not respond to events caused by players
|
|
If NOT_MONSTERS is set, the trigger does not respond to events caused by monsters
|
|
If NOT_PROJECTILES is set, the trigger does not respond to events caused by projectiles
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Shatter, Glass, "func_glass" );
|
|
|
|
ResponseDef Glass::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Glass::Glass()
|
|
{
|
|
if ( !noise.length() )
|
|
{
|
|
const char * realname;
|
|
|
|
if (max_health <= 60)
|
|
{
|
|
realname = gi.GlobalAlias_FindRandom( "impact_smlglass" );
|
|
if ( realname )
|
|
noise = str( realname );
|
|
}
|
|
else
|
|
{
|
|
realname = gi.GlobalAlias_FindRandom( "impact_lrgglass" );
|
|
if ( realname )
|
|
noise = str( realname );
|
|
}
|
|
}
|
|
gi.soundindex( noise.c_str() );
|
|
}
|
|
|
|
//
|
|
// MadeBreakingSound
|
|
//
|
|
// Entity-less notifier for AI
|
|
//
|
|
void MadeBreakingSound
|
|
(
|
|
Vector pos,
|
|
Entity * activator
|
|
)
|
|
|
|
{
|
|
Entity *ent;
|
|
Event *ev;
|
|
|
|
//
|
|
// make sure activator is valid
|
|
//
|
|
|
|
if ( !activator )
|
|
activator = world;
|
|
|
|
ent = NULL;
|
|
while( ent = findradius( ent, pos.vec3(), SOUND_BREAKING_RADIUS ) )
|
|
{
|
|
if ( !ent->deadflag && ent->isSubclassOf( Sentient ) && ( ent != activator ) &&
|
|
gi.inPHS( pos.vec3(), ent->centroid.vec3() )
|
|
)
|
|
{
|
|
ev = new Event( EV_HeardBreaking );
|
|
ev->AddEntity( activator );
|
|
ev->AddVector( pos );
|
|
ent->PostEvent( ev, 0 );
|
|
}
|
|
}
|
|
}
|
|
|
|
CLASS_DECLARATION( Entity, BloodSplat, NULL );
|
|
|
|
ResponseDef BloodSplat::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
int BloodSplat::numBloodSplats = 0;
|
|
Queue BloodSplat::queueBloodSplats;
|
|
|
|
BloodSplat::BloodSplat
|
|
(
|
|
Vector pos,
|
|
Vector ang,
|
|
float scale
|
|
)
|
|
|
|
{
|
|
BloodSplat *fadesplat;
|
|
|
|
if ( numBloodSplats > sv_maxbloodsplats->value )
|
|
{
|
|
// Fade one out of the list.
|
|
fadesplat = ( BloodSplat * )queueBloodSplats.Dequeue();
|
|
fadesplat->ProcessEvent( EV_FadeOut );
|
|
numBloodSplats--;
|
|
|
|
// Don't spawn one until we others have faded
|
|
PostEvent( EV_Remove, 0 );
|
|
return;
|
|
}
|
|
|
|
setMoveType( MOVETYPE_NONE );
|
|
setSolidType( SOLID_NOT );
|
|
setModel( "sprites/bloodsplat.spr" );
|
|
edict->s.frame = G_Random( 4 );
|
|
setSize( "0 0 0", "0 0 0" );
|
|
|
|
queueBloodSplats.Enqueue( this );
|
|
numBloodSplats++;
|
|
|
|
edict->s.scale = scale * edict->s.scale;
|
|
setAngles( ang );
|
|
setOrigin( pos );
|
|
}
|
|
|
|
BloodSplat::~BloodSplat()
|
|
{
|
|
if ( queueBloodSplats.Inqueue( this ) )
|
|
{
|
|
queueBloodSplats.Remove( this );
|
|
numBloodSplats--;
|
|
}
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_clipbox (0 .5 .8) (-16 -16 -16) (16 16 16)
|
|
|
|
Invisible bounding box used like a clip brush. This is mainly used for blocking
|
|
off areas or improving clipping without having to recompile the map. Because of
|
|
this, it will most likely only be spawned via script.
|
|
age is exceeded, or when activated
|
|
|
|
"mins" min point of the clip.
|
|
"maxs" max point of the clip.
|
|
"type" -
|
|
0 Monster and Player clip
|
|
1 Monster clip
|
|
2 Player clip
|
|
|
|
/*****************************************************************************/
|
|
|
|
CLASS_DECLARATION( Entity, ClipBox, "func_clipbox" );
|
|
|
|
ResponseDef ClipBox::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
ClipBox::ClipBox()
|
|
{
|
|
int type;
|
|
|
|
setMoveType( MOVETYPE_NONE );
|
|
setSolidType( SOLID_BBOX );
|
|
hideModel();
|
|
|
|
type = G_GetIntArg( "type" );
|
|
|
|
edict->clipmask = MASK_SOLID;
|
|
switch( type )
|
|
{
|
|
case 1 :
|
|
edict->svflags |= SVF_MONSTERCLIP;
|
|
break;
|
|
|
|
case 2 :
|
|
edict->svflags |= SVF_PLAYERCLIP;
|
|
break;
|
|
|
|
default :
|
|
edict->svflags |= SVF_PLAYERCLIP|SVF_MONSTERCLIP;
|
|
break;
|
|
}
|
|
|
|
mins = G_GetVectorArg( "mins" );
|
|
maxs = G_GetVectorArg( "maxs" );
|
|
|
|
origin = ( mins + maxs ) * 0.5;
|
|
|
|
setSize( mins - origin, maxs - origin );
|
|
setOrigin( origin );
|
|
}
|