// 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: // Base class for all enities that are controlled by Sin. If you have any // object that should be called on a periodic basis and it is not an entity, // then you have to have an dummy entity that calls it. // // An entity in Sin is any object that is not part of the world. Any non-world // object that is visible in Sin is an entity, although it is not required that // all entities be visible to the player. Some objects are basically just virtual // constructs that act as an instigator of certain actions, for example, some // triggers are invisible and cannot be touched, but when activated by other // objects can cause things to happen. // // All entities are capable of receiving messages from Sin or from other entities. // Messages received by an entity may be ignored, passed on to their superclass, // or acted upon by the entity itself. The programmer must decide on the proper // action for the entity to take to any message. There will be many messages // that are completely irrelevant to an entity and should be ignored. Some messages // may require certain states to exist and if they are received by an entity when // it these states don't exist may indicate a logic error on the part of the // programmer or map designer and should be reported as warnings (if the problem is // not severe enough for the game to be halted) or as errors (if the problem should // not be ignored at any cost). // #include "entity.h" #include "worldspawn.h" #include "scriptmaster.h" #include "sentient.h" #include "misc.h" #include "specialfx.h" #include "object.h" #include "player.h" CLASS_DECLARATION( Listener, Entity, NULL ); // Player events Event EV_ClientConnect( "client_connect" ); Event EV_ClientDisconnect( "client_disconnect" ); Event EV_ClientKill( "client_kill" ); Event EV_ClientMove( "client_move" ); Event EV_ClientEndFrame( "client_endframe" ); // Generic entity events Event EV_GetEntName( "getentname" ); Event EV_Classname( "classname" ); Event EV_Activate( "doActivate" ); Event EV_Use( "doUse" ); Event EV_PreciseUse( "doPreciseUse" ); //### precise use //Event EV_Footstep( "footstep" ); Event EV_FadeOut( "fadeout" ); Event EV_Fade( "fade" ); Event EV_Killed( "killed" ); Event EV_GotKill( "gotkill" ); Event EV_Pain( "pain" ); Event EV_Damage( "damage" ); Event EV_Kill( "kill", EV_CONSOLE ); Event EV_Gib( "gib" ); Event EV_Hurt( "hurt" ); Event EV_CourseAngles( "courseangles" ); Event EV_SmoothAngles( "smoothangles" ); Event EV_TakeDamage( "takedamage" ); Event EV_NoDamage( "nodamage" ); // Physics events Event EV_MoveDone( "movedone" ); Event EV_Touch( "doTouch" ); Event EV_Blocked( "doBlocked" ); Event EV_UseBoundingBox( "usebbox" ); // Animation events Event EV_NewAnim( "animChanged" ); Event EV_LastFrame( "lastFrame" ); Event EV_NextAnim( "nextanim" ); Event EV_NextFrame( "nextframe" ); Event EV_PrevFrame( "prevframe" ); Event EV_SetFrame( "setframe" ); Event EV_StopAnim( "stopanim" ); Event EV_EndAnim( "endanim" ); Event EV_ProcessInitCommands( "processinit" ); Event EV_Attach( "attach" ); Event EV_AttachModel( "attachmodel" ); Event EV_Detach( "detach" ); // script stuff Event EV_Model( "model" ); Event EV_Hide( "hide" ); Event EV_Show( "show" ); Event EV_BecomeSolid( "solid" ); Event EV_BecomeNonSolid( "notsolid" ); Event EV_Ghost( "ghost" ); Event EV_PlaySound( "playsound" ); Event EV_PHSSound( "phssound" ); Event EV_StopSound( "stopsound" ); Event EV_GravityAxis( "gravityaxis", EV_CHEAT ); Event EV_Bind( "bind" ); Event EV_Unbind( "unbind" ); Event EV_JoinTeam( "joinTeam" ); Event EV_QuitTeam( "quitTeam" ); Event EV_SetHealth( "health", EV_CHEAT ); Event EV_SetScale( "scale" ); Event EV_SetSize( "setsize" ); Event EV_SetAlpha( "alpha" ); Event EV_SetGroupAlpha( "groupalpha" ); //### Event EV_SetOrigin( "origin" ); Event EV_SetTargetName( "targetname" ); Event EV_SetTarget( "target" ); Event EV_SetKillTarget( "killtarget" ); Event EV_SetAngles( "angles" ); Event EV_RegisterAlias( "alias" ); Event EV_RegisterAliasAndCache( "aliascache" ); Event EV_RandomSound( "randomsound" ); Event EV_RandomPHSSound( "randomphssound" ); Event EV_Tesselate( "shatter" ); Event EV_SetMass( "mass" ); //### extended targeting stuff Event EV_SetTargetName2( "targetname2" ); Event EV_SetTarget2( "target2" ); Event EV_SetTarget3( "target3" ); Event EV_SetTarget4( "target4" ); Event EV_SetKillTarget2( "killtarget2" ); //### //HACK HACK Event EV_EntitySound( "ambientsound" ); Event EV_RandomGlobalEntitySound( "randomglobalambientsound" ); Event EV_RandomEntitySound( "randomambientsound" ); Event EV_StopEntitySound( "stopambientsound" ); Event EV_Anim( "anim" ); Event EV_StartAnimating( "animate" ); Event EV_GroupModelEvent( "group" ); Event EV_DialogEvent( "dialog" ); Event EV_SetSkin( "skin" ); // AI sound events Event EV_WeaponSound( "weaponsound" ); Event EV_MovementSound( "movementsound" ); Event EV_PainSound( "painsound" ); Event EV_DeathSound( "deathsound" ); Event EV_BreakingSound( "breakingsound" ); Event EV_DoorSound( "doorsound" ); Event EV_MutantSound( "mutantsound" ); Event EV_VoiceSound( "voicesound" ); Event EV_MachineSound( "machinesound" ); Event EV_RadioSound( "radiosound" ); Event EV_HeardWeapon( "heardweapon" ); Event EV_HeardMovement( "heardmovement" ); Event EV_HeardPain( "heardpain" ); Event EV_HeardDeath( "hearddeath" ); Event EV_HeardBreaking( "heardbreaking" ); Event EV_HeardDoor( "hearddoor" ); Event EV_HeardMutant( "heardmutant" ); Event EV_HeardVoice( "heardvoice" ); Event EV_HeardMachine( "heardmachine" ); Event EV_HeardRadio( "heardradio" ); // Conditionals Event EV_IfSkill( "ifskill" ); // Lighting Event EV_SetLight( "light" ); Event EV_LightOn( "lightOn" ); Event EV_LightOff( "lightOff" ); Event EV_LightRed( "lightRed" ); Event EV_LightGreen( "lightGreen" ); Event EV_LightBlue( "lightBlue" ); Event EV_LightRadius( "lightRadius" ); Event EV_Lightoffset( "lightoffset" ); Event EV_Minlight( "minlight" ); Event EV_Gravity( "gravity" ); // Entity flag specific Event EV_EntityFlags( "flags" ); Event EV_EntityRenderEffects( "rendereffects" ); Event EV_EntityEffects( "effects" ); // Special Effects Event EV_SpawnParticles( "sparks" ); // Tesselation setup events Event EV_Shatter_MinSize( "shatter_minsize" ); Event EV_Shatter_MaxSize( "shatter_maxsize" ); Event EV_Shatter_Thickness( "shatter_thickness" ); Event EV_Shatter_Percentage( "shatter_percentage" ); Event EV_Mutate( "mutate", EV_CHEAT ); Event EV_Censor( "censor" ); ResponseDef Entity::Responses[] = { { &EV_Damage, ( Response )Entity::DamageEvent }, { &EV_Kill, ( Response )Entity::Kill }, { &EV_FadeOut, ( Response )Entity::FadeOut }, { &EV_Fade, ( Response )Entity::Fade }, { &EV_Hide, ( Response )Entity::EventHideModel }, { &EV_Show, ( Response )Entity::EventShowModel }, { &EV_BecomeSolid, ( Response )Entity::BecomeSolid }, { &EV_BecomeNonSolid, ( Response )Entity::BecomeNonSolid }, { &EV_Ghost, ( Response )Entity::Ghost }, { &EV_PlaySound, ( Response )Entity::PlaySound }, { &EV_StopSound, ( Response )Entity::StopSound }, { &EV_GravityAxis, ( Response )Entity::GravityAxisEvent }, { &EV_Bind, ( Response )Entity::BindEvent }, { &EV_Unbind, ( Response )Entity::EventUnbind }, { &EV_JoinTeam, ( Response )Entity::JoinTeam }, { &EV_QuitTeam, ( Response )Entity::EventQuitTeam }, { &EV_SetHealth, ( Response )Entity::SetHealth }, { &EV_SetSize, ( Response )Entity::SetSize }, { &EV_SetScale, ( Response )Entity::SetScale }, { &EV_SetAlpha, ( Response )Entity::SetAlpha }, { &EV_SetGroupAlpha, ( Response )Entity::SetGroupAlpha }, //### { &EV_SetOrigin, ( Response )Entity::SetOrigin }, { &EV_SetTargetName, ( Response )Entity::SetTargetName }, { &EV_SetTarget, ( Response )Entity::SetTarget }, { &EV_SetKillTarget, ( Response )Entity::SetKillTarget }, //### extended targeting stuff { &EV_SetTargetName2, ( Response )Entity::SetTargetName2 }, { &EV_SetTarget2, ( Response )Entity::SetTarget2 }, { &EV_SetTarget3, ( Response )Entity::SetTarget3 }, { &EV_SetTarget4, ( Response )Entity::SetTarget4 }, { &EV_SetKillTarget2, ( Response )Entity::SetKillTarget2 }, //### { &EV_SetAngles, ( Response )Entity::SetAngles }, { &EV_SetMass, ( Response )Entity::SetMassEvent }, { &EV_CourseAngles, ( Response )Entity::CourseAnglesEvent }, { &EV_SmoothAngles, ( Response )Entity::SmoothAnglesEvent }, { &EV_RegisterAlias, ( Response )Entity::RegisterAlias }, { &EV_RegisterAliasAndCache, ( Response )Entity::RegisterAliasAndCache }, { &EV_RandomSound, ( Response )Entity::RandomSound }, { &EV_EntitySound, ( Response )Entity::EntitySound }, { &EV_RandomEntitySound,( Response )Entity::RandomEntitySound }, { &EV_RandomGlobalEntitySound, ( Response )Entity::RandomGlobalEntitySoundEvent }, { &EV_StopEntitySound, ( Response )Entity::StopEntitySound }, { &EV_Anim, ( Response )Entity::AnimEvent }, { &EV_StartAnimating, ( Response )Entity::StartAnimatingEvent }, { &EV_NextAnim, ( Response )Entity::NextAnimEvent }, { &EV_NextFrame, ( Response )Entity::NextFrameEvent }, { &EV_PrevFrame, ( Response )Entity::PrevFrameEvent }, { &EV_SetFrame, ( Response )Entity::SetFrameEvent }, { &EV_StopAnim, ( Response )Entity::StopAnimatingEvent }, { &EV_EndAnim, ( Response )Entity::EndAnimEvent }, { &EV_Model, ( Response )Entity::SetModelEvent }, { &EV_SetLight, ( Response )Entity::SetLight }, { &EV_LightOn, ( Response )Entity::LightOn }, { &EV_LightOff, ( Response )Entity::LightOff }, { &EV_LightRed, ( Response )Entity::LightRed }, { &EV_LightGreen, ( Response )Entity::LightGreen }, { &EV_LightBlue, ( Response )Entity::LightBlue }, { &EV_LightRadius, ( Response )Entity::LightRadius }, { &EV_Tesselate, ( Response )Entity::Tesselate }, { &EV_EntityFlags, ( Response )Entity::Flags }, { &EV_EntityEffects, ( Response )Entity::Effects }, { &EV_EntityRenderEffects, ( Response )Entity::RenderEffects }, { &EV_RandomPHSSound, ( Response )Entity::RandomPHSSound }, { &EV_PHSSound, ( Response )Entity::PHSSound }, { &EV_WeaponSound, ( Response )Entity::WeaponSound }, { &EV_MovementSound, ( Response )Entity::MovementSound }, { &EV_PainSound, ( Response )Entity::PainSound }, { &EV_DeathSound, ( Response )Entity::DeathSound }, { &EV_BreakingSound, ( Response )Entity::BreakingSound }, { &EV_DoorSound, ( Response )Entity::DoorSound }, { &EV_MutantSound, ( Response )Entity::MutantSound }, { &EV_VoiceSound, ( Response )Entity::VoiceSound }, { &EV_MachineSound, ( Response )Entity::MachineSound }, { &EV_RadioSound, ( Response )Entity::RadioSound }, { &EV_SpawnParticles, ( Response )Entity::SpawnParticles }, { &EV_GroupModelEvent, ( Response )Entity::GroupModelEvent }, { &EV_DialogEvent, ( Response )Entity::DialogEvent }, { &EV_ProcessInitCommands,( Response )Entity::ProcessInitCommandsEvent }, { &EV_Attach, ( Response )Entity::AttachEvent }, { &EV_AttachModel, ( Response )Entity::AttachModelEvent }, { &EV_Detach, ( Response )Entity::DetachEvent }, { &EV_TakeDamage, ( Response )Entity::TakeDamageEvent }, { &EV_NoDamage, ( Response )Entity::NoDamageEvent }, { &EV_SetSkin, ( Response )Entity::SetSkinEvent }, { &EV_Lightoffset, ( Response )Entity::Lightoffset }, { &EV_Minlight, ( Response )Entity::Minlight }, { &EV_Gravity, ( Response )Entity::Gravity }, { &EV_Shatter_MinSize, ( Response )Entity::SetShatterMinSize }, { &EV_Shatter_MaxSize, ( Response )Entity::SetShatterMaxSize }, { &EV_Shatter_Thickness,( Response )Entity::SetShatterThickness }, { &EV_Shatter_Percentage,( Response )Entity::SetShatterPercentage }, { &EV_UseBoundingBox, ( Response )Entity::UseBoundingBoxEvent }, { &EV_Hurt, ( Response )Entity::HurtEvent }, { &EV_IfSkill, ( Response )Entity::IfSkillEvent }, { &EV_GetEntName, ( Response )Entity::GetEntName }, { &EV_Censor, ( Response )Entity::Censor }, { NULL, NULL } }; Entity::Entity() { const char *m; Event *ev; int minlight; classname = this->getClassname(); if ( game.force_entnum ) { game.force_entnum = false; edict = &g_edicts[ game.spawn_entnum ]; LL_Remove( edict, next, prev ); G_InitEdict( edict ); LL_Add( &active_edicts, edict, next, prev ); } else { edict = G_Spawn (); } client = edict->client; edict->entity = this; entnum = edict->s.number; m = G_GetSpawnArg( "classname" ); if ( m ) { strncpy( edict->entname, m, sizeof( edict->entname ) - 1 ); } // spawning variables spawnflags = G_GetIntArg( "spawnflags" ); if ( spawnflags & SPAWNFLAG_DETAIL ) { edict->s.renderfx |= RF_DETAIL; } // rendering variables setAlpha( G_GetFloatArg( "alpha", 1.0f ) ); setScale( G_GetFloatArg( "scale", 1.0f ) ); minlight = G_GetIntArg( "minlight", 0 ); if ( minlight ) edict->s.renderfx |= RF_MINLIGHT; edict->s.lightofs = G_GetFloatArg( "lightoffset", 0 ); if ( edict->s.lightofs ) edict->s.renderfx |= RF_LIGHTOFFSET; viewheight = 0; light_level = 0; // Animation variables next_anim = -1; next_frame = -1; frame_delta = "0 0 0"; next_anim_delta = "0 0 0"; next_anim_time = 0; total_delta = "0 0 0"; animDoneEvent = NULL; animating = false; last_frame_in_anim = 0; last_animation_time = -1; num_frames_in_gun_anim = 0; // team variables teamchain = NULL; teammaster = NULL; m = G_GetSpawnArg( "team" ); if ( m ) { moveteam = str( m ); } // physics variables contents = 0; mass = 0; gravity = 1.0; groundentity = NULL; groundsurface = NULL; groundentity_linkcount = 0; bindmaster = NULL; velocity = vec_zero; avelocity = vec_zero; SetGravityAxis( G_GetIntArg( "gravityaxis", 0 ) ); // model binding variables numchildren = 0; memset( &children, 0, sizeof( children ) ); setOrigin( G_GetSpawnArg( "origin" ) ); worldorigin.copyTo( edict->s.old_origin ); setMoveType( MOVETYPE_NONE ); setSolidType( SOLID_NOT ); // targeting variables SetTargetName( G_GetSpawnArg( "targetname" ) ); SetTarget( G_GetSpawnArg( "target" ) ); //### extended targeting stuff SetTargetName2( G_GetSpawnArg( "targetname2" ) ); SetTarget2( G_GetSpawnArg( "target2" ) ); SetTarget3( G_GetSpawnArg( "target3" ) ); SetTarget4( G_GetSpawnArg( "target4" ) ); //### // Character state health = 0; max_health = 0; deadflag = DEAD_NO; flags = 0; // underwater variables watertype = 0; waterlevel = 0; // Pain and damage variables takedamage = DAMAGE_NO; enemy = NULL; pain_finished = 0; damage_debounce_time = 0; m = G_GetSpawnArg( "model" ); if ( m ) { setModel( m ); } // // see if we have a mins and maxs set for this model // if ( gi.IsModel( edict->s.modelindex ) && !mins.length() && !maxs.length()) { vec3_t tempmins, tempmaxs; gi.CalculateBounds( edict->s.modelindex, edict->s.scale, tempmins, tempmaxs ); setSize( tempmins, tempmaxs ); } // // get the number of groups for this model // edict->s.numgroups = gi.NumGroups( edict->s.modelindex ); m = G_GetSpawnArg( "bind" ); if ( m ) { str name; ev = new Event( EV_Bind ); // construct an object name name = "$"; name += m; ev->AddString( name ); // Wait until all entities are spawned. PostEvent( ev, 0 ); } // // initialize tesselation variables // tess_max_size = size.length() / 4; tess_min_size = tess_max_size / 3; if ( tess_min_size < 8 ) { tess_min_size = 8; } if ( tess_max_size <= tess_min_size ) { tess_max_size = tess_min_size * 2; } tess_thickness = tess_min_size; tess_percentage = TESS_DEFAULT_PERCENT; } Entity::~Entity() { Container bindlist; Entity *ent; int num; int i; // unbind any entities that are bound to me // can't unbind within this loop, so make an array // and unbind them outside of it. num = 0; for( ent = teamchain; ent; ent = ent->teamchain ) { if ( ent->bindmaster == this ) { bindlist.AddObject( ent ); } } num = bindlist.NumObjects(); for( i = 1; i <= num; i++ ) { bindlist.ObjectAt( i )->unbind(); } bindlist.FreeObjectList(); unbind(); quitTeam(); detach(); // // go through and set our children // num = numchildren; for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) { if ( !children[ i ] ) { continue; } ent = ( Entity * )G_GetEntity( children[ i ] ); ent->PostEvent( EV_Remove, 0 ); num--; } if ( targetname.length() && world ) { world->RemoveTargetEntity( targetname, this ); } //### extended targeting stuff if ( targetname2.length() && world ) { world->RemoveTargetEntity( targetname2, this ); } //### this->CancelPendingEvents(); G_FreeEdict( edict ); } EXPORT_FROM_DLL void Entity::SetEntNum ( int num ) { if ( edict ) { G_FreeEdict( edict ); } edict = &g_edicts[ num ]; LL_Remove( edict, next, prev ); G_InitEdict( edict ); LL_Add( &active_edicts, edict, next, prev ); client = edict->client; edict->entity = this; entnum = num; } EXPORT_FROM_DLL void Entity::GetEntName ( Event *ev ) { strncpy( edict->entname, getClassname(), sizeof( edict->entname ) - 1 ); } EXPORT_FROM_DLL void Entity::SetTarget ( const char *text ) { if ( text ) { target = text; } else { target = ""; } } EXPORT_FROM_DLL void Entity::SetTargetName ( const char *text ) { if ( targetname.length() && world ) { world->RemoveTargetEntity( targetname, this ); } if ( text ) { if ( text[ 0 ] == '$' ) text++; targetname = text; } else { targetname = ""; } if ( targetname.length() && world ) { world->AddTargetEntity( targetname, this ); } } EXPORT_FROM_DLL void Entity::SetKillTarget ( const char *text ) { if ( text ) { killtarget = text; } else { killtarget = ""; } } EXPORT_FROM_DLL int Entity::modelIndex ( const char *mdl ) { str name; assert( mdl ); if ( !mdl ) { return 0; } // Prepend 'models/' to make things easier if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) ) { name = "models/"; } name += mdl; return gi.modelindex( name.c_str() ); } EXPORT_FROM_DLL void Entity::setModel ( const char *mdl ) { str temp; if ( !mdl ) { mdl = ""; } // Prepend 'models/' to make things easier temp = ""; if ( !strchr( mdl, '*' ) && !strchr( mdl, '\\' ) && !strchr( mdl, '/' ) ) { temp = "models/"; } temp += mdl; // we use a temp string so that if model was passed into here, we don't // accidentally free up the string that we're using in the process. model = temp; gi.setmodel( edict, model.c_str() ); if ( gi.IsModel( edict->s.modelindex ) ) { Event *ev; edict->s.numgroups = gi.NumGroups( edict->s.modelindex ); if ( !LoadingSavegame ) { CancelEventsOfType( EV_ProcessInitCommands ); ev = new Event( EV_ProcessInitCommands ); ev->AddInteger( edict->s.modelindex ); PostEvent( ev, 0 ); } } // Sanity check to see if we're expecting a B-Model assert( !( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) ); if ( ( edict->solid == SOLID_BSP ) && !edict->s.modelindex ) { const char *name; name = getClassID(); if ( !name ) { name = getClassname(); } gi.dprintf( "%s with SOLID_BSP and no model - '%s'(%d)\n", name, targetname.c_str(), entnum ); // Make it non-solid so that the collision code doesn't kick us out. setSolidType( SOLID_NOT ); } mins = edict->mins; maxs = edict->maxs; size = edict->size; } EXPORT_FROM_DLL void Entity::ProcessInitCommands ( int index ) { sinmdl_cmd_t *cmds; if ( LoadingSavegame ) { // Don't process init commands when loading a savegame since // it will cause items to be added to inventories unnecessarily. // All variables affected by the init commands will be set // by the unarchive functions. return; } cmds = gi.InitCommands( index ); if (cmds) { int i, j; Event *event; for (i=0;inum_cmds;i++) { event = new Event( cmds->cmds[i].args[0] ); for(j=1;jcmds[i].num_args;j++) { event->AddToken( cmds->cmds[i].args[j] ); } ProcessEvent( event ); } } } EXPORT_FROM_DLL void Entity::ProcessInitCommandsEvent ( Event *ev ) { int index; index = ev->GetInteger( 1 ); ProcessInitCommands( index ); } EXPORT_FROM_DLL void Entity::EventHideModel ( Event *ev ) { hideModel(); } EXPORT_FROM_DLL void Entity::EventShowModel ( Event *ev ) { showModel(); } EXPORT_FROM_DLL void Entity::setAlpha ( float alpha ) { if ( alpha > 1.0f ) { alpha = 1.0f; } if ( alpha < 0 ) { alpha = 0; } translucence = 1.0f - alpha; edict->s.alpha = alpha; edict->s.renderfx &= ~RF_TRANSLUCENT; if ( ( translucence > 0 ) && ( translucence <= 1.0 ) ) { edict->s.renderfx |= RF_TRANSLUCENT; } } EXPORT_FROM_DLL void Entity::setScale ( float scale ) { if ( scale > 255.0f ) { scale = 255.0f; } if ( scale < 0.004f ) { scale = 0.004f; } edict->s.scale = scale; } EXPORT_FROM_DLL void Entity::setSolidType ( solid_t type ) { if ( ( !LoadingSavegame ) && ( type == SOLID_BSP ) && ( this != world ) && ( !model.length() || ( ( model[ 0 ] != '*' ) && ( !strstr( model.c_str(), ".bsp" ) ) ) ) ) { error( "setSolidType", "SOLID_BSP entity at x%.2f y%.2f z%.2f with no BSP model", worldorigin[ 0 ], worldorigin[ 1 ], worldorigin[ 2 ] ); } edict->solid = type; link(); edict->svflags &= ~SVF_NOCLIENT; if ( hidden() ) { edict->svflags |= SVF_NOCLIENT; } } EXPORT_FROM_DLL void Entity::setSize ( Vector min, Vector max ) { Vector delta; if ( flags & FL_ROTATEDBOUNDS ) { vec3_t tempmins, tempmaxs; float mat[3][3]; // // rotate the mins and maxs for the model // min.copyTo( tempmins ); max.copyTo( tempmaxs ); VectorCopy( orientation[ 0 ], mat[ 0 ] ); VectorNegate( orientation[ 1 ], mat[ 1 ] ); VectorCopy( orientation[ 2 ], mat[ 2 ] ); CalculateRotatedBounds2( mat, tempmins, tempmaxs ); mins = Vector( tempmins ); maxs = Vector( tempmaxs ); size = max - min; mins.copyTo( edict->mins ); maxs.copyTo( edict->maxs ); size.copyTo( edict->size ); mins.copyTo( edict->fullmins ); maxs.copyTo( edict->fullmaxs ); edict->fullradius = size.length() * 0.5; } else { if ( ( min == edict->mins ) && ( max == edict->maxs ) ) { return; } mins = min; maxs = max; size = max - min; mins.copyTo( edict->mins ); maxs.copyTo( edict->maxs ); size.copyTo( edict->size ); // // get the full mins and maxs for this model // if ( gi.IsModel( edict->s.modelindex ) ) { Vector delta; vec3_t fmins; vec3_t fmaxs; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; gi.CalculateBounds( edict->s.modelindex, edict->s.scale, fmins, fmaxs ); edict->fullmins[ 0 ] = fmins[ grav.x ]; edict->fullmaxs[ 0 ] = fmaxs[ grav.x ]; if ( grav.sign > 0 ) { edict->fullmins[ 1 ] = fmins[ grav.y ]; edict->fullmins[ 2 ] = fmins[ grav.z ]; edict->fullmaxs[ 1 ] = fmaxs[ grav.y ]; edict->fullmaxs[ 2 ] = fmaxs[ grav.z ]; } else { edict->fullmins[ 1 ] = -fmaxs[ grav.y ]; edict->fullmins[ 2 ] = -fmaxs[ grav.z ]; edict->fullmaxs[ 1 ] = -fmins[ grav.y ]; edict->fullmaxs[ 2 ] = -fmins[ grav.z ]; } delta = Vector( edict->fullmaxs ) - Vector( edict->fullmins ); edict->fullradius = delta.length() * 0.5f; } else { mins.copyTo( edict->fullmins ); maxs.copyTo( edict->fullmaxs ); edict->fullradius = size.length() * 0.5; } } link(); } EXPORT_FROM_DLL Vector Entity::getLocalVector ( Vector vec ) { Vector pos; pos[ 0 ] = vec * orientation[ 0 ]; pos[ 1 ] = vec * orientation[ 1 ]; pos[ 2 ] = vec * orientation[ 2 ]; return pos; } EXPORT_FROM_DLL Vector Entity::getParentVector ( Vector vec ) { Vector pos; if ( !bindmaster ) { return vec; } pos[ 0 ] = vec * bindmaster->orientation[ 0 ]; pos[ 1 ] = vec * bindmaster->orientation[ 1 ]; pos[ 2 ] = vec * bindmaster->orientation[ 2 ]; return pos; } EXPORT_FROM_DLL void Entity::link ( void ) { gi.linkentity( edict ); absmin = edict->absmin; absmax = edict->absmax; centroid = ( absmin + absmax ) * 0.5; centroid.copyTo( edict->centroid ); // If this has a parent, then set the areanum the same // as the parent's if ( edict->s.parent ) { edict->areanum = g_edicts[ edict->s.parent ].areanum; } } EXPORT_FROM_DLL void Entity::setOrigin ( Vector org ) { Entity * ent; int i, num; origin = org; if ( bindmaster ) { MatrixTransformVector( origin.vec3(), bindmaster->orientation, worldorigin.vec3() ); worldorigin += bindmaster->worldorigin; worldorigin.copyTo( edict->s.vieworigin ); } else if ( edict->s.parent ) { org.copyTo( edict->s.vieworigin ); ent = ( Entity * )G_GetEntity( edict->s.parent ); worldorigin = ent->centroid; } else { worldorigin = origin; worldorigin.copyTo( edict->s.vieworigin ); } worldorigin.copyTo( edict->s.origin ); link(); // // go through and set our children // num = numchildren; for( i = 0; ( i < MAX_MODEL_CHILDREN ) && num; i++ ) { if ( !children[ i ] ) { continue; } ent = ( Entity * )G_GetEntity( children[ i ] ); ent->setOrigin( ent->origin ); num--; } } EXPORT_FROM_DLL qboolean Entity::GetBone ( const char *name, Vector *pos, Vector *forward, Vector *right, Vector *up ) { vec3_t trans[ 3 ]; vec3_t p1, p2; vec3_t orient; int groupindex; int tri_num; // get the bone information if ( !gi.GetBoneInfo( edict->s.modelindex, name, &groupindex, &tri_num, orient) ) { return false; } if ( !gi.GetBoneTransform( edict->s.modelindex, groupindex, tri_num, orient, edict->s.anim, edict->s.frame, edict->s.scale, trans, p1 ) ) { return false; } if ( forward || right || up ) { R_ConcatRotations( trans, orientation, trans ); } if ( pos ) { MatrixTransformVector( p1, orientation, p2 ); *pos = Vector( p2 ); } if ( forward ) { *forward = Vector( trans[ 0 ] ); } if ( right ) { *right = Vector( trans[ 1 ] ); } if ( up ) { *up = Vector( trans[ 2 ] ); } return true; } EXPORT_FROM_DLL void Entity::setAngles ( Vector ang ) { Entity * ent; float mat[3][3]; int num,i; angles[ 0 ] = angmod( ang[ 0 ] ); angles[ 1 ] = angmod( ang[ 1 ] ); angles[ 2 ] = angmod( ang[ 2 ] ); if ( bindmaster ) { AnglesToMat( angles.vec3(), mat ); R_ConcatRotations( mat, bindmaster->orientation, orientation ); MatrixToEulerAngles( orientation, worldangles.vec3() ); worldangles.copyTo( edict->s.viewangles ); } else if (edict->s.parent) { float trans[3][3]; float local_trans[3][3]; vec3_t p1; ent = ( Entity * )G_GetEntity( edict->s.parent ); ang.copyTo( edict->s.viewangles ); //### added fake "origin" bone that acts like modelindexes in Quake2 if(edict->s.bone.tri_num == BONE_ORIGIN) { //gi.dprintf("fake 'origin' bone: setAngles\n"); // angles = ent->angles; AnglesToMat(angles.vec3(), mat); R_ConcatRotations(mat, ent->orientation, orientation); MatrixToEulerAngles(orientation, worldangles.vec3()); // worldangles = angles; // AnglesToMat(worldangles.vec3(), orientation); // worldangles.copyTo(edict->s.viewangles); } // if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation, // ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) ) else if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation, ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) ) //### { AnglesToMat( angles.vec3(), mat ); R_ConcatRotations( mat, trans, local_trans ); R_ConcatRotations( local_trans, ent->orientation, orientation ); MatrixToEulerAngles( orientation, worldangles.vec3() ); } } else { worldangles = angles; AnglesToMat( worldangles.vec3(), orientation ); worldangles.copyTo( edict->s.viewangles ); } worldangles.copyTo( edict->s.angles ); // Fill the edicts matrix VectorCopy( orientation[ 0 ], edict->s.mat[ 0 ] ); VectorCopy( orientation[ 1 ], edict->s.mat[ 1 ] ); VectorCopy( orientation[ 2 ], edict->s.mat[ 2 ] ); // // go through and set our children // num = numchildren; for (i=0;(i < MAX_MODEL_CHILDREN) && num;i++) { if (!children[i]) continue; ent = ( Entity * )G_GetEntity( children[i] ); ent->setAngles( ent->angles ); num--; } } EXPORT_FROM_DLL qboolean Entity::droptofloor ( float maxfall ) { trace_t trace; Vector end; end = origin; end[ 2 ]-= maxfall; origin += "0 0 1"; trace = G_Trace( origin, mins, maxs, end, this, MASK_SOLID, "Entity::droptofloor" ); if ( trace.fraction == 1 || trace.allsolid ) { groundentity = NULL; return false; } setOrigin( trace.endpos ); groundentity = trace.ent; groundentity_linkcount = trace.ent->linkcount; return true; } void Entity::Damage ( Entity *inflictor, Entity *attacker, int damage, Vector position, Vector direction, Vector normal, int knockback, int dflags, int meansofdeath, int groupnum, int trinum, float damage_multiplier ) { Event *ev; if ( !attacker ) { attacker = world; } if ( !inflictor ) { inflictor = world; } ev = new Event( EV_Damage ); ev->AddInteger( damage ); ev->AddEntity ( inflictor ); ev->AddEntity ( attacker ); ev->AddVector ( position ); ev->AddVector ( direction ); ev->AddVector ( normal ); ev->AddInteger( knockback ); ev->AddInteger( dflags ); ev->AddInteger( meansofdeath ); ev->AddInteger( groupnum ); ev->AddInteger( trinum ); ev->AddFloat ( damage_multiplier ); ProcessEvent ( ev ); } void Entity::DamageEvent ( Event *ev ) { Entity *inflictor; Entity *attacker; int damage; Vector dir; Vector momentum; Event *event; float m; if ( ( takedamage == DAMAGE_NO ) || ( movetype == MOVETYPE_NOCLIP ) ) { return; } damage = ev->GetInteger( 1 ); inflictor = ev->GetEntity( 2 ); attacker = ev->GetEntity( 3 ); // figure momentum add if ( ( inflictor != world ) && ( movetype != MOVETYPE_NONE ) && ( movetype != MOVETYPE_BOUNCE ) && ( movetype != MOVETYPE_PUSH ) && ( movetype != MOVETYPE_STOP ) ) { dir = worldorigin - ( inflictor->worldorigin + ( inflictor->mins + inflictor->maxs ) * 0.5 ); dir.normalize(); if ( mass < 50) { m = 50; } else { m = mass; } momentum = dir * damage * ( 1700.0 / m ); velocity += momentum; } // check for godmode or invincibility if ( flags & FL_GODMODE ) { return; } // Forcefields make objects invulnerable if ( flags & FL_FORCEFIELD ) { float alpha; float radius; Entity *forcefield; // // spawn forcefield // forcefield = new Entity; radius = ( centroid - worldorigin ).length(); forcefield->setModel( "sphere2.def" ); forcefield->setOrigin( centroid ); forcefield->worldorigin.copyTo(forcefield->edict->s.old_origin); forcefield->setMoveType( MOVETYPE_NONE ); forcefield->setSolidType( SOLID_NOT ); forcefield->edict->s.scale = radius / 16; alpha = damage / 100; if ( alpha > 1 ) alpha = 1; if ( alpha < 0.15f ) alpha = 0.15f; forcefield->edict->s.alpha = alpha; forcefield->edict->s.renderfx |= RF_TRANSLUCENT; forcefield->PostEvent( EV_Remove, 0.1f ); return; } // team play damage avoidance //if ( ( global->teamplay == 1 ) && ( edict->team > 0 ) && ( edict->team == attacker->edict->team ) ) // { // return; // } if ( !deathmatch->value && isSubclassOf( Player ) ) { damage *= 0.15; } if ( deadflag ) { // Check for gib. if ( inflictor->isSubclassOf( Projectile ) ) { Event *gibEv; health -= damage; gibEv = new Event( EV_Gib ); gibEv->AddEntity( this ); gibEv->AddFloat( health ); ProcessEvent( gibEv ); } return; } // do the damage health -= damage; if ( health <= 0 ) { if ( attacker ) { event = new Event( EV_GotKill ); event->AddEntity( this ); event->AddInteger( damage ); event->AddEntity( inflictor ); // location based damage event->AddString( ev->GetString( 4 ) ); event->AddInteger( ev->GetInteger( 9 ) ); event->AddInteger( 0 ); attacker->ProcessEvent( event ); } event = new Event( EV_Killed ); event->AddEntity( attacker ); event->AddInteger( damage ); event->AddEntity( inflictor ); // location based damage event->AddString( ev->GetString( 4 ) ); event->AddInteger( ev->GetInteger( 9 ) ); //### needed for the Hoverbike ProcessEvent( event ); return; } if (flags & FL_TESSELATE) { TesselateModel ( this, tess_min_size, tess_max_size, dir, damage, tess_percentage*0.5f, tess_thickness, ev->GetVector( 5 ) ); } if (flags & FL_DARKEN) { edict->s.renderfx |= RF_LIGHTOFFSET; if ( max_health ) { edict->s.lightofs = - ( 40.0f * ( (float)(max_health - health) / (float)max_health ) ); } else { edict->s.lightofs -= damage; } if ( edict->s.lightofs < -127 ) edict->s.lightofs = -127; if ( edict->s.lightofs > 127 ) edict->s.lightofs = 127; } event = new Event( EV_Pain ); event->AddFloat( damage ); event->AddEntity( attacker ); // location based damage event->AddString( ev->GetString( 4 ) ); ProcessEvent( event ); } /* ============ CanDamage Returns true if the inflictor can directly damage the target. Used for explosions and melee attacks. ============ */ qboolean Entity::CanDamage ( Entity *target ) { trace_t trace; Vector pos; // bmodels need special checking because their origin is 0,0,0 if ( target->getMoveType() == MOVETYPE_PUSH ) { pos = ( target->absmin + target->absmax ) * 0.5; trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 1" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } return false; } trace = G_Trace( origin, vec_origin, vec_origin, target->centroid, this, MASK_SHOT, "Entity::CanDamage 2" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( 15, 15, 0 ); trace = G_Trace( origin, vec_origin, vec_origin, pos, this, MASK_SHOT, "Entity::CanDamage 3" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( -15, 15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 4" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( 15, -15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 5" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } pos = target->centroid + Vector( -15, -15, 0 ); trace = G_Trace( origin, vec_zero, vec_zero, pos, this, MASK_SHOT, "Entity::CanDamage 6" ); if ( trace.fraction == 1 || trace.ent == target->edict ) { return true; } return false; } EXPORT_FROM_DLL qboolean Entity::IsTouching ( Entity *e1 ) { if ( e1->absmin.x > absmax.x ) { return false; } if ( e1->absmin.y > absmax.y ) { return false; } if ( e1->absmin.z > absmax.z ) { return false; } if ( e1->absmax.x < absmin.x ) { return false; } if ( e1->absmax.y < absmin.y ) { return false; } if ( e1->absmax.z < absmin.z ) { return false; } return true; } void Entity::StopAnimating ( void ) { // Cancel all animating events last_animation_time = -1; animating = false; total_delta = vec_zero; if ( animDoneEvent ) { CancelEventsOfType( animDoneEvent ); delete animDoneEvent; animDoneEvent = NULL; } } void Entity::StartAnimating ( void ) { // start animating AnimateFrame(); animating = true; } void Entity::NextAnim ( int animnum ) { if ( ( animnum >= 0 ) && ( animnum < gi.NumAnims( edict->s.modelindex ) ) ) { next_anim = animnum; } else { // bad value return; } // get the next anim delta gi.Anim_Delta( edict->s.modelindex, next_anim, next_anim_delta.vec3() ); next_anim_delta *= edict->s.scale; // get the next anim time next_anim_time = gi.Anim_Time( edict->s.modelindex, next_anim ); NextFrame( 0 ); } void Entity::NextFrame ( int framenum ) { if ( ( framenum >= 0 ) && ( framenum <= last_frame_in_anim ) ) { next_frame = framenum; } else { // bad value return; } } void Entity::AnimateFrame ( void ) { float delta; sinmdl_cmd_t * cmds; Event *ev; int i; int j; // // see if we have already animated this frame // if ( ( level.time == last_animation_time ) && ( next_anim < 0 ) && ( next_frame == edict->s.frame + 1 ) ) { return; } // see if we have an anim change pending if (next_anim >= 0) { edict->s.anim = next_anim; last_frame_in_anim = gi.Anim_NumFrames( edict->s.modelindex, edict->s.anim ) - 1; next_anim = -1; if ( edict->s.gunmodelindex ) { const char * animname; animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim ); // // see if the anim exists in the world model // edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, animname ); if ( edict->s.gunanim < 0 ) { // // see if at least we have an idle // edict->s.gunanim = gi.Anim_Random( edict->s.gunmodelindex, "idle" ); } if ( edict->s.gunanim >= 0 ) { num_frames_in_gun_anim = gi.Anim_NumFrames( edict->s.gunmodelindex, edict->s.gunanim ); } else { edict->s.gunanim = 0; num_frames_in_gun_anim = 0; } } } // see if we have a frame change pending if (next_frame >= 0) { edict->s.frame = next_frame; next_frame = -1; } #if 0 { const char * animname; animname = gi.Anim_NameForNum( edict->s.modelindex, edict->s.anim ); warning( "aframe", "%d anim %s frame %d", entnum, animname, edict->s.frame ); } #endif delta = gi.Frame_Time( edict->s.modelindex, edict->s.anim, edict->s.frame ); if ( !delta ) { delta = FRAMETIME; } next_frame = edict->s.frame + ( int )( ( float )FRAMETIME / delta ); // should never be greater...but just in case if ( ( edict->s.frame >= last_frame_in_anim ) || ( next_frame > last_frame_in_anim ) ) { PostEvent( EV_LastFrame, 0 ); if ( animDoneEvent ) { PostEvent( animDoneEvent, 0 ); animDoneEvent = NULL; } next_frame -= last_frame_in_anim+1; } // get the current frame delta gi.Frame_Delta( edict->s.modelindex, edict->s.anim, edict->s.frame, frame_delta.vec3() ); total_delta += frame_delta * edict->s.scale; cmds = gi.Frame_Commands( edict->s.modelindex, edict->s.anim, edict->s.frame ); if ( cmds ) { for( i = 0; i < cmds->num_cmds; i++ ) { ev = new Event( cmds->cmds[ i ].args[ 0 ] ); for( j = 1; j < cmds->cmds[ i ].num_args; j++ ) { ev->AddToken( cmds->cmds[ i ].args[ j ] ); } ProcessEvent( ev ); } } last_animation_time = level.time; // // check to see if we have a secondary animation system going on here // if ( edict->s.gunmodelindex ) { if ( num_frames_in_gun_anim > 1 ) { edict->s.gunframe = ( edict->s.frame * num_frames_in_gun_anim ) / ( last_frame_in_anim + 1 ); } else { edict->s.gunframe = 0; } } } void Entity::RandomAnimate ( const char *animname, Event *endevent ) { int num; num = gi.Anim_Random( edict->s.modelindex, animname ); // // if we have an event that hasn't been processed, kill the current one // if ( animDoneEvent ) { CancelEventsOfType( animDoneEvent ); delete animDoneEvent; animDoneEvent = NULL; } // // see if we even have a valid animation at all // if ( num == -1 ) { if ( endevent ) { PostEvent( endevent, FRAMETIME ); } animDoneEvent = NULL; return; } NextAnim( num ); animDoneEvent = endevent; if ( !animating ) { StartAnimating(); } last_animation_time = -1; } EXPORT_FROM_DLL void Entity::joinTeam ( Entity *teammember ) { Entity *ent; Entity *master; Entity *prev; Entity *next; if ( teammaster && ( teammaster != this ) ) { quitTeam(); } assert( teammember ); if ( !teammember ) { warning( "joinTeam", "Null entity" ); return; } master = teammember->teammaster; if ( !master ) { master = teammember; teammember->teammaster = teammember; teammember->teamchain = this; // make anyone who's bound to me part of the new team for( ent = teamchain; ent != NULL; ent = ent->teamchain ) { ent->teammaster = master; } } else { // skip past the chain members bound to the entity we're teaming up with prev = teammember; next = teammember->teamchain; if ( bindmaster ) { // if we have a bindmaster, joing after any entities bound to the entity // we're joining while( next && next->isBoundTo( teammember ) ) { prev = next; next = next->teamchain; } } else { // if we're not bound to someone, then put us at the end of the team while( next ) { prev = next; next = next->teamchain; } } // make anyone who's bound to me part of the new team and // also find the last member of my team for( ent = this; ent->teamchain != NULL; ent = ent->teamchain ) { ent->teamchain->teammaster = master; } prev->teamchain = this; ent->teamchain = next; } teammaster = master; flags |= FL_TEAMSLAVE; } EXPORT_FROM_DLL void Entity::quitTeam ( void ) { Entity *ent; if ( !teammaster ) { return; } if ( teammaster == this ) { if ( !teamchain->teamchain ) { teamchain->teammaster = NULL; } else { // make next teammate the teammaster for( ent = teamchain; ent; ent = ent->teamchain ) { ent->teammaster = teamchain; } } teamchain->flags &= ~FL_TEAMSLAVE; } else { assert( flags & FL_TEAMSLAVE ); assert( teammaster->teamchain ); ent = teammaster; while( ent->teamchain != this ) { // this should never happen assert( ent->teamchain ); ent = ent->teamchain; } ent->teamchain = teamchain; if ( !teammaster->teamchain ) { teammaster->teammaster = NULL; } } teammaster = NULL; teamchain = NULL; flags &= ~FL_TEAMSLAVE; } EXPORT_FROM_DLL void Entity::EventQuitTeam ( Event *ev ) { quitTeam(); } qboolean Entity::isBoundTo ( Entity *master ) { Entity *ent; for( ent = bindmaster; ent != NULL; ent = ent->bindmaster ) { if ( ent == master ) { return true; } } return false; } EXPORT_FROM_DLL void Entity::bind ( Entity *master ) { float mat[ 3 ][ 3 ]; float local[ 3 ][ 3 ]; Vector ang; assert( master ); if ( !master ) { warning( "bind", "Null master entity" ); return; } if ( master == this ) { warning( "bind", "Trying to bind to oneself." ); return; } // unbind myself from my master unbind(); bindmaster = master; // We are now separated from our previous team and are either // an individual, or have a team of our own. Now we can join // the new bindmaster's team. Bindmaster must be set before // joining the team, or we will be placed in the wrong position // on the team. joinTeam( master ); // calculate local angles TransposeMatrix( bindmaster->orientation, mat ); R_ConcatRotations( mat, orientation, local ); MatrixToEulerAngles( local, ang.vec3() ); setAngles( ang ); setOrigin( getParentVector( origin - bindmaster->worldorigin ) ); return; } EXPORT_FROM_DLL void Entity::unbind ( void ) { Entity *prev; Entity *next; Entity *last; Entity *ent; if ( !bindmaster ) { return; } //bindmaster = NULL; origin = Vector( edict->s.origin ); angles = Vector( edict->s.angles ); if ( !teammaster ) { bindmaster = NULL; //Teammaster already has been freed return; } // We're still part of a team, so that means I have to extricate myself // and any entities that are bound to me from the old team. // Find the node previous to me in the team prev = teammaster; for( ent = teammaster->teamchain; ent && ( ent != this ); ent = ent->teamchain ) { prev = ent; } // If ent is not pointing to me, then something is very wrong. assert( ent ); if ( !ent ) { error( "unbind", "corrupt team chain\n" ); } // Find the last node in my team that is bound to me. // Also find the first node not bound to me, if one exists. last = this; for( next = teamchain; next != NULL; next = next->teamchain ) { if ( !next->isBoundTo( this ) ) { break; } // Tell them I'm now the teammaster next->teammaster = this; last = next; } // disconnect the last member of our team from the old team last->teamchain = NULL; // connect up the previous member of the old team to the node that // follow the last node bound to me (if one exists). if ( teammaster != this ) { prev->teamchain = next; if ( !next && ( teammaster == prev ) ) { prev->teammaster = NULL; } } else if ( next ) { // If we were the teammaster, then the nodes that were not bound to me are now // a disconnected chain. Make them into their own team. for( ent = next; ent->teamchain != NULL; ent = ent->teamchain ) { ent->teammaster = next; } next->teammaster = next; next->flags &= ~FL_TEAMSLAVE; } // If we don't have anyone on our team, then clear the team variables. if ( teamchain ) { // make myself my own team teammaster = this; } else { // no longer a team teammaster = NULL; } flags &= ~FL_TEAMSLAVE; bindmaster = NULL; } EXPORT_FROM_DLL void Entity::EventUnbind ( Event *ev ) { unbind(); } EXPORT_FROM_DLL void Entity::FadeOut ( Event *ev ) { PostEvent( EV_FadeOut, 0.1f ); edict->s.renderfx |= RF_TRANSLUCENT; translucence += 0.03f; if ( translucence >= 0.96f ) { PostEvent( EV_Remove, 0 ); } setAlpha( 1.0f - translucence ); } EXPORT_FROM_DLL void Entity::Fade ( Event *ev ) { float rate = ev->GetFloat( 1 ); edict->s.renderfx |= RF_TRANSLUCENT; translucence += rate; setAlpha( 1.0f - translucence ); if ( translucence <= 1 ) PostEvent( EV_FadeOut, 0.1f ); } EXPORT_FROM_DLL void Entity::SetMassEvent ( Event *ev ) { mass = ev->GetFloat( 1 ); } void Entity::CheckGround ( void ) { Vector point; trace_t trace; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; if ( flags & ( FL_SWIM | FL_FLY ) ) { return; } if ( velocity[ grav.z ] > 100 ) { groundentity = NULL; return; } // if the hull point one-quarter unit down is solid the entity is on ground point = worldorigin; point[ grav.z ] -= 0.25 * grav.sign; trace = G_Trace( worldorigin, mins, maxs, point, this, MASK_MONSTERSOLID, "Entity::CheckGround" ); // check steepness if ( ( ( trace.plane.normal[ grav.z ] * grav.sign ) <= 0.7 ) && !trace.startsolid ) { groundentity = NULL; return; } groundentity = trace.ent; groundentity_linkcount = trace.ent->linkcount; groundplane = trace.plane; groundsurface = trace.surface; groundcontents = trace.contents; if ( !trace.startsolid && !trace.allsolid ) { setOrigin( trace.endpos ); velocity[ grav.z ] = 0; } } //### // NOTE: Didn't bother to make it work with gravaxis void Entity::CheckCeilingGround (void) { Vector point; trace_t trace; // const gravityaxis_t &grav = gravity_axis[ gravaxis ]; if ( flags & ( FL_SWIM | FL_FLY ) ) { return; } // if ( velocity[ grav.z ] < -100 ) if ( velocity.z < -100 ) { groundentity = NULL; return; } // if the hull point one-quarter unit down is solid the entity is on ground point = worldorigin; // point[ grav.z ] -= 0.25 * grav.sign; point.z += 0.25; trace = G_Trace( worldorigin, mins, maxs, point, this, MASK_MONSTERSOLID, "Entity::CheckCeilingGround" ); // check steepness // if ( ( ( trace.plane.normal[ grav.z ] * grav.sign ) <= 0.7 ) && !trace.startsolid ) if(((trace.plane.normal[2]) >= -0.7 ) && !trace.startsolid) { groundentity = NULL; return; } groundentity = trace.ent; groundentity_linkcount = trace.ent->linkcount; groundplane = trace.plane; groundsurface = trace.surface; groundcontents = trace.contents; if ( !trace.startsolid && !trace.allsolid ) { setOrigin( trace.endpos ); // velocity[ grav.z ] = 0; velocity.z = 0; } } //### EXPORT_FROM_DLL void Entity::BecomeSolid ( Event *ev ) { if ( ( model.length() ) && ( ( model[ 0 ] == '*' ) || ( strstr( model.c_str(), ".bsp" ) ) ) ) { setSolidType( SOLID_BSP ); } else { setSolidType( SOLID_BBOX ); } } EXPORT_FROM_DLL void Entity::BecomeNonSolid ( Event *ev ) { setSolidType( SOLID_NOT ); } EXPORT_FROM_DLL void Entity::Ghost ( Event *ev ) { // Make not solid, but send still send over whether it is hidden or not setSolidType( SOLID_NOT ); edict->svflags &= ~SVF_NOCLIENT; } EXPORT_FROM_DLL void Entity::PlaySound ( Event *ev ) { char name[ 128 ]; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // name[0] = 0; volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: strcpy( name, ev->GetString( i ) ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } channel |= CHAN_NO_PHS_ADD; sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::StopSound ( Event *ev ) { if (ev->NumArgs() < 1) stopsound( CHAN_BODY ); else stopsound( ev->GetInteger( 1 ) ); } EXPORT_FROM_DLL void Entity::SetGravityAxis ( int axis ) { Vector min; Vector max; if ( ( axis < 0 ) || ( axis > 5 ) ) { axis = 0; } // don't do anything if the axis has been already set if ( axis == gravaxis ) return; edict->s.effects &= ~( EF_GRAVITY_AXIS_0 | EF_GRAVITY_AXIS_1 | EF_GRAVITY_AXIS_2 ); edict->s.effects |= GRAVITYAXIS_TO_EFFECTS( axis ); gravaxis = EFFECTS_TO_GRAVITYAXIS( edict->s.effects ); groundentity = NULL; const gravityaxis_t &grav = gravity_axis[ gravaxis ]; min[ grav.x ] = mins[ 0 ]; min[ grav.y ] = mins[ 1 ] * grav.sign; min[ grav.z ] = mins[ 2 ] * grav.sign; max[ grav.x ] = maxs[ 0 ]; max[ grav.y ] = maxs[ 1 ] * grav.sign; max[ grav.z ] = maxs[ 2 ] * grav.sign; setSize( min, max ); } EXPORT_FROM_DLL void Entity::GravityAxisEvent ( Event *ev ) { SetGravityAxis( ev->GetInteger( 1 ) ); } EXPORT_FROM_DLL void Entity::BindEvent ( Event *ev ) { Entity *ent; ent = ev->GetEntity( 1 ); if ( ent ) { bind( ent ); } } EXPORT_FROM_DLL void Entity::JoinTeam ( Event *ev ) { Entity *ent; ent = ev->GetEntity( 1 ); if ( ent ) { joinTeam( ent ); } } EXPORT_FROM_DLL void Entity::SetLight ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_r = ev->GetFloat( 1 ); edict->s.color_g = ev->GetFloat( 2 ); edict->s.color_b = ev->GetFloat( 3 ); edict->s.radius = ev->GetFloat( 4 ); } EXPORT_FROM_DLL void Entity::LightOn ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; } EXPORT_FROM_DLL void Entity::LightOff ( Event *ev ) { edict->s.renderfx &= ~RF_DLIGHT; } EXPORT_FROM_DLL void Entity::LightRed ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_r = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightGreen ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_g = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightBlue ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.color_b = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::LightRadius ( Event *ev ) { edict->s.renderfx |= RF_DLIGHT; edict->s.radius = ev->GetFloat( 1 ); } EXPORT_FROM_DLL void Entity::SetHealth ( Event *ev ) { health = ev->GetFloat( 1 ); max_health = health; } EXPORT_FROM_DLL void Entity::SetSize ( Event *ev ) { Vector min, max; min = ev->GetVector( 1 ); max = ev->GetVector( 2 ); setSize( min, max ); } EXPORT_FROM_DLL void Entity::SetScale ( Event *ev ) { setScale( ev->GetFloat( 1 ) ); } EXPORT_FROM_DLL void Entity::SetAlpha ( Event *ev ) { setAlpha( ev->GetFloat( 1 ) ); } EXPORT_FROM_DLL void Entity::SetOrigin ( Event *ev ) { setOrigin( ev->GetVector( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTargetName ( Event *ev ) { SetTargetName( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTarget ( Event *ev ) { SetTarget( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetKillTarget ( Event *ev ) { SetKillTarget( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetAngles ( Event *ev ) { setAngles( ev->GetVector( 1 ) ); } EXPORT_FROM_DLL void Entity::CourseAnglesEvent ( Event *ev ) { // Angles will be sent over the net as 8-bit values (default) edict->s.effects &= ~EF_SMOOTHANGLES; } EXPORT_FROM_DLL void Entity::SmoothAnglesEvent ( Event *ev ) { // Angles will be sent over the net as 16-bit values for smoother rotation (or slow rotation) edict->s.effects |= EF_SMOOTHANGLES; } EXPORT_FROM_DLL void Entity::RegisterAlias ( Event *ev ) { if ( ev->NumArgs() < 3 ) { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), 1 ); } else { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), ev->GetString( 2 ), ev->GetInteger( 3 ) ); } } EXPORT_FROM_DLL void Entity::RegisterAliasAndCache ( Event *ev ) { int length; str realname; const char * ptr; realname = ev->GetString( 2 ); if ( ev->NumArgs() < 3 ) { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(), 1 ); } else { gi.Alias_Add( edict->s.modelindex, ev->GetString( 1 ), realname.c_str(), ev->GetInteger( 3 ) ); } length = realname.length(); ptr = realname.c_str(); ptr += length - 4; if ( ( length > 4 ) && ( !strcmpi( ptr, ".wav" ) ) ) { gi.soundindex( realname.c_str() ); } else if ( ( length > 4 ) && ( !strcmpi( ptr, ".def" ) ) ) { gi.modelindex( realname.c_str() ); } } EXPORT_FROM_DLL void Entity::positioned_sound ( Vector origin, str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { if ( soundname.length() ) { gi.positioned_sound( worldorigin.vec3(), edict, channel, gi.soundindex( soundname.c_str() ), volume, attenuation, timeofs, pitch, fadetime, flags ); } else { warning( "sound", "Null sample pointer" ); } } EXPORT_FROM_DLL void Entity::RandomPositionedSound ( Vector origin, str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { positioned_sound( worldorigin, name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } else { warning( "RandomPositionedSound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::sound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { if ( soundname.length() ) { gi.sound( edict, channel, gi.soundindex( soundname.c_str() ), volume, attenuation, timeofs, pitch, fadetime, flags ); } else { warning( "sound", "Null sample pointer" ); } } EXPORT_FROM_DLL void Entity::RandomGlobalSound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } else { warning( "RandomGlobalSound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::RandomSound ( str soundname, float volume, int channel, int attenuation, float pitch, float timeofs, float fadetime, int flags ) { str realname; realname = GetRandomAlias( soundname ); sound( realname, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::RandomSound ( Event *ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } channel |= CHAN_NO_PHS_ADD; RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::RandomPHSSound ( Event *ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } RandomSound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::PHSSound ( Event *ev ) { char name[ 128 ]; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; // // set defaults // name[0] = 0; volume = 1.0f; channel = CHAN_BODY; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch (i-1) { case 0: strcpy( name, ev->GetString( i ) ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } EXPORT_FROM_DLL void Entity::EntitySound ( Event *ev ) { edict->s.sound = gi.soundindex( ev->GetString( 1 ) ); if ( ev->NumArgs() > 1 ) { int attenuation; attenuation = ev->GetInteger( 2 ); if (attenuation > 3) attenuation = 3; if (attenuation < 0) attenuation = 0; edict->s.sound |= attenuation<<14; } else { edict->s.sound |= ATTN_IDLE<<14; } } EXPORT_FROM_DLL void Entity::StopEntitySound ( Event *ev ) { edict->s.sound = 0; } EXPORT_FROM_DLL void Entity::RandomEntitySound ( Event *ev ) { const char * alias; const char * soundname; alias = ev->GetString( 1 ); soundname = gi.Alias_FindRandom( edict->s.modelindex, alias ); edict->s.sound = gi.soundindex( soundname ); if ( ev->NumArgs() > 1 ) { int attenuation; attenuation = ev->GetInteger( 2 ); if (attenuation > 3) attenuation = 3; if (attenuation < 0) attenuation = 0; edict->s.sound |= attenuation<<14; } else { edict->s.sound |= ATTN_IDLE<<14; } } EXPORT_FROM_DLL void Entity::RandomGlobalEntitySound ( str soundname, int attenuation ) { const char * name; name = gi.GlobalAlias_FindRandom( soundname.c_str() ); if ( name ) { edict->s.sound = gi.soundindex( name ); bound( attenuation, 0, 3 ); edict->s.sound |= attenuation<<14; } else { warning( "RandomGlobalEntitySound", "Couldn't find alias for %s", soundname.c_str() ); } } EXPORT_FROM_DLL void Entity::RandomGlobalEntitySoundEvent ( Event *ev ) { const char *alias; int attenuation; alias = ev->GetString( 1 ); attenuation = ATTN_IDLE; if ( ev->NumArgs() > 1 ) { attenuation = ev->GetInteger( 2 ); } RandomGlobalEntitySound( alias, attenuation ); } EXPORT_FROM_DLL qboolean Entity::attach ( int parent_entity_num, int group_num, int tri_num, Vector orient ) { int i; Entity * parent; if ( entnum == parent_entity_num ) { warning("attach","Trying to attach to oneself." ); return false; } if (edict->s.parent) detach(); // // get the parent // parent = ( Entity * )G_GetEntity( parent_entity_num ); if (parent->numchildren < MAX_MODEL_CHILDREN) { // // find a free spot in the parent // for ( i=0; i < MAX_MODEL_CHILDREN; i++ ) if (!parent->children[i]) { break; } parent->children[i] = entnum; parent->numchildren++; edict->s.parent = parent_entity_num; edict->s.bone.group_num = group_num; edict->s.bone.tri_num = tri_num; VectorCopy( orient.vec3(), edict->s.bone.orientation ); setOrigin( origin ); return true; } return false; } EXPORT_FROM_DLL void Entity::detach ( void ) { int i; int num; Entity * parent; if (!edict->s.parent) return; parent = ( Entity * )G_GetEntity( edict->s.parent ); if (!parent) return; for ( i=0,num = parent->numchildren; i < MAX_MODEL_CHILDREN; i++ ) { if (!parent->children[i]) { continue; } if (parent->children[i] == entnum) { parent->children[i] = 0; parent->numchildren--; break; } num--; if (!num) break; } edict->s.parent = 0; // // i don't think we want to do this automatically, we might later, but for right now lets not // //setOrigin( edict->origin + parent->origin ); } void Entity::AnimEvent ( Event *ev ) { int num; num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) ); NextAnim( num ); if ( !animating ) { StartAnimating(); } } void Entity::StartAnimatingEvent ( Event *ev ) { StartAnimating(); } void Entity::StopAnimatingEvent ( Event *ev ) { StopAnimating(); } void Entity::EndAnimEvent ( Event *ev ) { PostEvent( EV_LastFrame, 0 ); if ( animDoneEvent ) { PostEvent( animDoneEvent, 0 ); animDoneEvent = NULL; } next_frame = 0; } void Entity::NextAnimEvent ( Event *ev ) { int num; num = gi.Anim_Random( edict->s.modelindex, ev->GetString( 1 ) ); NextAnim( num ); } void Entity::NextFrameEvent ( Event *ev ) { NextFrame( ev->GetInteger( 1 ) ); } void Entity::PrevFrameEvent ( Event *ev ) { edict->s.prevframe = ev->GetInteger( 1 ); } void Entity::SetFrameEvent ( Event *ev ) { int framenum; framenum = ev->GetInteger( 1 ); edict->s.frame = framenum; NextFrame( framenum + 1 ); } void Entity::Tesselate(Event *ev) { Vector origin, dir, temp; Entity * ent; int i, power, min_size, max_size, thickness; float percentage; // dir is 1 // power is 2 // minsize is 3 // maxsize is 4 // percentage is 5 // thickness 6 // entity is 7 // origin 8 // // initialize some variables // ent = this; min_size = TESS_DEFAULT_MIN_SIZE; max_size = TESS_DEFAULT_MIN_SIZE; thickness = min_size; percentage = TESS_DEFAULT_PERCENT; VectorCopy( vec3_origin, origin ); VectorCopy( vec3_origin, dir ); for ( i = 1; i <= ev->NumArgs(); i++ ) { switch( i ) { case 1: temp = ev->GetVector( i ); temp.AngleVectors( &dir, NULL, NULL ); break; case 2: power = ev->GetInteger( i ); break; case 3: min_size = ev->GetInteger( i ); break; case 4: max_size = ev->GetInteger( i ); break; case 5: percentage = ev->GetFloat( i ); break; case 6: thickness = ev->GetInteger( i ); break; case 7: ent = ev->GetEntity( i ); break; case 8: origin = ev->GetVector( i ); break; } } TesselateModel ( ent, min_size, max_size, dir, power, percentage, thickness, origin ); } void Entity::SetShatterMinSize( Event *ev ) { tess_min_size = ev->GetInteger( 1 ); } void Entity::SetShatterMaxSize( Event *ev ) { tess_max_size = ev->GetInteger( 1 ); } void Entity::SetShatterThickness( Event *ev ) { tess_thickness = ev->GetInteger( 1 ); } void Entity::SetShatterPercentage( Event *ev ) { tess_percentage = ev->GetFloat( 1 ) / 100.0f;; } void Entity::Flags( Event *ev ) { const char *flag; int mask; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = FLAG_IGNORE; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "blood" ) ) mask = FL_BLOOD; else if ( !stricmp( flag, "sparks" ) ) mask = FL_SPARKS; else if ( !stricmp( flag, "shatter" ) ) mask = FL_TESSELATE; else if ( !stricmp( flag, "blast" ) ) mask = FL_BLASTMARK; else if ( !stricmp( flag, "die_shatter" ) ) mask = FL_DIE_TESSELATE; else if ( !stricmp( flag, "explode" ) ) mask = FL_DIE_EXPLODE; else if ( !stricmp( flag, "die_gibs" ) ) mask = FL_DIE_GIBS; else if ( !stricmp( flag, "darken" ) ) mask = FL_DARKEN; else if ( !stricmp( flag, "forcefield" ) ) mask = FL_FORCEFIELD; else if ( !stricmp( flag, "stealth" ) ) mask = FL_STEALTH; else { action = FLAG_IGNORE; ev->Error( "Unknown flag '%s'", flag ); } switch (action) { case FLAG_SET: // preserver non-configurable bits flags &= (FL_BLOOD - 1); case FLAG_ADD: flags |= mask; break; case FLAG_CLEAR: flags &= ~mask; break; case FLAG_IGNORE: break; } } if ( parentmode->value ) { if ( flags & (FL_BLOOD|FL_DIE_GIBS) ) { flags &= ~FL_BLOOD; flags &= ~FL_DIE_GIBS; edict->s.effects &= ~EF_WARM; //### } } } void Entity::Effects( Event *ev ) { const char *flag; int mask=0; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = 0; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "rotate" ) ) mask = EF_ROTATE; else if ( !stricmp( flag, "rocket" ) ) mask = EF_ROCKET; else if ( !stricmp( flag, "gib" ) ) mask = EF_GIB; else if ( !stricmp( flag, "pulse" ) ) mask = EF_PULSE; else if ( !stricmp( flag, "everyframe" ) ) mask = EF_EVERYFRAME; else if ( !stricmp( flag, "autoanimate" ) ) mask = EF_AUTO_ANIMATE; //### else if ( !stricmp( flag, "warm" ) ) mask = EF_WARM; else if ( !stricmp( flag, "stingerrocket" ) ) mask = EF_ANIMEROCKET; else if ( !stricmp( flag, "flames" ) ) mask = EF_FLAMES; else if ( !stricmp( flag, "plasma1" ) ) mask = EF_PLASMATRAIL1; else if ( !stricmp( flag, "plasma2" ) ) mask = EF_PLASMATRAIL2; else if ( !stricmp( flag, "thruster" ) ) mask = EF_HOVERTHRUST; else if ( !stricmp( flag, "turbo" ) ) mask = EF_HOVERTURBO; //### else { action = FLAG_IGNORE; ev->Error( "Unknown token %s.", flag ); } switch (action) { case FLAG_SET: case FLAG_ADD: edict->s.effects |= mask; break; case FLAG_CLEAR: edict->s.effects &= ~mask; break; case FLAG_IGNORE: break; } } } void Entity::RenderEffects( Event *ev ) { const char *flag; int mask=0; int action; int i; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 for ( i = 1; i <= ev->NumArgs(); i++ ) { action = 0; flag = ev->GetString( i ); switch( flag[0] ) { case '+': action = FLAG_ADD; flag++; break; case '-': action = FLAG_CLEAR; flag++; break; default: action = FLAG_SET; break; } if ( !stricmp( flag, "minlight" ) ) mask = RF_MINLIGHT; else if ( !stricmp( flag, "fullbright" ) ) mask = RF_FULLBRIGHT; else if ( !stricmp( flag, "envmapped" ) ) mask = RF_ENVMAPPED; else if ( !stricmp( flag, "glow" ) ) mask = RF_GLOW; else if ( !stricmp( flag, "dontdraw" ) ) mask = RF_DONTDRAW; else { action = FLAG_IGNORE; ev->Error( "Unknown token %s.", flag ); } switch (action) { case FLAG_SET: case FLAG_ADD: edict->s.renderfx |= mask; break; case FLAG_CLEAR: edict->s.renderfx &= ~mask; break; case FLAG_IGNORE: break; } } } void Entity::BroadcastSound ( Event *soundevent, int channel, Event &event, float radius ) { Sentient *ent; Vector delta; Event *ev; str name; float r2; float dist2; float volume; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; int n; #if 0 int count; count = 0; #endif if ( ( ( int )event != ( int )NullEvent ) && !( this->flags & FL_NOTARGET ) ) { r2 = radius * radius; n = SentientList.NumObjects(); for( i = 1; i <= n; i++ ) { ent = SentientList.ObjectAt( i ); if ( ent->deadflag || ( ent == this ) ) { continue; } delta = centroid - ent->centroid; // dot product returns length squared dist2 = delta * delta; if ( ( dist2 <= r2 ) && ( ( edict->areanum == ent->edict->areanum ) || ( gi.AreasConnected( edict->areanum, ent->edict->areanum ) ) ) ) { ev = new Event( event ); ev->AddEntity( this ); ev->AddVector( worldorigin ); ent->PostEvent( ev, 0 ); #if 0 count++; #endif } } } #if 0 gi.dprintf( "Broadcast sound to %d entities\n", count ); #endif if ( !soundevent->NumArgs() ) { return; } // // set defaults // volume = 1.0f; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = 0; for ( i = 1 ; i <= soundevent->NumArgs() ; i++ ) { switch (i-1) { case 0: name = soundevent->GetString( i ); break; case 1: volume = soundevent->GetFloat( i ); break; case 2: attenuation = soundevent->GetFloat( i ); break; case 3: pitch = soundevent->GetFloat( i ); break; case 4: timeofs = soundevent->GetFloat( i ); break; case 5: fadetime = soundevent->GetFloat( i ); break; case 6: flags = soundevent->GetInteger( i ); break; default: break; } } RandomSound( name.c_str(), volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } void Entity::WeaponSound ( Event *ev ) { BroadcastSound( ev, CHAN_WEAPON, EV_HeardWeapon, SOUND_WEAPON_RADIUS ); } void Entity::MovementSound ( Event *ev ) { static int movement_count = 0; // // movement sounds now happen very infrequently, unless this is a client // if ( isClient() || ( movement_count++ > 15 ) ) { BroadcastSound( ev, CHAN_BODY, EV_HeardMovement, SOUND_MOVEMENT_RADIUS ); if ( movement_count > 15 ) movement_count = 0; } } void Entity::PainSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardPain, SOUND_PAIN_RADIUS ); } void Entity::DeathSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardDeath, SOUND_DEATH_RADIUS ); } void Entity::BreakingSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardBreaking, SOUND_BREAKING_RADIUS ); } void Entity::DoorSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardDoor, SOUND_DOOR_RADIUS ); } void Entity::MutantSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardMutant, SOUND_MUTANT_RADIUS ); } void Entity::VoiceSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardVoice, SOUND_VOICE_RADIUS ); } void Entity::MachineSound ( Event *ev ) { BroadcastSound( ev, CHAN_AUTO, EV_HeardMachine, SOUND_MACHINE_RADIUS ); } void Entity::RadioSound ( Event *ev ) { BroadcastSound( ev, CHAN_VOICE, EV_HeardRadio, SOUND_RADIO_RADIUS ); } void Entity::SpawnParticles ( Event *ev ) { int i; Vector norm; int count; int lightstyle; norm = orientation[0]; count = 4; lightstyle = 122; for ( i = 1 ; i <= ev->NumArgs() ; i++ ) { switch( i ) { case 1: norm = ev->GetVector( 1 ); break; case 2: count = ev->GetInteger( 2 ); break; case 3: lightstyle = ev->GetInteger( 3 ); break; case 4: flags = ev->GetInteger( 4 ); default: break; } } Particles( worldorigin, norm, count, lightstyle, flags ); } void Entity::Prethink ( void ) { } void Entity::Postthink ( void ) { } void Entity::SetWaterType ( void ) { qboolean isinwater; watertype = gi.pointcontents( worldorigin.vec3() ); isinwater = watertype & MASK_WATER; if ( isinwater ) { waterlevel = 1; } else { waterlevel = 0; } } void Entity::DamageSkin ( trace_t * trace, float damage ) { int group; group = trace->intersect.parentgroup; if ( !edict->s.groups[ group ] ) { edict->s.groups[ group ]++; } } void Entity::Kill ( Event *ev ) { health = 0; Damage( this, this, 10, worldorigin, vec_zero, vec_zero, 0, 0, MOD_SUICIDE, -1, -1, 1.0f ); } void Entity::GroupModelEvent ( Event *ev ) { const char * group_name; const char * token; int i, group_num, argnum, flags; int mask; int action; qboolean do_all; #define FLAG_IGNORE 0 #define FLAG_SET 1 #define FLAG_CLEAR 2 #define FLAG_ADD 3 do_all = false; // "group" is first group_name = ev->GetString( 1 ); if ( str( group_name ) != str( "all" ) ) { group_num = gi.Group_NameToNum( edict->s.modelindex, group_name ); if (group_num < 0) { ev->Error( "group %s not found.", group_name ); } } else { group_num = 0; do_all = true; } flags = 0; argnum = 2; for ( i = argnum; i <= ev->NumArgs() ; i++ ) { token = ev->GetString( i ); action = 0; switch( token[0] ) { case '+': action = FLAG_ADD; token++; break; case '-': action = FLAG_CLEAR; token++; break; default: action = FLAG_SET; break; } if (!strcmpi( token, "skin1")) { mask = MDL_GROUP_SKINOFFSET_BIT0; } else if (!strcmpi (token, "skin2")) { mask = MDL_GROUP_SKINOFFSET_BIT1; } else if (!strcmpi (token, "nodraw")) { mask = MDL_GROUP_NODRAW; } else if (!strcmpi (token, "envmapped")) { mask = MDL_GROUP_ENVMAPPED_SILVER; } else if (!strcmpi (token, "goldenvmapped")) { mask = MDL_GROUP_ENVMAPPED_GOLD; } else if (!strcmpi (token, "translucent33")) { mask = MDL_GROUP_TRANSLUCENT_33; } else if (!strcmpi (token, "translucent66")) { mask = MDL_GROUP_TRANSLUCENT_66; } else if (!strcmpi (token, "fullbright")) { mask = MDL_GROUP_FULLBRIGHT; } else { ev->Error( "Unknown token %s.", token ); action = FLAG_IGNORE; } for( ; group_num < edict->s.numgroups ; group_num++ ) { switch (action) { case FLAG_SET: // clear out group edict->s.groups[ group_num ] = 0; case FLAG_ADD: edict->s.groups[ group_num ] |= mask; break; case FLAG_CLEAR: edict->s.groups[ group_num ] &= ~mask; break; case FLAG_IGNORE: break; } if ( !do_all ) break; } } } void Entity::DialogEvent ( Event * ev ) { str name; float volume; int channel; float attenuation; float pitch; float timeofs; float fadetime; int flags; int i; str icon_name; str dialog_text; if ( ( dialog->value == 1 ) || ( dialog->value == 3 ) ) { icon_name = ev->GetString( 1 ); dialog_text = ev->GetString( 2 ); SendDialog( icon_name.c_str(), dialog_text.c_str() ); } if ( ( dialog->value == 0 ) || ( dialog->value == 1 ) ) return; // // set defaults // volume = 1.0f; channel = CHAN_DIALOG_SECONDARY | CHAN_NO_PHS_ADD; attenuation = ATTN_NORM; pitch = 1.0f; timeofs = 0; fadetime = 0; flags = SOUND_SYNCH; for ( i = 3 ; i <= ev->NumArgs() ; i++ ) { switch (i-3) { case 0: name = ev->GetString( i ); break; case 1: volume = ev->GetFloat( i ); break; case 2: channel = ev->GetInteger( i ); break; case 3: attenuation = ev->GetFloat( i ); break; case 4: pitch = ev->GetFloat( i ); break; case 5: timeofs = ev->GetFloat( i ); break; case 6: fadetime = ev->GetFloat( i ); break; case 7: flags = ev->GetInteger( i ); break; default: break; } } sound( name, volume, channel, attenuation, pitch, timeofs, fadetime, flags ); } void Entity::AttachEvent ( Event * ev ) { Entity * parent; const char * bone; int groupindex; int tri_num; vec3_t orient; parent = ev->GetEntity( 1 ); bone = ev->GetString( 2 ); if ( !parent ) return; if ( gi.GetBoneInfo( parent->edict->s.modelindex, bone, &groupindex, &tri_num, orient ) ) { attach( parent->entnum, groupindex, tri_num, Vector(orient) ); } else { warning( "AttachEvent", "GetBoneInfo failed for bone %s", bone ); } } void Entity::AttachModelEvent ( Event * ev ) { ThrowObject * tobj; const char * bone; int groupindex; int tri_num; vec3_t orient; str modelname; tobj = new ThrowObject; modelname = ev->GetString( 1 ); bone = ev->GetString( 2 ); if ( ev->NumArgs() > 2 ) { tobj->SetTargetName( ev->GetString( 3 ) ); } tobj->setModel( modelname ); if ( gi.GetBoneInfo( edict->s.modelindex, bone, &groupindex, &tri_num, orient ) ) { tobj->attach( this->entnum, groupindex, tri_num, Vector(orient) ); } else { warning( "AttachModelEvent", "GetBoneInfo failed for bone %s", bone ); } } void Entity::DetachEvent ( Event * ev ) { float trans[ 3 ][ 3 ]; Entity * ent; vec3_t p1, p2; if ( edict->s.parent <= 0 ) { return; } ent = ( Entity * )G_GetEntity( edict->s.parent ); if ( gi.GetBoneTransform( ent->edict->s.modelindex, edict->s.bone.group_num, edict->s.bone.tri_num, edict->s.bone.orientation, ent->edict->s.anim, ent->edict->s.frame, ent->edict->s.scale, trans, p1 ) ) { VectorAdd( p1, origin.vec3(), p1 ); MatrixTransformVector( p1, ent->orientation, p2 ); VectorAdd( p2, ent->worldorigin, p2 ); } detach(); setOrigin( p2 ); worldorigin.copyTo( edict->s.old_origin ); } void Entity::TakeDamageEvent ( Event * ev ) { takedamage = DAMAGE_YES; } void Entity::NoDamageEvent ( Event * ev ) { takedamage = DAMAGE_NO; } void Entity::SetSkinEvent ( Event *ev ) { int num; num = gi.Skin_NumForName( edict->s.modelindex, ev->GetString( 1 ) ); if ( num >= 0 ) { edict->s.skinnum = num; } else { ev->Error( "Could not find %s as a skin name.\n", ev->GetString( 1 ) ); } } void Entity::Lightoffset ( Event *ev ) { edict->s.lightofs = ev->GetFloat( 1 ); if ( edict->s.lightofs != 0 ) edict->s.renderfx |= RF_LIGHTOFFSET; else edict->s.renderfx &= ~RF_LIGHTOFFSET; } void Entity::Gravity ( Event *ev ) { gravity = ev->GetFloat( 1 ); } void Entity::Minlight ( Event *ev ) { if ( ev->GetInteger( 1 ) ) edict->s.renderfx |= RF_MINLIGHT; else edict->s.renderfx &= ~RF_MINLIGHT; } void Entity::UseBoundingBoxEvent ( Event *ev ) { edict->svflags |= SVF_USEBBOX; } void Entity::HurtEvent ( Event *ev ) { Vector normal; float dmg; if ( ev->NumArgs() < 1 ) { dmg = 50; } else { dmg = ev->GetFloat( 1 ); } normal = Vector( orientation[ 0 ] ); Damage( world, world, dmg, worldorigin, vec_zero, normal, dmg, 0, MOD_CRUSH, -1, -1, 1.0f ); } void Entity::IfSkillEvent ( Event *ev ) { float skilllevel; skilllevel = ev->GetFloat( 1 ); if ( skill->value == skilllevel ) { int argc; int numargs; Event *event; int i; numargs = ev->NumArgs(); argc = numargs - 2 + 1; event = new Event( ev->GetToken( 2 ) ); for( i = 1; i < argc; i++ ) { event->AddToken( ev->GetToken( 2 + i ) ); } ProcessEvent( event ); } } void Entity::Censor ( Event *ev ) { Vector delta; float oldsize; float newsize; if ( !parentmode->value ) return; oldsize = size.length(); setSolidType( SOLID_NOT ); setModel( "censored.def" ); gi.CalculateBounds( edict->s.modelindex, 1, mins.vec3(), maxs.vec3() ); delta = maxs - mins; newsize = delta.length(); edict->s.scale = oldsize / newsize; mins *= edict->s.scale; maxs *= edict->s.scale; setSize( mins, maxs ); setOrigin( origin ); } //=============================================================== //### 2015 added stuff // extended targeting stuff EXPORT_FROM_DLL void Entity::SetTarget2 (const char *text) { if ( text ) { target2 = text; } else { target2 = ""; } } EXPORT_FROM_DLL void Entity::SetTarget3 (const char *text) { if ( text ) { target3 = text; } else { target3 = ""; } } EXPORT_FROM_DLL void Entity::SetTarget4 (const char *text) { if ( text ) { target4 = text; } else { target4 = ""; } } EXPORT_FROM_DLL void Entity::SetTargetName2 (const char *text) { if ( targetname2.length() && world ) { world->RemoveTargetEntity( targetname2, this ); } if ( text ) { targetname2 = text; } else { targetname2 = ""; } if ( targetname2.length() && world ) { world->AddTargetEntity( targetname2, this ); } } EXPORT_FROM_DLL void Entity::SetKillTarget2 (const char *text) { if ( text ) { killtarget2 = text; } else { killtarget2 = ""; } } EXPORT_FROM_DLL void Entity::SetTargetName2 (Event *ev) { SetTargetName2( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTarget2 (Event *ev) { SetTarget2( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTarget3 (Event *ev) { SetTarget3( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetTarget4 (Event *ev) { SetTarget4( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetKillTarget2 (Event *ev) { SetKillTarget2( ev->GetString( 1 ) ); } EXPORT_FROM_DLL void Entity::SetGroupAlpha (Event *ev) { float alpha; alpha = ev->GetFloat(1); if ( alpha > 1.0f ) { alpha = 1.0f; } if ( alpha < 0 ) { alpha = 0; } translucence = 1.0f - alpha; edict->s.alpha = alpha; edict->s.renderfx &= ~RF_TRANSLUCENT; // if ( ( translucence > 0 ) && ( translucence <= 1.0 ) ) // { // edict->s.renderfx |= RF_TRANSLUCENT; // } }