sin-2015/object.cpp
1999-04-22 00:00:00 +00:00

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;
}
//###