753 lines
No EOL
16 KiB
C++
753 lines
No EOL
16 KiB
C++
// Copyright (C) 1998 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: Skeet Entity
|
|
//
|
|
|
|
|
|
#include "g_local.h"
|
|
#include "object.h"
|
|
#include "misc.h"
|
|
#include "explosion.h"
|
|
#include "gibs.h"
|
|
#include "specialfx.h"
|
|
#include "sentient.h" //###
|
|
|
|
|
|
CLASS_DECLARATION( Entity, Object, "object" );
|
|
|
|
ResponseDef Object::Responses[] =
|
|
{
|
|
{ &EV_Killed, ( Response )Object::Killed },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
Object::Object()
|
|
{
|
|
const char * animname;
|
|
const char * skinname;
|
|
Vector defangles;
|
|
|
|
SetKillTarget( G_GetSpawnArg( "killtarget" ) );
|
|
setSolidType( SOLID_BBOX );
|
|
// if it isn't solid, lets still make it shootable
|
|
if (spawnflags & 1)
|
|
{
|
|
if ( !(spawnflags & 2) )
|
|
{
|
|
edict->svflags |= SVF_SHOOTABLE;
|
|
setOrigin( origin );
|
|
}
|
|
else
|
|
setSolidType( SOLID_NOT );
|
|
}
|
|
if (!health)
|
|
{
|
|
health = (maxs-mins).length();
|
|
max_health = health;
|
|
}
|
|
takedamage = DAMAGE_YES;
|
|
if ( spawnflags & 2 )
|
|
{
|
|
takedamage = DAMAGE_NO;
|
|
}
|
|
|
|
// angles
|
|
defangles = Vector( 0, G_GetFloatArg( "angle", 0 ), 0 );
|
|
if (defangles.y == -1)
|
|
{
|
|
defangles = Vector( -90, 0, 0 );
|
|
}
|
|
else if (defangles.y == -2)
|
|
{
|
|
defangles = Vector( 90, 0, 0 );
|
|
}
|
|
angles = G_GetVectorArg( "angles", defangles );
|
|
setAngles( angles );
|
|
setOrigin( origin );
|
|
|
|
//
|
|
// we want the bounds of this model auto-rotated
|
|
//
|
|
flags |= FL_ROTATEDBOUNDS;
|
|
|
|
//
|
|
// rotate the mins and maxs for the model
|
|
//
|
|
setSize( mins, maxs );
|
|
|
|
animname = G_GetSpawnArg( "anim" );
|
|
if ( animname && strlen(animname) && gi.IsModel( edict->s.modelindex ) )
|
|
{
|
|
int animnum;
|
|
|
|
animnum = gi.Anim_NumForName( edict->s.modelindex, animname );
|
|
if (animnum >= 0)
|
|
NextAnim( animnum );
|
|
|
|
// Sets up the edict
|
|
AnimateFrame();
|
|
StopAnimating();
|
|
//
|
|
// we only want to start animating if it was explicitly defined in the def file
|
|
//
|
|
//StartAnimating();
|
|
}
|
|
skinname = G_GetSpawnArg( "skin" );
|
|
if ( skinname && strlen(skinname) && gi.IsModel( edict->s.modelindex ) )
|
|
{
|
|
int skinnum;
|
|
|
|
skinnum = gi.Skin_NumForName( edict->s.modelindex, skinname );
|
|
if (skinnum >= 0)
|
|
edict->s.skinnum = skinnum;
|
|
}
|
|
if ( parentmode->value )
|
|
{
|
|
flags &= ~FL_BLOOD;
|
|
flags &= ~FL_DIE_GIBS;
|
|
}
|
|
|
|
if ( !(flags & (FL_BLOOD|FL_SPARKS|FL_TESSELATE)) )
|
|
{
|
|
flags |= FL_DARKEN;
|
|
flags |= FL_TESSELATE;
|
|
flags |= FL_DIE_TESSELATE;
|
|
}
|
|
}
|
|
|
|
void Object::Killed(Event *ev)
|
|
{
|
|
Entity * ent;
|
|
Entity * attacker;
|
|
Vector dir;
|
|
Event * event;
|
|
const char * name;
|
|
int num;
|
|
int i; //###
|
|
|
|
takedamage = DAMAGE_NO;
|
|
setSolidType( SOLID_NOT );
|
|
hideModel();
|
|
|
|
attacker = ev->GetEntity( 1 );
|
|
|
|
if (flags & FL_DIE_TESSELATE)
|
|
{
|
|
dir = worldorigin - attacker->worldorigin;
|
|
TesselateModel
|
|
(
|
|
this,
|
|
tess_min_size,
|
|
tess_max_size,
|
|
dir,
|
|
ev->GetInteger( 2 ),
|
|
tess_percentage,
|
|
tess_thickness,
|
|
vec3_origin
|
|
);
|
|
ProcessEvent( EV_BreakingSound );
|
|
}
|
|
|
|
if (flags & FL_DIE_EXPLODE)
|
|
{
|
|
CreateExplosion( worldorigin, 50, 0.5f, true, this, this, this );
|
|
}
|
|
|
|
if (flags & FL_DIE_GIBS)
|
|
{
|
|
setSolidType( SOLID_NOT );
|
|
hideModel();
|
|
|
|
CreateGibs( this, -150, edict->s.scale, 3 );
|
|
}
|
|
|
|
//
|
|
// kill the killtargets
|
|
//
|
|
//###
|
|
/*
|
|
name = KillTarget();
|
|
if ( name && strcmp( name, "" ) )
|
|
{
|
|
num = 0;
|
|
do
|
|
{
|
|
num = G_FindTarget( num, name );
|
|
if ( !num )
|
|
{
|
|
break;
|
|
}
|
|
|
|
ent = G_GetEntity( num );
|
|
ent->PostEvent( EV_Remove, 0 );
|
|
}
|
|
while ( 1 );
|
|
}
|
|
*/
|
|
// added extended targeting stuff
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0:
|
|
name = KillTarget();
|
|
break;
|
|
case 1:
|
|
name = KillTarget2();
|
|
break;
|
|
}
|
|
|
|
if ( name && strcmp( name, "" ) )
|
|
{
|
|
num = 0;
|
|
do
|
|
{
|
|
num = G_FindTarget( num, name );
|
|
if ( !num )
|
|
{
|
|
break;
|
|
}
|
|
|
|
ent = G_GetEntity( num );
|
|
ent->PostEvent( EV_Remove, 0 );
|
|
} while ( 1 );
|
|
}
|
|
}
|
|
//###
|
|
|
|
//
|
|
// fire targets
|
|
//
|
|
//###
|
|
/*
|
|
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( attacker );
|
|
ent->ProcessEvent( event );
|
|
}
|
|
while ( 1 );
|
|
}
|
|
*/
|
|
// added extended targeting stuff
|
|
for(i = 0; i < 4; i++)
|
|
{
|
|
switch(i)
|
|
{
|
|
case 0:
|
|
name = Target();
|
|
break;
|
|
case 1:
|
|
name = Target2();
|
|
break;
|
|
case 2:
|
|
name = Target3();
|
|
break;
|
|
case 3:
|
|
name = Target4();
|
|
break;
|
|
}
|
|
|
|
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( attacker );
|
|
ent->ProcessEvent( event );
|
|
} while ( 1 );
|
|
}
|
|
}
|
|
//###
|
|
|
|
PostEvent( EV_Remove, 0 );
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_throwobject (0 .5 .8) (0 0 0) (0 0 0)
|
|
|
|
This is an object you can pickup and throw at people
|
|
|
|
/*****************************************************************************/
|
|
CLASS_DECLARATION( Object, ThrowObject, "func_throwobject" );
|
|
|
|
Event EV_ThrowObject_Pickup( "pickup" );
|
|
Event EV_ThrowObject_Throw( "throw" );
|
|
Event EV_ThrowObject_PickupOffset( "pickupoffset" );
|
|
Event EV_ThrowObject_ThrowSound( "throwsound" );
|
|
|
|
ResponseDef ThrowObject::Responses[] =
|
|
{
|
|
{ &EV_Touch, ( Response )ThrowObject::Touch },
|
|
{ &EV_ThrowObject_Pickup, ( Response )ThrowObject::Pickup },
|
|
{ &EV_ThrowObject_Throw, ( Response )ThrowObject::Throw },
|
|
{ &EV_ThrowObject_PickupOffset, ( Response )ThrowObject::PickupOffset },
|
|
{ &EV_ThrowObject_ThrowSound, ( Response )ThrowObject::ThrowSound },
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
ThrowObject::ThrowObject()
|
|
{
|
|
pickup_offset = vec_zero;
|
|
}
|
|
|
|
void ThrowObject::PickupOffset
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
pickup_offset = edict->s.scale * ev->GetVector( 1 );
|
|
}
|
|
|
|
void ThrowObject::ThrowSound
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
throw_sound = ev->GetString( 1 );
|
|
}
|
|
|
|
void ThrowObject::Touch
|
|
(
|
|
Event *ev
|
|
)
|
|
|
|
{
|
|
Event * e;
|
|
Entity * other;
|
|
|
|
if ( movetype != MOVETYPE_BOUNCE )
|
|
return;
|
|
|
|
other = ev->GetEntity( 1 );
|
|
assert( other );
|
|
|
|
if ( other->isSubclassOf( Teleporter ) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( other->entnum == owner )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( throw_sound.length() )
|
|
{
|
|
e = new Event( EV_StopEntitySound );
|
|
ProcessEvent( e );
|
|
}
|
|
|
|
if (other->takedamage)
|
|
other->Damage( this, G_GetEntity( owner ), size.length()*velocity.length()/400, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1, 1.0f );
|
|
Damage( this, this, max_health, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1 , 1 );
|
|
}
|
|
|
|
void ThrowObject::Throw
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
Entity *owner;
|
|
Entity *targetent;
|
|
float speed;
|
|
float traveltime;
|
|
float vertical_speed;
|
|
Vector target, dir;
|
|
float grav;
|
|
Vector xydir;
|
|
Event * e;
|
|
|
|
owner = ev->GetEntity( 1 );
|
|
assert( owner );
|
|
if (!owner)
|
|
return;
|
|
speed = ev->GetFloat( 2 );
|
|
if ( !speed )
|
|
speed = 1;
|
|
targetent = ev->GetEntity( 3 );
|
|
assert( targetent );
|
|
if (!targetent)
|
|
return;
|
|
if ( ev->NumArgs() == 4 )
|
|
grav = ev->GetFloat( 4 );
|
|
else
|
|
grav = 1;
|
|
|
|
e = new Event( EV_Detach );
|
|
ProcessEvent( e );
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
gravity = grav;
|
|
|
|
target = targetent->worldorigin;
|
|
target.z += targetent->viewheight;
|
|
setMoveType( MOVETYPE_BOUNCE );
|
|
setSolidType( SOLID_BBOX );
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
|
|
dir = target - worldorigin;
|
|
xydir = dir;
|
|
xydir.z = 0;
|
|
traveltime = xydir.length() / speed;
|
|
vertical_speed = ( dir.z / traveltime ) + ( 0.5f * gravity * sv_gravity->value * traveltime );
|
|
xydir.normalize();
|
|
|
|
// setup ambient flying sound
|
|
if ( throw_sound.length() )
|
|
{
|
|
e = new Event( EV_RandomEntitySound );
|
|
e->AddString( throw_sound );
|
|
ProcessEvent( e );
|
|
}
|
|
|
|
velocity = speed * xydir;
|
|
velocity.z = vertical_speed;
|
|
|
|
angles = velocity.toAngles();
|
|
angles[ PITCH ] = - angles[ PITCH ];
|
|
setAngles( angles );
|
|
|
|
avelocity.x = crandom() * 200;
|
|
avelocity.y = crandom() * 200;
|
|
takedamage = DAMAGE_YES;
|
|
|
|
}
|
|
|
|
void ThrowObject::Pickup
|
|
(
|
|
Event *ev
|
|
)
|
|
{
|
|
Entity * ent;
|
|
Event * e;
|
|
str bone;
|
|
|
|
ent = ev->GetEntity( 1 );
|
|
assert( ent );
|
|
if ( !ent )
|
|
return;
|
|
bone = ev->GetString( 2 );
|
|
|
|
e = new Event( EV_Attach );
|
|
e->AddEntity( ent );
|
|
e->AddString( bone );
|
|
setOrigin( pickup_offset );
|
|
ProcessEvent( e );
|
|
edict->s.renderfx &= ~RF_FRAMELERP;
|
|
}
|
|
|
|
//
|
|
// Barrel with fire coming out of it
|
|
//
|
|
CLASS_DECLARATION( Object, FireBarrel, "world_firebarrel" );
|
|
|
|
ResponseDef FireBarrel::Responses[] =
|
|
{
|
|
{ NULL, NULL }
|
|
};
|
|
|
|
FireBarrel::FireBarrel()
|
|
{
|
|
Vector offset;
|
|
|
|
fire = new FireSprite;
|
|
// put the fire 3/4 of the way up the barrel
|
|
offset[ 2 ] = (3 * size[ 2 ]) / 4;
|
|
fire->setOrigin( worldorigin + offset );
|
|
fire->setScale( edict->s.scale );
|
|
}
|
|
|
|
FireBarrel::~FireBarrel()
|
|
{
|
|
fire->PostEvent( EV_Remove, 0 );
|
|
fire = NULL;
|
|
}
|
|
|
|
//### 2015 added stuff
|
|
|
|
/*****************************************************************************/
|
|
/*SINED func_goliathobject (0 .5 .8) (0 0 0) (0 0 0) NOTSOLID NODAMAGE FALL STACKING
|
|
|
|
This is a func_throwobject with added functionality that's been optimized for use by a goliath
|
|
|
|
NOTSOLID makes it not solid while sitting around.
|
|
NODAMAGE makes it not damagable while sitting around.
|
|
FALL makes it be effected by gravity while waiting around to be thrown.
|
|
STACKING makes the object stay on top of other BBOX entities so they can be stacked
|
|
/*****************************************************************************/
|
|
CLASS_DECLARATION( ThrowObject, GoliathObject, "func_goliathobject" );
|
|
|
|
ResponseDef GoliathObject::Responses[] =
|
|
{
|
|
{&EV_Touch, (Response)GoliathObject::Touch},
|
|
{&EV_ThrowObject_Pickup, (Response)GoliathObject::Pickup},
|
|
{&EV_ThrowObject_Throw, (Response)GoliathObject::Throw},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
GoliathObject::GoliathObject()
|
|
{
|
|
int falling;
|
|
ScriptVariable *var;
|
|
|
|
// the attackmode thing is a quick hack
|
|
// to get these working with a func_spawn
|
|
falling = G_GetIntArg("attackmode", 0);
|
|
if(falling)
|
|
{
|
|
spawnflags |= 4 | 8;
|
|
|
|
// since attackmode is set, that means we were spawn
|
|
// up in the air, so roll around a bit while falling
|
|
avelocity.y = crandom() * 300;
|
|
}
|
|
|
|
if(spawnflags & 4)
|
|
{
|
|
setMoveType(MOVETYPE_TOSS);
|
|
|
|
if(spawnflags & 8)
|
|
{
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
}
|
|
}
|
|
|
|
mass = 500;
|
|
pickup_offset = vec_zero;
|
|
|
|
// incriment the throw object counter
|
|
var = levelVars.GetVariable("goliathobject_count");
|
|
if(!var)
|
|
{
|
|
var = levelVars.CreateVariable("goliathobject_count", 1);
|
|
}
|
|
else
|
|
{
|
|
falling = var->intValue() + 1;
|
|
var->setIntValue(falling);
|
|
}
|
|
}
|
|
|
|
GoliathObject::~GoliathObject()
|
|
{
|
|
ScriptVariable *var;
|
|
int i;
|
|
|
|
// decriment the throw object counter
|
|
var = levelVars.GetVariable("goliathobject_count");
|
|
if(var)
|
|
{
|
|
i = var->intValue() - 1;
|
|
if(i < 0)
|
|
i = 0;
|
|
var->setIntValue(i);
|
|
}
|
|
}
|
|
|
|
void GoliathObject::Touch (Event *ev)
|
|
{
|
|
Event * e;
|
|
Entity * other;
|
|
|
|
// added for freshly spawned & falling Goliath rubble
|
|
if(movetype == MOVETYPE_TOSS)
|
|
{
|
|
other = ev->GetEntity(1);
|
|
assert(other);
|
|
|
|
// if not moving a significant amount, don't do anything
|
|
if(velocity.length() < 100)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// if landing on a sentient, shatter & hurt it
|
|
if(other->isSubclassOf(Sentient))
|
|
{
|
|
if(other->takedamage)
|
|
other->Damage( this, this, size.length()*velocity.length()/1000, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1, 1.0f );
|
|
Damage( this, this, max_health*2, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1 , 1 );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if(movetype != MOVETYPE_BOUNCE)
|
|
return;
|
|
|
|
other = ev->GetEntity(1);
|
|
assert(other);
|
|
|
|
if(other->isSubclassOf(Teleporter))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(other->entnum == owner)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if(throw_sound.length())
|
|
{
|
|
e = new Event(EV_StopEntitySound);
|
|
ProcessEvent(e);
|
|
}
|
|
|
|
if (other->takedamage)
|
|
other->Damage( this, G_GetEntity( owner ), size.length()*velocity.length()/900, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1, 1.0f );
|
|
Damage( this, this, max_health, worldorigin, velocity, level.impact_trace.plane.normal, 32, 0, MOD_THROWNOBJECT, -1, -1 , 1 );
|
|
}
|
|
|
|
void GoliathObject::Throw (Event *ev)
|
|
{
|
|
Entity *owner;
|
|
Entity *targetent;
|
|
float speed;
|
|
float traveltime;
|
|
float vertical_speed;
|
|
Vector target, dir;
|
|
float grav;
|
|
Vector xydir;
|
|
Event * e;
|
|
|
|
owner = ev->GetEntity(1);
|
|
assert(owner);
|
|
if(!owner)
|
|
return;
|
|
speed = ev->GetFloat(2);
|
|
if(!speed)
|
|
speed = 500;
|
|
targetent = ev->GetEntity(3);
|
|
assert(targetent);
|
|
if(!targetent)
|
|
return;
|
|
if(ev->NumArgs() == 4)
|
|
grav = ev->GetFloat(4);
|
|
else
|
|
grav = 1;
|
|
|
|
e = new Event(EV_Detach);
|
|
ProcessEvent(e);
|
|
|
|
this->owner = owner->entnum;
|
|
edict->owner = owner->edict;
|
|
|
|
gravity = grav;
|
|
|
|
target = targetent->worldorigin;
|
|
target.z += targetent->viewheight;
|
|
if(skill->value == 1)
|
|
{
|
|
if(G_Random() < 0.3)
|
|
target += targetent->velocity*0.25;
|
|
else if(G_Random() < 0.5)
|
|
target += targetent->velocity*0.5;
|
|
}
|
|
else if(skill->value == 2)
|
|
{
|
|
if(G_Random() < 0.3)
|
|
target += targetent->velocity*0.25;
|
|
else if(G_Random() < 0.6)
|
|
target += targetent->velocity*0.5;
|
|
else
|
|
target += targetent->velocity*0.75;
|
|
}
|
|
else
|
|
{
|
|
if(G_Random() < 0.3)
|
|
target += targetent->velocity*0.25;
|
|
}
|
|
|
|
setMoveType(MOVETYPE_BOUNCE);
|
|
setSolidType(SOLID_BBOX);
|
|
edict->clipmask = MASK_PROJECTILE;
|
|
|
|
dir = target - worldorigin;
|
|
xydir = dir;
|
|
xydir.z = 0;
|
|
traveltime = xydir.length() / speed;
|
|
vertical_speed = (dir.z / traveltime) + (0.5f * gravity * sv_gravity->value * traveltime);
|
|
xydir.normalize();
|
|
|
|
// setup ambient flying sound
|
|
if(throw_sound.length())
|
|
{
|
|
// not used anywhere in original Sin, so
|
|
// I went ahead and made it a global alias call
|
|
// so it'd be easier to set
|
|
e = new Event( EV_RandomGlobalEntitySound );
|
|
e->AddString( throw_sound );
|
|
ProcessEvent( e );
|
|
}
|
|
|
|
velocity = speed * xydir;
|
|
velocity.z = vertical_speed;
|
|
throwvel = velocity; // added for anti-floating
|
|
|
|
angles = velocity.toAngles();
|
|
angles[PITCH] = - angles[PITCH];
|
|
setAngles(angles);
|
|
|
|
avelocity.x = crandom() * 200;
|
|
avelocity.y = crandom() * 200;
|
|
takedamage = DAMAGE_YES;
|
|
|
|
}
|
|
|
|
void GoliathObject::Pickup (Event *ev)
|
|
{
|
|
Entity * ent;
|
|
Event * e;
|
|
str bone;
|
|
|
|
ent = ev->GetEntity(1);
|
|
assert(ent);
|
|
if(!ent)
|
|
return;
|
|
bone = ev->GetString(2);
|
|
|
|
setOrigin(pickup_offset);
|
|
|
|
setMoveType(MOVETYPE_NONE);
|
|
edict->clipmask = 0;
|
|
groundentity = NULL;
|
|
setAngles(vec_zero);
|
|
setSize(mins, maxs);
|
|
|
|
e = new Event(EV_Attach);
|
|
e->AddEntity(ent);
|
|
e->AddString(bone);
|
|
ProcessEvent(e);
|
|
edict->s.renderfx &= ~RF_FRAMELERP;
|
|
}
|
|
|
|
//###
|