//----------------------------------------------------------------------------- // // $Logfile:: /EF2/Code/DLLs/game/weaputils.cpp $ // $Revision:: 136 $ // $Author:: Singlis $ // $Date:: 9/26/03 2:36p $ // // Copyright (C) 1998 by Ritual Entertainment, Inc. // All rights reserved. // // This source may not be distributed and/or modified without // expressly written permission by Ritual Entertainment, Inc. // // // DESCRIPTION: // General Weapon Utility Functions #include "_pch_cpp.h" //#include "g_local.h" #include "weaputils.h" #include "specialfx.h" #include "sentient.h" #include "actor.h" #include "decals.h" #include "weapon.h" #include "player.h" #include "mp_manager.hpp" #include void FlashPlayers ( const Vector &org, float r, float g, float b, float a, float minradius, float radius, float minTime, float time, int type ); qboolean MeleeAttack ( const Vector &pos, const Vector &original_end, float damage, Entity *attacker, meansOfDeath_t means_of_death, float attack_width, float attack_min_height, float attack_max_height, float knockback, qboolean hit_dead, Container*victimlist, Weapon *weapon, bool critical ) { trace_t trace; Entity *victim; Vector dir; float world_dist; Vector new_pos; Entity *skip_ent; qboolean hit_something = false; Vector mins; Vector maxs; Container potential_victimlist; int i; int num_traces; Vector end; end = original_end; /* if ( attack_width == 0 ) attack_width = 15; if ( attack_min_height == 0 ) attack_min_height = -15; if ( attack_max_height == 0 ) attack_max_height = 15; */ if ( attacker && attacker->isSubclassOf( Player ) && ( means_of_death != MOD_FIRE ) && ( means_of_death != MOD_ON_FIRE ) ) { Player *player = (Player *)attacker; player->shotFired(); } // See how far the world is away dir = end - pos; world_dist = dir.length(); new_pos = pos; skip_ent = attacker; num_traces = 0; while( new_pos != end ) { trace = G_Trace( pos, vec_zero, vec_zero, end, skip_ent, MASK_SOLID, false, "MeleeAttack - World test" ); num_traces++; if ( trace.fraction < 1.0f ) { if ( attacker->isSubclassOf( Actor ) ) { Actor *act = (Actor*)attacker; if ( trace.entityNum == ENTITYNUM_WORLD ) act->SetActorFlag ( "meleehitworld" , true ); else act->SetActorFlag ( "meleehitworld" , false ); } if ( ( trace.entityNum == ENTITYNUM_WORLD ) || ( trace.ent && trace.ent->entity && !trace.ent->entity->takedamage ) ) { dir = trace.endpos - pos; world_dist = dir.length(); break; } else { // Make sure we don't go backwards any in our trace if ( ( Vector( new_pos - pos ).length() + 0.001f ) >= Vector( trace.endpos - pos ).length() ) break; if ( num_traces > 10 ) { // We have done too many traces, stop here dir = trace.endpos - pos; world_dist = dir.length(); break; } new_pos = trace.endpos; if ( trace.ent ) skip_ent = trace.ent->entity; } } else { break; } } // Find things hit dir = end - pos; dir.normalize(); end = pos + ( dir * world_dist ); victim = NULL; mins = Vector( -attack_width, -attack_width, attack_min_height ); maxs = Vector( attack_width, attack_width, attack_max_height ); if ( g_showbullettrace->integer ) { G_DebugBBox(pos, mins, maxs, 1.0f, 0.0f, 0.0f, 1.0f ); } G_TraceEntities( pos, mins, maxs, end, &potential_victimlist, MASK_MELEE ); for( i = 1 ; i <= potential_victimlist.NumObjects() ; i++ ) { victim = potential_victimlist.ObjectAt( i ); if ( victimlist && victimlist->ObjectInList(victim) ) continue; if ( victim && victim->takedamage && ( victim != attacker ) ) { dir = end - pos; dir.normalize(); if ( dir == vec_zero ) { dir = victim->centroid - pos; dir.normalize(); } if ( victim->isSubclassOf( Sentient ) && ( victim->health > 0.0f ) ) hit_something = true; if ( ( victim->health > 0 ) || hit_dead ) { if ( victimlist && victim->isSubclassOf( Sentient ) && ( victim->health > 0 ) ) victimlist->AddObject( victim ); // Override damage if we're using the GameplayManager system // Comes from From -- // Actor::MeleeAttack // Player::AdvancedMeleeAttack // GameplayManager *gpm = GameplayManager::getTheGameplayManager(); if ( weapon && gpm->hasObject(weapon->getArchetype()) ) { str attacktype = ""; if ( attacker->isSubclassOf( Player ) ) { Player *player = (Player*)attacker; attacktype = player->getAttackType(); } GameplayFormulaData fd(attacker, 0, weapon, attacktype); switch ( weapon->GetCurMode() ) { case FIRE_MODE1: if ( gpm->hasFormula("DamageMode1") ) damage = gpm->calculate("DamageMode1", fd); break; case FIRE_MODE2: if ( gpm->hasFormula("DamageMode2") ) damage = gpm->calculate("DamageMode2", fd); break; } if ( critical && !victim->deadflag ) { damage *= 2.0f ; str snd = gpm->getStringValue( "Criticals.Use", "wav"); if ( snd.length() ) { int channel = CHAN_BODY; float volume = -1.0f; float mindist = -1.0f; if ( gpm->hasProperty( "Criticals.Use","channel") ) channel = (int)gpm->getFloatValue( "Criticals.Use", "channel"); if ( gpm->hasProperty( "Criticals.Use","volume") ) volume = (int)gpm->getFloatValue( "Criticals.Use", "volume"); if ( gpm->hasProperty( "Criticals.Use","mindist") ) mindist = (int)gpm->getFloatValue( "Criticals.Use", "mindist"); weapon->Sound(snd, channel, volume, mindist); } str effect = gpm->getStringValue("Criticals.Use", "tikifx"); if ( effect.length() ) { Vector pos, pos2, fxpos; weapon->GetActorMuzzlePosition(&pos, NULL, NULL, NULL, "tag_swipe1"); weapon->GetActorMuzzlePosition(&pos2, NULL, NULL, NULL, "tag_swipe2"); fxpos = (pos + pos2) / 2.0f; // Spark is halfway between the two points weapon->SpawnEffect(effect, fxpos, Vector(0.0f,0.0f,0.0f), 2.0f); } } if ( gpm->hasFormula("Knockback") ) knockback = gpm->calculate("Knockback", fd); } Vector painDir; painDir = victim->centroid - pos; painDir.normalize(); victim->Damage( attacker, attacker, damage, pos, painDir, vec_zero, knockback, 0, means_of_death, -1, -1, weapon ); } } } if ( hit_something && attacker && attacker->isSubclassOf( Player ) && ( means_of_death != MOD_FIRE ) && ( means_of_death != MOD_ON_FIRE ) ) { Player *player = (Player *)attacker; player->shotHit(); } return hit_something; } Event EV_Projectile_Speed ( "speed", EV_DEFAULT, "f", "projectileSpeed", "set the speed of the projectile" ); Event EV_Projectile_MinSpeed ( "minspeed", EV_TIKIONLY, "f", "minspeed", "set the minimum speed of the projectile (this is for charge up weapons)" ); Event EV_Projectile_ChargeSpeed ( "chargespeed", EV_TIKIONLY, NULL, NULL, "set the projectile's speed to be determined by the charge time" ); Event EV_Projectile_Damage ( "hitdamage", EV_TIKIONLY, "f", "projectileHitDamage", "set the damage a projectile does when it hits something" ); Event EV_Projectile_Life ( "life", EV_DEFAULT, "f", "projectileLife", "set the life of the projectile" ); Event EV_Projectile_MinLife ( "minlife", EV_TIKIONLY, "f", "minProjectileLife", "set the minimum life of the projectile (this is for charge up weapons)" ); Event EV_Projectile_ChargeLife ( "chargelife", EV_TIKIONLY, NULL, NULL, "set the projectile's life to be determined by the charge time" ); Event EV_Projectile_Knockback ( "knockback", EV_TIKIONLY, "f", "projectileKnockback", "set the knockback of the projectile when it hits something" ); Event EV_Projectile_DLight ( "dlight", EV_TIKIONLY, "ffff", "color_red color_green color_blue intensity", "set the color and intensity of the dynamic light on the projectile" ); Event EV_Projectile_Avelocity ( "avelocity", EV_TIKIONLY, "SFSFSF", "[random|crandom] yaw [random|crandom] pitch [random|crandom] roll", "set the angular velocity of the projectile" ); Event EV_Projectile_MeansOfDeath ( "meansofdeath", EV_TIKIONLY, "s", "meansOfDeath", "set the meansOfDeath of the projectile" ); Event EV_Projectile_BeamCommand ( "beam", EV_TIKIONLY, "sSSSSSS", "command arg1 arg2 arg3 arg4 arg5 arg6", "send a command to the beam of this projectile" ); Event EV_Projectile_UpdateBeam ( "updatebeam", EV_CODEONLY, NULL, NULL, "Update the attached beam" ); Event EV_Projectile_SetQuietExpire ( "setquietexpire", EV_TIKIONLY, NULL, NULL, "If projectile times out without dying, make it not play explosion tiki" ); Event EV_Projectile_BounceFactor ( "bouncefactor", EV_TIKIONLY, "f", "bounceFactor", "set the amount of velocity retained when a projectile bounces" ); Event EV_Projectile_BounceTouch ( "bouncetouch", EV_TIKIONLY, "s", "bouncetype", "Make the projectile bounce when it hits a non-damageable solid" ); Event EV_Projectile_BounceSound ( "bouncesound", EV_DEFAULT, NULL, NULL, "Set the name of the sound that is played when the projectile bounces" ); Event EV_Projectile_Explode ( "explode", EV_DEFAULT, NULL, NULL, "Make the projectile explode" ); Event EV_Projectile_ImpactMarkShader ( "impactmarkshader", EV_TIKIONLY, "s", "shader", "Set the impact mark of the shader" ); Event EV_Projectile_ImpactMarkRadius ( "impactmarkradius", EV_TIKIONLY, "f", "radius", "Set the radius of the impact mark" ); Event EV_Projectile_ImpactMarkOrientation ( "impactmarkorientation", EV_TIKIONLY, "f", "degrees", "Set the orientation of the impact mark" ); Event EV_Projectile_SetExplosionModel ( "explosionmodel", EV_TIKIONLY, "s", "modelname", "Set the modelname of the explosion to be spawned" ); Event EV_Projectile_SetAddVelocity ( "addvelocity", EV_TIKIONLY, "fff", "velocity_x velocity_y velocity_z", "Set a velocity to be added to the projectile when it is created" ); Event EV_Projectile_AddOwnerVelocity ( "addownervelocity", EV_TIKIONLY, "b", "bool", "Set whether or not the owner's velocity is added to the projectile's velocity" ); Event EV_Projectile_HeatSeek ( "heatseek", EV_TIKIONLY, NULL, NULL, "Make the projectile heat seek" ); Event EV_Projectile_Drunk ( "drunk", EV_TIKIONLY, "BF", "drunkFlag angleModifier", "Make the projectile drunk" ); Event EV_Projectile_SetCanHitOwner ( "canhitowner", EV_TIKIONLY, NULL, NULL, "Make the projectile be able to hit its owner" ); Event EV_Projectile_ClearOwner ( "clearowner", EV_CODEONLY, NULL, NULL, "Make the projectile be able to hit its owner now" ); Event EV_Projectile_RemoveWhenStopped ( "removewhenstopped", EV_TIKIONLY, NULL, NULL, "Make the projectile get removed when it stops" ); Event EV_Projectile_SetOwnerControl ( "ownercontrol", EV_TIKIONLY, NULL, NULL, "Make the projectile's angles controlled by the player" ); Event EV_Projectile_SetOwnerControlLaser ( "ownerControlLaser", EV_TIKIONLY, "B", "bool", "Make's the projectile's angles controlled by the player (laser method)" ); Event EV_Projectile_SetControlTurnSpeed ( "controlTurnSpeed", EV_TIKIONLY, "f", "controlTurnSpeed", "Sets the max turn speed of a controlled projectile" ); Event EV_Projectile_StickOnTouch ( "stickontouch", EV_TIKIONLY, "B", "stick_on_touch", "sets whether or not the projectile sticks on contact" ); Event EV_Projectile_TriggerDetonate ( "triggerdetonate", EV_TIKIONLY, "B", "bool", "Make the projectile explode when triggered." ); Event EV_Projectile_AngleThink ( "anglethink", EV_TIKIONLY, NULL, NULL, "Make the projectile update it's angles in flight" ); Event EV_Projectile_ScaleByCharge ( "scaleByCharge", EV_TIKIONLY, "ff", "minScale maxScale", "Makes the projectile size scaled by the charge." ); Event EV_Projectile_ScaleExplosion ( "scaleExplosion", EV_TIKIONLY, NULL, NULL, "Make the explosion scale with the projectile." ); Event EV_Projectile_NotifyActors ( "notifyactors", EV_TIKIONLY, NULL, NULL, "Notify's active actors when close" ); Event EV_Projectile_NotShootable ( "proj_NotShootable", EV_TIKIONLY, NULL, NULL, "Specifies that this projectile is not shootable and can't hit shootable only stuff." ); Event EV_Projectile_HitsProjectiles ( "proj_HitsProjectiles", EV_TIKIONLY, "b", "hitsProjectilesBool", "Specifies whether or not this projectile will hit other projectiles." ); Event EV_Projectile_MinOnGroundTime ( "minOnGroundTime", EV_TIKIONLY, "f", "minOnGroundTime", "Specifies how long the projectile has to stay on the ground until it changes it's animation." ); CLASS_DECLARATION( Entity, Projectile, NULL ) { { &EV_Touch, &Projectile::Touch }, { &EV_Projectile_Speed, &Projectile::SetSpeed }, { &EV_Projectile_MinSpeed, &Projectile::SetMinSpeed }, { &EV_Projectile_ChargeSpeed, &Projectile::SetChargeSpeed }, { &EV_Projectile_Damage, &Projectile::SetDamage }, { &EV_Projectile_Life, &Projectile::SetLife }, { &EV_Projectile_MinLife, &Projectile::SetMinLife }, { &EV_Projectile_ChargeLife, &Projectile::SetChargeLife }, { &EV_Projectile_Knockback, &Projectile::SetKnockback }, { &EV_Projectile_DLight, &Projectile::SetDLight }, { &EV_Projectile_Avelocity, &Projectile::SetAvelocity }, { &EV_Projectile_MeansOfDeath, &Projectile::SetMeansOfDeath }, { &EV_Projectile_SetQuietExpire, &Projectile::SetQuietExpire }, { &EV_Projectile_BounceFactor, &Projectile::SetBounceFactor }, { &EV_Projectile_BounceTouch, &Projectile::SetBounceTouch }, { &EV_Projectile_BounceSound, &Projectile::SetBounceSound }, { &EV_Projectile_BeamCommand, &Projectile::BeamCommand }, { &EV_Projectile_UpdateBeam, &Projectile::UpdateBeam }, { &EV_Projectile_Explode, &Projectile::Explode }, { &EV_Projectile_ImpactMarkShader, &Projectile::SetImpactMarkShader }, { &EV_Projectile_ImpactMarkRadius, &Projectile::SetImpactMarkRadius }, { &EV_Projectile_ImpactMarkOrientation, &Projectile::SetImpactMarkOrientation }, { &EV_Projectile_SetExplosionModel, &Projectile::SetExplosionModel }, { &EV_Projectile_SetAddVelocity, &Projectile::SetAddVelocity }, { &EV_Projectile_AddOwnerVelocity, &Projectile::AddOwnerVelocity }, { &EV_Projectile_HeatSeek, &Projectile::HeatSeek }, { &EV_Projectile_Drunk, &Projectile::Drunk }, { &EV_Projectile_SetCanHitOwner, &Projectile::SetCanHitOwner }, { &EV_Projectile_ClearOwner, &Projectile::ClearOwner }, { &EV_Projectile_RemoveWhenStopped, &Projectile::RemoveWhenStopped }, { &EV_Killed, &Projectile::destroyed }, { &EV_Stop, &Projectile::Stopped }, { &EV_Projectile_SetOwnerControl, &Projectile::SetOwnerControl }, { &EV_Projectile_SetOwnerControlLaser, &Projectile::setOwnerControlLaser }, { &EV_Projectile_SetControlTurnSpeed, &Projectile::setControlTurnSpeed }, { &EV_Projectile_StickOnTouch, &Projectile::StickOnTouch }, { &EV_Projectile_TriggerDetonate, &Projectile::TriggerDetonate }, { &EV_Projectile_AngleThink, &Projectile::AngleThink }, { &EV_Projectile_ScaleByCharge, &Projectile::setScaleByCharge }, { &EV_Projectile_ScaleExplosion, &Projectile::setScaleExplosion }, { &EV_Projectile_NotifyActors, &Projectile::setNotifyActors }, { &EV_Projectile_NotShootable, &Projectile::setNotShootable }, { &EV_Projectile_HitsProjectiles, &Projectile::setHitsProjectiles }, { &EV_Projectile_MinOnGroundTime, &Projectile::setMinOnGroundTime }, { NULL, NULL } }; //-------------------------------------------------------------- // Name: Projectile() // Class: Projectile // // Description: Constructor // // Parameters: None // // Returns: None //-------------------------------------------------------------- Projectile::Projectile() { animate = new Animate( this ); if ( LoadingSavegame ) { // Archive function will setup all necessary data return; } m_beam = NULL; speed = 0; minspeed = 0; bouncefactor = 0; damage = 0; life = 5; knockback = 0; dlight_radius = 0; dlight_color = Vector( 1.0f, 1.0f, 1.0f ); avelocity = Vector( 0.0f, 0.0f, 0.0f ); mins = Vector( -1.0f, -1.0f, -1.0f ); maxs = Vector( 1.0f, 1.0f, 1.0f ); meansofdeath = (meansOfDeath_t )0; projFlags = 0; gravity = 0; impactmarkradius = 10; charge_fraction = 1.0f; target = NULL; addownervelocity = true; drunk = false; can_hit_owner = false; remove_when_stopped = false; stick_on_touch = false; takedamage = DAMAGE_NO; ownercontrol = false; _ownerControlLaser = false; _ownerControlUsed = false; _controlTurnSpeed = 20.0f; firstTimeOwnerControl = true; triggerdetonate = false; drunkAngleModifier = 1.0; _notifyActors = false; _scaleByCharge = false; _scaleExplosion = false; _minScaleFromCharge = 1.0f; _maxScaleFromCharge = 1.0f; // make this shootable but non-solid on the client setContents( CONTENTS_SHOOTABLE_ONLY ); setFullTrace( true ); // // touch triggers by default // flags |= FL_TOUCH_TRIGGERS; startTime = level.time; _notShootable = false; _hitsProjectiles = true; _minOnGroundTime = 0.0f; _onGround = false; _startOnGroundTime = 0.0f; minlife = 0.0f; _heatSeek = false; _damagedSomething = false; } //-------------------------------------------------------------- // Name: ~Projectile() // Class: Projectile // // Description: Destructor // // Parameters: None // // Returns: None //-------------------------------------------------------------- Projectile::~Projectile() { detach(); } float Projectile::ResolveMinimumDistance( Entity *potential_target, float currmin ) { float currdist; float dot; Vector angle; Vector delta; Vector norm; float sine = 0.4f; delta = potential_target->centroid - this->origin; norm = delta; norm.normalize(); // Test if the target is in front of the missile dot = norm * orientation[ 0 ]; if ( dot < 0.0f ) { return currmin; } // Test if we're within the rocket's viewcone (45 degree cone) dot = norm * orientation[ 1 ]; if ( fabs( dot ) > sine ) { return currmin; } dot = norm * orientation[ 2 ]; if ( fabs( dot ) > sine ) { return currmin; } currdist = delta.length(); if ( currdist < currmin ) { currmin = currdist; target = potential_target; } return currmin; } float Projectile::AdjustAngle( float maxadjust, float currangle, float targetangle ) { float dangle; float magangle; dangle = currangle - targetangle; if ( dangle ) { magangle = ( float )fabs( dangle ); while( magangle >= 360.0f ) { magangle -= 360.0f; } if ( magangle < maxadjust ) { currangle = targetangle; } else { if ( magangle > 180.0f ) { maxadjust = -maxadjust; } if ( dangle > 0 ) { maxadjust = -maxadjust; } currangle += maxadjust; } } while( currangle >= 360.0f ) { currangle -= 360.0f; } while( currangle < 0.0f ) { currangle += 360.0f; } return currangle; } void Projectile::Drunk( Event *ev ) { if ( ev->NumArgs() > 0 ) drunk = ev->GetBoolean( 1 ); else drunk = true; if ( ev->NumArgs() > 1 ) drunkAngleModifier = ev->GetFloat( 2 ); if ( drunk ) { turnThinkOn(); } } void Projectile::TriggerDetonate( Event *ev ) { if( ev->NumArgs() > 0 ) triggerdetonate = ev->GetBoolean(1); else triggerdetonate = true; if ( triggerdetonate ) { turnThinkOn(); } } void Projectile::SetOwnerControl( Event *ev ) { ownercontrol = ev->GetBoolean( 1 ); if ( ownercontrol ) { turnThinkOn(); } } void Projectile::setOwnerControlLaser( Event *ev ) { if ( ev->NumArgs() > 0 ) { _ownerControlLaser = ev->GetBoolean( 1 ); } else { _ownerControlLaser = true; } } void Projectile::setControlTurnSpeed( Event *ev ) { _controlTurnSpeed = ev->GetFloat( 1 ); } void Projectile::HeatSeek( Event * ) { float mindist; Entity *ent; trace_t trace; Vector delta; Vector v; int n; int i; _heatSeek = true; if ( ( !target ) || ( target == world ) ) { mindist = 8192.0f; n = SentientList.NumObjects(); for( i = 1; i <= n; i++ ) { ent = SentientList.ObjectAt( i ); if ( ent->entnum == owner ) { continue; } if ( ( ( ent->takedamage != DAMAGE_AIM ) || ( ent->health <= 0.0f ) ) && !( edict->svflags & SVF_MONSTER ) ) { continue; } trace = G_Trace( this->origin, vec_zero, vec_zero, ent->centroid, this, MASK_SHOT, false, "DrunkMissile::HeatSeek" ); if ( ( trace.fraction != 1.0f ) && ( trace.ent != ent->edict ) ) { continue; } mindist = ResolveMinimumDistance( ent, mindist ); } } else { float angspeed; delta = target->centroid - this->origin; v = delta.toAngles(); angspeed = 5.0f; angles.x = AdjustAngle( angspeed, angles.x, v.x ); angles.y = AdjustAngle( angspeed, angles.y, v.y ); angles.z = AdjustAngle( angspeed, angles.z, v.z ); setAngles( angles ); velocity = Vector( orientation[ 0 ] ) * speed; } PostEvent( EV_Projectile_HeatSeek, 0.1f ); //turnThinkOn(); } void Projectile::AngleThink( Event * ) { turnThinkOn(); } void Projectile::NotifyActors() { Actor *act; Vector selfToActor; float distanceToActor; Event *projCloseEvent; //First loop through the awake list for( int i = 1; i <= ActiveList.NumObjects(); i++ ) { act = ActiveList.ObjectAt( i ); selfToActor = act->origin - origin; distanceToActor = selfToActor.length(); if ( distanceToActor < 192 ) { projCloseEvent = new Event(EV_Actor_ProjectileClose); projCloseEvent->AddEntity( G_GetEntity(owner) ); act->ProcessEvent(projCloseEvent); } } } void Projectile::Think( void ) { Vector end; Vector f, r, u; Vector delta; Entity *own; Player *player; if ( _notifyActors ) { NotifyActors(); } if ( drunk ) { angles += Vector( G_CRandom( drunkAngleModifier ), G_CRandom( drunkAngleModifier ), 0.0f ); velocity = Vector( orientation[ 0 ] ) * speed; } if ( !_onGround ) { setAngles( velocity.toAngles() ); } // Determine if the projectile is on the ground (if needed) if ( ( _minOnGroundTime > 0.0f ) && !_onGround ) { trace_t trace = G_Trace( origin, vec_zero, vec_zero, origin - Vector( 0, 0, maxs[2] - mins[2] ) , this, MASK_PROJECTILE, false, "Projectile::Think" ); if ( trace.ent && trace.ent->entity && trace.ent->entity == world ) { // Projectile is on the ground if ( _startOnGroundTime == 0.0f ) { _startOnGroundTime = level.time; } if ( _startOnGroundTime + _minOnGroundTime < level.time ) { _onGround = true; if ( animate && animate->HasAnim( "onground" ) ) { animate->RandomAnimate( "onground" ); } velocity = vec_zero; } } else { _startOnGroundTime = 0.0f; } } if ( this->owner != ENTITYNUM_NONE ) { own = G_GetEntity(owner); if ( own && own->isSubclassOf( Player ) ) { player = (Player *)own; if ( ownercontrol ) { if ( firstTimeOwnerControl && !_ownerControlLaser ) { setAngles( player->GetVAngles() ); prevPlayerAngles = player->GetVAngles(); firstTimeOwnerControl = false; //towcam = new Camera; //towcam->setOrigin( origin ); //towcam->setAngles( player->GetVAngles() ); //towcam->FollowEntity((Entity *)this, 25.0, MASK_SOLID, (Entity *)this); //player->SetCamera(towcam, 0); } else { if ( _ownerControlLaser ) { Vector pos; Vector dir; Vector forward; Weapon *weapon; Vector goalAngles; weapon = player->GetActiveWeapon( WEAPON_DUAL ); if ( weapon && weapon->getControllingProjectile() ) { if ( !_ownerControlUsed ) { _ownerControlUsed = true; if ( animate && animate->HasAnim( "controlled" ) ) { animate->RandomAnimate( "controlled" ); } } weapon->GetMuzzlePosition( &pos, &forward, NULL, NULL ); trace_t trace = G_Trace( pos, vec_zero, vec_zero, pos + 2000 * forward, NULL, MASK_SOLID, false, "Projectile::Think" ); dir = trace.endpos - origin; goalAngles = dir.toAngles(); delta = goalAngles - angles; delta.EulerNormalize(); // Don't let the projectile try to turn all of the way around if ( ( delta[ YAW ] < -135.0f ) || ( delta[ YAW ] > 135.0f ) || ( delta[ PITCH ] < -135.0f ) || ( delta[ PITCH ] > 135.0f ) ) { delta[ YAW ] = 0.0f; delta[ PITCH ] = 0.0f; } delta.EulerNormalize(); if ( delta[ YAW ] < -_controlTurnSpeed ) delta[ YAW ] = -_controlTurnSpeed; if ( delta[ YAW ] > _controlTurnSpeed ) delta[ YAW ] = _controlTurnSpeed; if ( delta[ PITCH ] < -_controlTurnSpeed ) delta[ PITCH ] = -_controlTurnSpeed; if ( delta[ PITCH ] > _controlTurnSpeed ) delta[ PITCH ] = _controlTurnSpeed; setAngles( angles + delta ); } } else { delta = (player->GetVAngles() - prevPlayerAngles); prevPlayerAngles = player->GetVAngles(); if ( player->isButtonDown( BUTTON_ATTACKRIGHT ) ) { setAngles( angles + delta ); } } } AngleVectors( angles, f, r, u ); orientation[0][0] = f.x; orientation[0][1] = f.y; orientation[0][2] = f.z; orientation[1][0] = r.x; orientation[1][1] = r.y; orientation[1][2] = r.z; orientation[2][0] = u.x; orientation[2][1] = u.y; orientation[2][2] = u.z; } if ( triggerdetonate ) { if ( ( player->getHealth() <= 0.0f ) || ( player->deadflag != DEAD_NO ) ) { PostEvent( EV_Remove, 0.0f ); } else if ( player->GetProjDetonate() ) { Event *newEvent = new Event( EV_Player_Weapon ); newEvent->AddString( "dual" ); newEvent->AddString( "nextSwitchTime" ); newEvent->AddFloat( 0.0f ); player->ProcessEvent( newEvent ); if ( level.time > startTime + minlife ) ProcessEvent( EV_Projectile_Explode ); else PostEvent( EV_Projectile_Explode, startTime + minlife - level.time ); } } } } else setAngles( angles ); if ( ownercontrol ) { velocity = Vector( orientation[ 0 ] ) * speed; } } void Projectile::AddOwnerVelocity( Event *ev ) { addownervelocity = ev->GetBoolean( 1 ); } void Projectile::SetAddVelocity( Event *ev ) { addvelocity.x = ev->GetFloat( 1 ); addvelocity.y = ev->GetFloat( 2 ); addvelocity.z = ev->GetFloat( 3 ); } void Projectile::SetExplosionModel( Event *ev ) { explosionmodel = ev->GetString( 1 ); CacheResource( explosionmodel, this ); } void Projectile::SetImpactMarkShader( Event *ev ) { impactmarkshader = ev->GetString( 1 ); } void Projectile::SetImpactMarkRadius( Event *ev ) { impactmarkradius = ev->GetFloat( 1 ); } void Projectile::SetImpactMarkOrientation( Event *ev ) { impactmarkorientation = ev->GetString( 1 ); } void Projectile::Explode( Event *ev ) { Entity *owner; Entity *ignoreEnt=NULL; if ( ev->NumArgs() == 1 ) ignoreEnt = ev->GetEntity( 1 ); // Get the owner of this projectile owner = G_GetEntity( this->owner ); // If the owner's not here, make the world the owner if ( !owner ) owner = world; takedamage = DAMAGE_NO; if (! ((level.time - startTime >= life-FRAMETIME) && (projFlags & P_QUIET_EXPIRE)) ) // if projectile didn't expire by timeout { // or isn't set to quiet_expire (FRAMETIME required due to float roundoff errors) // Spawn an explosion model if ( explosionmodel.length() ) { // Move the projectile back off the surface a bit so we can see // explosion effects. Vector dir, v; float scale; v = velocity; v.normalize(); dir = v; //v = origin - ( v * 36 ); if ( _scaleExplosion ) scale = edict->s.scale; else scale = 1.0f; //ExplosionAttack( v, owner, explosionModleToUse, dir, ignoreEnt, scale ); ExplosionAttack( centroid, owner, explosionmodel, dir, ignoreEnt, scale ); } } CancelEventsOfType( EV_Projectile_UpdateBeam ); // Kill the beam if ( m_beam ) { m_beam->ProcessEvent( EV_Remove ); m_beam = NULL; } // Remove the projectile PostEvent( EV_Remove, 0.0f ); } //---------------------------------------------------------------- // Name: SetQuietExpire // Class: Projectile // // Description: sets projectile to not spawn explosionmodel if it times out // // Parameters: None // // Returns: void //---------------------------------------------------------------- void Projectile::SetQuietExpire( Event * ) { projFlags |= P_QUIET_EXPIRE; // set projectile to not spawn explosionmodel if it times out } void Projectile::SetBounceFactor( Event *ev ) { bouncefactor = ev->GetFloat(1); } void Projectile::SetBounceTouch( Event *ev ) { // if arg1 is "all", make projectile bounce off *everything* if (ev->NumArgs() > 0) { str t = ev->GetString( 1 ); // add more tests here if we want them (for p_bounce_actor, p_bounce_player, etc) if (!t.icmp("all")) { projFlags |= P_BOUNCE_ALL; } } // "bouncetouch all" should fall through and set P_BOUNCE_TOUCH and movetype anyway projFlags |= P_BOUNCE_TOUCH; setMoveType( MOVETYPE_BOUNCE ); } void Projectile::BeamCommand( Event *ev ) { int i; if ( !m_beam ) { m_beam = new FuncBeam; m_beam->setOrigin( this->origin ); m_beam->Ghost( NULL ); } Event *beamev = new Event( ev->GetToken( 1 ) ); for( i=2; i<=ev->NumArgs(); i++ ) { beamev->AddToken( ev->GetToken( i ) ); } m_beam->ProcessEvent( beamev ); PostEvent( EV_Projectile_UpdateBeam, level.frametime ); } void Projectile::UpdateBeam( Event * ) { if ( m_beam ) { m_beam->setOrigin( this->origin ); PostEvent( EV_Projectile_UpdateBeam, level.frametime ); } } void Projectile::SetBounceSound( Event *ev ) { bouncesound = ev->GetString( 1 ); } void Projectile::SetChargeLife( Event * ) { projFlags |= P_CHARGE_LIFE; } void Projectile::SetMinLife( Event *ev ) { minlife = ev->GetFloat( 1 ); projFlags |= P_CHARGE_LIFE; } void Projectile::SetLife( Event *ev ) { life = ev->GetFloat( 1 ); } void Projectile::SetSpeed( Event *ev ) { speed = ev->GetFloat( 1 ); } void Projectile::SetMinSpeed( Event *ev ) { minspeed = ev->GetFloat( 1 ); projFlags |= P_CHARGE_SPEED; } void Projectile::SetChargeSpeed( Event * ) { projFlags |= P_CHARGE_SPEED; } void Projectile::SetAvelocity( Event *ev ) { int i=1; int j=0; str vel; if ( ev->NumArgs() < 3 ) { warning( "ClientGameCommandManager::SetAngularVelocity", "Expecting at least 3 args for command randvel" ); } while ( j < 3 ) { vel = ev->GetString( i++ ); if ( vel == "crandom" ) { avelocity[j++] = ev->GetFloat( i++ ) * crandom(); } else if ( vel == "random" ) { avelocity[j++] = ev->GetFloat( i++ ) * random(); } else { avelocity[j++] = (float)atof( vel.c_str() ); } } } void Projectile::SetDamage( Event *ev ) { damage = ev->GetFloat( 1 ); } void Projectile::SetKnockback( Event *ev ) { knockback = ev->GetFloat( 1 ); } void Projectile::SetDLight( Event *ev ) { dlight_color[0] = ev->GetFloat( 1 ); dlight_color[1] = ev->GetFloat( 2 ); dlight_color[2] = ev->GetFloat( 3 ); dlight_radius = ev->GetFloat( 4 ); } void Projectile::SetMeansOfDeath( Event *ev ) { meansofdeath = (meansOfDeath_t )MOD_NameToNum( ev->GetString( 1 ) ); } void Projectile::DoDecal( void ) { if ( impactmarkshader.length() ) { Decal *decal = new Decal; decal->setShader( impactmarkshader ); decal->setOrigin( level.impact_trace.endpos ); decal->setDirection( level.impact_trace.plane.normal ); decal->setOrientation( impactmarkorientation ); decal->setRadius( impactmarkradius ); } } void Projectile::Touch( Event *ev ) { // Other is what got hit Entity *other = ev->GetEntity( 1 ); assert( other ); // *really* bouncy projectile if ( projFlags & P_BOUNCE_ALL ) { if (bouncefactor) velocity *= bouncefactor; return; } // Don't touch teleporters if ( other->isSubclassOf( Teleporter ) ) { return; } // Can't hit yourself with a projectile if ( other->entnum == edict->ownerNum ) { return; } // Remove it if we hit the sky if ( HitSky() ) { PostEvent( EV_Remove, 0.0f ); return; } if ( can_hit_owner ) { ProcessEvent( EV_Projectile_ClearOwner ); } // Bouncy Projectile if ( ( projFlags & P_BOUNCE_TOUCH ) && !other->takedamage ) //( !other->takedamage || other->health <= 0 ) ) { Actor *act = (Actor *)other; if ( other->isSubclassOf(Actor) ) { if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && (act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO )) { // Remove the owner so it can be hit after the bounce //this->owner = ENTITYNUM_NONE; //edict->ownerNum = ENTITYNUM_NONE; edict->ownerNum = other->entnum; if ( bouncesound.length() ) Sound( bouncesound, CHAN_BODY ); //Bounce The Projectile velocity *= act->bounce_off_velocity; // Tell the actor something just bounced off of it Event *event = new Event( EV_Actor_BounceOff ); event->AddVector( origin ); act->PostEvent( event, 0.0f ); return; } } if (bouncefactor) velocity *= bouncefactor; // Play a bouncy sound if ( !_onGround && bouncesound.length() ) { Sound( bouncesound, CHAN_BODY ); } BroadcastSound(); return; } if ( stick_on_touch && other->projectilesCanStickToMe ) { turnThinkOff(); Vector forward; angles.AngleVectors(&forward); forward.normalize(); Vector endOfTrace = ( forward * 1000.0f ) + origin; if ( other->entnum == ENTITYNUM_WORLD ) { if ( animate && animate->HasAnim( "stuck" ) ) { animate->RandomAnimate( "stuck" ); } trace_t trace = G_Trace( origin, vec_zero, vec_zero, endOfTrace, NULL, MASK_PROJECTILE, false, "ProjectileTouch" ); if( trace.ent && trace.ent->entity && trace.ent->entity == world ) { origin.x = trace.endpos[0]; origin.y = trace.endpos[1]; origin.z = trace.endpos[2]; setOrigin(origin); } setMoveType( MOVETYPE_NONE ); CancelEventsOfType( EV_Projectile_AngleThink ); velocity = Vector(0, 0, 0); return; } else { if ( animate && animate->HasAnim( "stuck_body" ) ) { animate->RandomAnimate( "stuck_body" ); } const trace_t trace = G_FullTrace( origin, vec_zero, vec_zero, endOfTrace, this, MASK_PROJECTILE, false, "ProjectileTouch" ); if( trace.ent && trace.ent->entity ) { const Vector targetDimensions( Vector( trace.ent->maxs ) - Vector( trace.ent->mins ) ); const Vector translation( ( G_Random( 0.24f ) - 0.12f ) * targetDimensions.x, // Lots of magic numbers that generally makes the ( G_Random( 0.24f ) - 0.12f ) * targetDimensions.y, // projectile stick in the target's torso ( G_Random( 0.12f ) + 0.12f ) * targetDimensions.z ); const float yawOffset = angles.yaw() - trace.ent->entity->angles.yaw(); const float pitchOffset = angles.pitch() - trace.ent->entity->angles.pitch(); const Vector rotation( pitchOffset, yawOffset, 0 ); if ( !attach( trace.ent->entity->entnum, -1, false, translation, rotation ) ) { PostEvent( EV_Remove, 0.0f ); } } setMoveType( MOVETYPE_NONE ); CancelEventsOfType( EV_Projectile_AngleThink ); velocity = Vector(0, 0, 0); } } else { // See if we should bounce off object if ( other->isSubclassOf( Actor ) ) { Actor *act = (Actor *)other; if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && (act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO )) { // Remove the owner so it can be hit after the bounce //this->owner = ENTITYNUM_NONE; //edict->ownerNum = ENTITYNUM_NONE; edict->ownerNum = other->entnum; if ( bouncesound.length() ) Sound( bouncesound, CHAN_BODY ); //Bounce The Projectile velocity *= act->bounce_off_velocity; // Tell the actor something just bounced off of it Event *event = new Event( EV_Actor_BounceOff ); event->AddVector( origin ); act->PostEvent( event, 0.0f ); return; } } // Remove the projectile PostEvent( EV_Remove, 0.0f ); // Call the explosion event Event *explEv; explEv = new Event( EV_Projectile_Explode ); explEv->AddEntity( other ); ProcessEvent( explEv ); } // Make the projectile not solid setSolidType( SOLID_NOT ); setMoveType( MOVETYPE_NONE ); //hideModel(); // Do a decal DoDecal(); // Get the owner of this projectile Entity *myOwner = G_GetEntity( owner ); // If the owner's not here, make the world the owner if ( !myOwner ) myOwner = world; str realname = GetRandomAlias( "impact_world" ); if ( other->isSubclassOf( Sentient ) ) realname = GetRandomAlias( "impact_flesh" ); if ( realname.length() > 1 ) Sound( realname, CHAN_VOICE ); // // Override damage if we're using the GameplayManager system // GameplayManager *gpm = GameplayManager::getTheGameplayManager(); if ( gpm->hasObject(getArchetype()) ) { str attacktype = ""; if ( myOwner->isSubclassOf( Player ) ) { Player *player = (Player*)myOwner; attacktype = player->getAttackType(); } GameplayFormulaData fd(myOwner, 0, this, attacktype); if ( gpm->hasFormula("OffensiveDamage") ) damage = GameplayManager::getTheGameplayManager()->calculate("OffensiveDamage", fd); if ( gpm->hasFormula("Knockback") ) knockback = GameplayManager::getTheGameplayManager()->calculate("Knockback", fd); } // Damage the thing that got hit if (other->takedamage) { if ( myOwner && myOwner->isSubclassOf( Player ) ) { Player *player = (Player *)myOwner; damage = player->getDamageDone( damage, meansofdeath, false ); } other->Damage( this, myOwner, damage, origin, velocity, level.impact_trace.plane.normal, knockback, 0, meansofdeath, level.impact_trace.intersect.surface, level.impact_trace.intersect.bone ); } BroadcastSound(); } void Projectile::SetCanHitOwner( Event * ) { can_hit_owner = true; } void Projectile::ClearOwner( Event * ) { //this->owner = ENTITYNUM_NONE; edict->ownerNum = ENTITYNUM_NONE; } void Projectile::RemoveWhenStopped( Event * ) { remove_when_stopped = true; } void Projectile::StickOnTouch( Event *ev ) { if(ev->NumArgs() > 0 ) stick_on_touch = ev->GetBoolean(1); else stick_on_touch = true; } void Projectile::Stopped( Event * ) { if ( remove_when_stopped ) PostEvent( EV_Remove, 0.0f ); } void Projectile::setScaleByCharge( Event *ev ) { _scaleByCharge = true; _minScaleFromCharge = ev->GetFloat( 1 ); _maxScaleFromCharge = ev->GetFloat( 2 ); } void Projectile::setScaleExplosion( Event * ) { _scaleExplosion = true; } void Projectile::setNotifyActors( Event *ev ) { _notifyActors = ev->GetBoolean( 1 ); if ( _notifyActors ) { turnThinkOn(); } } void Projectile::setNotShootable( Event * ) { _notShootable = true; } void Projectile::setHitsProjectiles( Event *ev ) { _hitsProjectiles = ev->GetBoolean( 1 ); } Entity *Projectile::getOwner( void ) { return G_GetEntity( owner ); } void Projectile::setMinOnGroundTime( Event *ev ) { _minOnGroundTime = ev->GetFloat( 1 ); turnThinkOn(); } void Projectile::didDamage( void ) { if ( !_damagedSomething ) { Entity *owner = getOwner(); _damagedSomething = true; if ( owner && owner->isSubclassOf( Player ) ) { Player *player = (Player *)owner; player->shotHit(); } } } void Projectile::destroyed( Event *ev ) { Entity *shooter; shooter = ev->GetEntity( 1 ); if ( shooter ) { owner = shooter->entnum; edict->ownerNum = shooter->entnum; } ProcessEvent( EV_Projectile_Explode ); } Event EV_Explosion_Radius ( "radius", EV_DEFAULT, "f", "projectileRadius", "set the radius for the explosion" ); Event EV_Explosion_ConstantDamage ( "constantdamage", EV_TIKIONLY, NULL, NULL, "Makes the explosion do constant damage over the radius" ); Event EV_Explosion_DamageEveryFrame ( "damageeveryframe", EV_TIKIONLY, NULL, NULL, "Makes the explosion damage every frame" ); Event EV_Explosion_DamageAgain ( "damageagain", EV_CODEONLY, NULL, NULL, "This event is generated each frame if explosion is set to damage each frame" ); Event EV_Explosion_Flash ( "flash", EV_TIKIONLY, "fffffff", "mintime time color_red color_green color_blue minradius radius", "Flash player screens" ); Event EV_Explosion_RadiusDamage ( "radiusdamage", EV_TIKIONLY, "f", "radiusDamage", "set the radius damage an explosion does" ); CLASS_DECLARATION( Projectile, Explosion, NULL ) { { &EV_Explosion_Radius, &Explosion::SetRadius }, { &EV_Explosion_ConstantDamage, &Explosion::SetConstantDamage }, { &EV_Explosion_DamageEveryFrame, &Explosion::SetDamageEveryFrame }, { &EV_Explosion_DamageAgain, &Explosion::DamageAgain }, { &EV_Explosion_Flash, &Explosion::SetFlash }, { &EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage }, { &EV_Explosion_RadiusDamage, &Explosion::SetRadiusDamage }, { NULL, NULL } }; Explosion::Explosion() { if ( LoadingSavegame ) { // Archive function will setup all necessary data return; } radius = 0; radius_damage = 0.0f; constant_damage = false; damage_every_frame = false; flash_mintime = 0; flash_time = 0; flash_type = 0; mins = Vector( -1.0f, -1.0f, -1.0f ); maxs = Vector( 1.0f, 1.0f, 1.0f ); } void Explosion::SetFlash( Event *ev ) { flash_mintime = ev->GetFloat( 1 ); flash_time = ev->GetFloat( 2 ); flash_r = ev->GetFloat( 3 ); flash_g = ev->GetFloat( 4 ); flash_b = ev->GetFloat( 5 ); flash_a = ev->GetFloat( 6 ); flash_minradius = ev->GetFloat( 7 ); flash_radius = ev->GetFloat( 8 ); flash_type = 0; if ( ev->NumArgs() > 8 ) { str t = ev->GetString( 9 ); if ( !t.icmp( "addblend" ) ) flash_type = 1; else if ( !t.icmp( "alphablend" ) ) flash_type = 0; } } void Explosion::SetRadius( Event *ev ) { radius = ev->GetFloat( 1 ); } void Explosion::SetRadiusDamage( Event *ev ) { radius_damage = ev->GetFloat( 1 ); } void Explosion::SetConstantDamage( Event * ) { constant_damage = true; } void Explosion::SetDamageEveryFrame( Event * ) { damage_every_frame = true; } void Explosion::DamageAgain( Event * ) { Entity *owner_ent; owner_ent = G_GetEntity( owner ); RadiusDamage( this, owner_ent, radius_damage, NULL, meansofdeath, radius, knockback, constant_damage ); PostEvent( EV_Explosion_DamageAgain, FRAMETIME ); } Projectile *ProjectileAttack( const Vector &start, Vector &dir, Entity *owner, const str &projectileModel, float fraction, float real_speed ) { Event *ev; Projectile *proj=NULL; float newspeed,newlife; SpawnArgs args; Entity *obj; float dot=0; if ( !projectileModel.length() ) { gi.WDPrintf( "ProjectileAttack : No model specified for ProjectileAttack" ); return NULL; } args.setArg( "model", projectileModel ); obj = args.Spawn(); if ( !obj ) { gi.WDPrintf( "projectile model '%s' not found\n", projectileModel.c_str() ); return NULL; } if ( obj->isSubclassOf( Projectile ) ) proj = ( Projectile * )obj; else gi.WDPrintf( "%s is not of class projectile\n", projectileModel.c_str() ); if ( !proj ) return NULL; if ( owner && owner->isSubclassOf( Player ) ) { Player *player = (Player *)owner; player->shotFired(); } // Create a new projectile entity and set it off proj->setModel( projectileModel ); proj->setMoveType( MOVETYPE_BOUNCE ); proj->CancelEventsOfType( EV_ProcessInitCommands ); proj->ProcessInitCommands( proj->edict->s.modelindex ); proj->owner = owner->entnum; proj->edict->ownerNum = owner->entnum; proj->angles = dir.toAngles(); proj->charge_fraction = fraction; if ( proj->_scaleByCharge ) { proj->edict->s.scale = proj->_minScaleFromCharge + ( proj->_maxScaleFromCharge - proj->_minScaleFromCharge ) * fraction; proj->damage *= proj->edict->s.scale; } if ( proj->projFlags & P_CHARGE_SPEED ) { newspeed = (proj->speed - proj->minspeed ) * fraction + proj->minspeed; } else { newspeed = proj->speed; } if ( real_speed ) newspeed = real_speed; if ( proj->addownervelocity ) { dot = DotProduct( owner->velocity, dir ); if ( dot < 0.0f ) dot = 0; } Player *player = NULL; Vector newAngles = proj->angles; Vector newVelocity = dir * ( newspeed + dot ); newVelocity += proj->addvelocity; if ( owner->isSubclassOf( Player ) ) { player = static_cast(owner); if ( player->GetTarget() && player->GetTargetEnemyLocked() ) { Vector anglesToTarget; if ( player->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity ) ) { newAngles = anglesToTarget; anglesToTarget.AngleVectors( &newVelocity ); newVelocity *= newspeed; } else { anglesToTarget = Vector::Identity(); } } if ( multiplayerManager.inMultiplayer() ) multiplayerManager.playerFired( player ); } Actor *actor = NULL; if ( owner->isSubclassOf( Actor ) ) { actor = static_cast(owner); if ( actor->combatSubsystem->shouldArcProjectile() ) { Vector anglesToTarget; Entity *enemy = actor->enemyManager->GetCurrentEnemy(); if ( enemy ) { Vector ActorToTarget = actor->centroid - enemy->centroid; float distToTarget = ActorToTarget.length(); if ( distToTarget > actor->combatSubsystem->GetLowArcRange() ) { if ( actor->combatSubsystem->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity , true ) ) { newAngles = anglesToTarget; anglesToTarget.AngleVectors( &newVelocity ); newVelocity *= newspeed; } else { anglesToTarget = Vector::Identity(); } } else { if ( actor->combatSubsystem->GetProjectileLaunchAngles( anglesToTarget, start, newspeed, proj->gravity , false ) ) { newAngles = anglesToTarget; anglesToTarget.AngleVectors( &newVelocity ); newVelocity *= newspeed; } else { anglesToTarget = Vector::Identity(); } } } } } proj->velocity = newVelocity; proj->setAngles( newAngles ); if ( proj->_notShootable ) { proj->setSolidType( SOLID_BBOX ); proj->setContents( 0 ); proj->edict->clipmask = MASK_PROJECTILE_NOTSHOOTABLE; } else { proj->setSolidType( SOLID_BBOX ); if ( proj->_hitsProjectiles ) proj->edict->clipmask = MASK_PROJECTILE; else proj->edict->clipmask = MASK_PROJECTILE_NOTSHOOTABLE; } proj->setSize( proj->mins, proj->maxs ); proj->setOrigin( start ); //proj->origin.copyTo( proj->edict->s.origin2 ); if ( proj->m_beam ) { proj->m_beam->setOrigin( start ); proj->m_beam->origin.copyTo( proj->m_beam->edict->s.origin2 ); } if ( proj->dlight_radius ) { G_SetConstantLight( &proj->edict->s.constantLight, &proj->dlight_color[0], &proj->dlight_color[1], &proj->dlight_color[2], &proj->dlight_radius ); } // Calc the life of the projectile if ( proj->projFlags & P_CHARGE_LIFE ) { newlife = proj->life * fraction; if ( newlife < proj->minlife ) newlife = proj->minlife; } else { newlife = proj->life; } // Remove the projectile after it's life expires ev = new Event( EV_Projectile_Explode ); proj->PostEvent( ev, newlife ); proj->animate->RandomAnimate( "idle", EV_StopAnimating ); // If can hit owner clear the owner of this projectile in a second if ( proj->can_hit_owner ) proj->PostEvent( EV_Projectile_ClearOwner, 1.0f ); // Notify any actor that is in way of projectile Vector end_pos; trace_t trace; Event *event; Entity *victim; end_pos = ( dir * 1000.0f ) + start; trace = G_Trace( start, vec_zero, vec_zero, end_pos, owner, MASK_PROJECTILE, false, "ProjectileAttack" ); if ( trace.entityNum != ENTITYNUM_NONE ) { event = new Event( EV_ActorIncomingProjectile ); event->AddEntity( proj ); victim = G_GetEntity( trace.entityNum ); victim->PostEvent( event, 0.0f ); } if ( owner && proj->triggerdetonate ) { Event *newEvent = new Event( EV_Player_Weapon ); newEvent->AddString( "dual" ); newEvent->AddString( "nextSwitchTime" ); newEvent->AddFloat( newlife ); owner->ProcessEvent( newEvent ); } return proj; } float BulletAttack( const Vector &start, const Vector &dir, const Vector &right, const Vector &up, float range, float damage, float knockback, int dflags, int meansofdeath, const Vector &spread, int count, Entity *owner, Entity *weap ) { Vector end; int i; trace_t trace; Entity *ent; Player *player; float damage_total = 0.0f; float original_value; float new_value; ent = NULL; if ( owner->isSubclassOf( Player ) ) { player = (Player *)owner; meansofdeath = (int)player->changetMeansOfDeath( (meansOfDeath_t)meansofdeath ); damage = player->getDamageDone( damage, meansofdeath, false ); player->shotFired(); if ( multiplayerManager.inMultiplayer() ) multiplayerManager.playerFired( player ); } for ( i=0; isetShader( "bloodsplat.spr" ); decal->setOrigin( Vector( trace.endpos ) + ( Vector( trace.plane.normal ) * 0.2 ) ); decal->setDirection( trace.plane.normal ); decal->setOrientation( "random" ); decal->setRadius( 8 );*/ if ( trace.ent ) ent = trace.ent->entity; /*if ( trace.ent ) { ent = trace.ent->entity; //Let Entity know a hitscan hit has been registered if ( ent->isSubclassOf( Actor ) ) { Actor *act = (Actor *)ent; act->SetActorFlag( "incominghitscan" , true ); } // See if bullet should bounce off of entity if ( ent->isSubclassOf( Actor ) ) { Actor *act = (Actor *)ent; Vector real_dir; Vector bounce_start; Vector angles; Vector left; Vector up; if ( act->GetActorFlag( ACTOR_FLAG_BOUNCE_OFF ) && ( act->Immune( meansofdeath ) || act->takedamage == DAMAGE_NO ) ) { // Play sound if ( owner ) owner->Sound( "snd_ricochet", 0 ); real_dir = trace.endpos - start; real_dir = real_dir * -1.0f; angles = real_dir.toAngles(); angles.AngleVectors( NULL, &left, &up ); real_dir.normalize(); real_dir *= range; real_dir += ( left * G_CRandom() * 100.0f ) + ( up * G_CRandom() * 100.0f ); bounce_start = trace.endpos; trace = G_Trace( bounce_start, vec_zero, vec_zero, bounce_start + real_dir, NULL, MASK_SHOT, false, "BulletAttack2" ); // Add tracer //No tracer this time around... Too many beam weapons //if ( G_Random( 1.0f ) < .2f ) //{ // FuncBeam *beam = CreateBeam( NULL, "tracer", bounce_start, trace.endpos, 1, 1.0f, .1f, 0.f ); // beam->PostEvent( EV_Remove, 1.0f ); //} if ( trace.ent ) { ent = trace.ent->entity; if ( ent == world ) { // Add bullet hole Decal *decal = new Decal; decal->setShader( "bullethole" ); decal->setOrigin( trace.endpos ); decal->setDirection( trace.plane.normal ); decal->setOrientation( "random" ); decal->setRadius( 2.0f ); // Add smoke Entity *smoke = new Entity( ENTITY_CREATE_FLAG_ANIMATE ); smoke->setModel( "fx_bulletsmoke.tik" ); smoke->setOrigin( trace.endpos ); smoke->animate->RandomAnimate( "idle" ); smoke->PostEvent( EV_Remove, 2.0f ); } } else { ent = NULL; } } } } else { ent = NULL; } */ if ( ent && ent->takedamage ) { // Override damage if we're using the GameplayManager system // Comes from From -- // Weapon::Shoot // /* GameplayManager *gpm = GameplayManager::getTheGameplayManager(); float bulletDamage; if ( weap && gpm->hasObject(weap->getArchetype()) ) { str attacktype = ""; if ( owner->isSubclassOf( Player ) ) { Player *player = (Player*)owner; attacktype = player->getAttackType(); } GameplayFormulaData fd(owner, 0, weap, attacktype); Weapon *weapon = (Weapon*)weap; switch ( weapon->GetCurMode() ) { case FIRE_MODE1: bulletDamage = gpm->getFloatValue(weapon->getArchetype() + ".Mode1", "bulletdamage"); if ( gpm->hasFormula("DamageMode1") ) damage = gpm->calculate("DamageMode1", fd, bulletDamage); break; case FIRE_MODE2: bulletDamage = gpm->getFloatValue(weapon->getArchetype() + ".Mode2", "bulletdamage"); if ( gpm->hasFormula("DamageMode2") ) damage = gpm->calculate("DamageMode2", fd, bulletDamage); break; } if ( gpm->hasFormula("Knockback") ) knockback = gpm->calculate("Knockback", fd); } */ original_value = ent->health; ent->Damage( NULL, owner, damage, trace.endpos, dir, trace.plane.normal, knockback, dflags, meansofdeath, trace.intersect.surface, trace.intersect.bone, weap); new_value = ent->health; damage_total += original_value - new_value; } else { Weapon *theWeapon; theWeapon = (Weapon*)weap; if ( theWeapon && theWeapon->shouldPlayMissSound() ) { theWeapon->SpawnSound( "snd_ricochet" , trace.endpos , -1 , 1.0 ); } } // Draw a debug trace line to show bullet fire if ( g_showbullettrace->integer ) G_DebugLine( start, end, 1.0f, 1.0f, 1.0f, 1.0f ); } if ( damage_total > 0.0f ) { if ( owner && owner->isSubclassOf( Player ) ) { Player *player = (Player *)owner; player->shotHit(); } return damage_total; } else { return 0; } } void ExplosionAttack( const Vector &pos, Entity *owner, const str &explosionModel, Vector dir, Entity *ignore, float scale ) { Explosion *explosion; Event *ev; float damageMult = 1.0; float damage; int meansofdeath; float damageDone; if ( !owner ) owner = world; if ( explosionModel.length() ) { explosion = new Explosion; // Create a new explosion entity and set it off explosion->setModel( explosionModel ); explosion->mins = Vector( -1.0f, -1.0f, -1.0f ); explosion->maxs = Vector( 1.0f, 1.0f, 1.0f ); explosion->setSolidType( SOLID_NOT ); explosion->CancelEventsOfType( EV_ProcessInitCommands ); // Process the INIT commands right away explosion->ProcessInitCommands( explosion->edict->s.modelindex ); explosion->owner = owner->entnum; explosion->edict->ownerNum = owner->entnum; if ( explosion->can_hit_owner ) explosion->PostEvent( EV_Projectile_ClearOwner, 0.1f ); explosion->angles = dir.toAngles(); explosion->velocity = dir * explosion->speed; explosion->edict->s.scale = scale; explosion->setAngles( explosion->angles ); //explosion->setMoveType( MOVETYPE_FLYMISSILE ); explosion->setMoveType( MOVETYPE_NONE ); explosion->edict->clipmask = MASK_PROJECTILE; explosion->setSize( explosion->mins, explosion->maxs ); explosion->setOrigin( pos ); explosion->origin.copyTo( explosion->edict->s.origin2 ); if ( explosion->dlight_radius ) { G_SetConstantLight( &explosion->edict->s.constantLight, &explosion->dlight_color[0], &explosion->dlight_color[1], &explosion->dlight_color[2], &explosion->dlight_radius ); } explosion->BroadcastSound(); explosion->animate->RandomAnimate( "idle", EV_StopAnimating ); damage = explosion->radius_damage * scale * damageMult; meansofdeath = explosion->meansofdeath; if ( owner->isSubclassOf( Player ) ) { Player *player = (Player *)owner; meansofdeath = (int)player->changetMeansOfDeath( (meansOfDeath_t)meansofdeath ); damage = player->getDamageDone( damage, explosion->meansofdeath, false ); } damageDone = RadiusDamage( explosion, owner, damage, ignore, meansofdeath, explosion->radius * scale, explosion->knockback, explosion->constant_damage ); if ( damageDone && owner && owner->isSubclassOf( Player ) ) { Player *player = (Player *)owner; player->shotHit(); } if ( explosion->flash_radius ) { FlashPlayers( explosion->origin, explosion->flash_r, explosion->flash_g, explosion->flash_b, explosion->flash_a, explosion->flash_minradius * scale, explosion->flash_radius * scale, explosion->flash_mintime, explosion->flash_time, explosion->flash_type ); } if ( explosion->damage_every_frame ) { explosion->PostEvent( EV_Explosion_DamageAgain, FRAMETIME ); } // Remove explosion after the life has expired if ( explosion->life ) { if ( explosion->explosionmodel.length() > 0 ) { ev = new Event( EV_Projectile_Explode ); explosion->PostEvent( ev, explosion->life ); } else { ev = new Event( EV_Remove ); explosion->PostEvent( ev, explosion->life ); } } } } void StunAttack( const Vector &pos, Entity *attacker, Entity *inflictor, float radius, float time, Entity *ignore ) { Entity *ent; Q_UNUSED(pos); ent = findradius( NULL, inflictor->origin, radius ); while( ent ) { if ( ( ent != ignore ) && ( ( ent->takedamage ) || ent->isSubclassOf( Actor ) ) ) { if ( inflictor->CanDamage( ent, attacker ) ) { // Fixme : Add in knockback ent->Stun( time ); } } ent = findradius( ent, inflictor->origin, radius ); } } float RadiusDamage( Entity *inflictor, Entity *attacker, float damage, Entity *ignore, int mod, float radius, float knockback, qboolean constant_damage) { float points; Entity *ent; Vector org; Vector dir; float dist; float rad; float totalDamage; if ( radius ) rad = radius; else rad = damage + 60.0f; totalDamage = 0.0f; ent = findradius( NULL, inflictor->origin, rad ); while( ent ) { if ( ( ent != ignore ) && ( ent->takedamage ) && ( ent->edict->solid != SOLID_NOT ) ) { org = ent->centroid; dir = org - inflictor->origin; dist = dir.length(); if ( dist > rad ) { ent = findradius( ent, inflictor->origin, rad ); continue; } if ( constant_damage ) { points = damage; } else { points = damage - ( damage * ( dist / rad ) ); knockback -= knockback * ( dist / rad ); if ( points < 0.0f ) points = 0.0f; if ( knockback < 0.0f ) knockback = 0.0f; } if ( ent == attacker ) { points *= 0.5f; } if ( points > 0.0f ) { // Add this in for deathmatch maybe // Only players can use cover //if ( !ent->isSubclassOf( Player ) || inflictor->CanDamage( ent, attacker ) ) if ( inflictor->CanDamage( ent, attacker ) ) { org = ent->centroid; dir = org - inflictor->origin; ent->Damage( inflictor, attacker, points, org, dir, vec_zero, knockback, DAMAGE_RADIUS, mod ); totalDamage += points; } } } ent = findradius( ent, inflictor->origin, rad ); } return totalDamage; } void FlashPlayers( const Vector &org, float r, float g, float b, float a, float minradius, float radius, float mintime, float time, int type ) { trace_t trace; Vector delta; float length; Player *player; gentity_t *ed; int i; Entity *ent; float newa = 1; Vector dirToExplosion; Vector playerViewDir; Vector playerViewAngles; float dot; Q_UNUSED(type); for( i = 0; i < game.maxclients; i++ ) { ed = &g_entities[ i ]; if ( !ed->inuse || !ed->entity ) continue; ent = ed->entity; if ( !ent->isSubclassOf( Player ) ) continue; player = ( Player * )ent; if ( !player->WithinDistance( org, radius ) ) continue; trace = G_Trace( org, vec_zero, vec_zero, player->origin, player, MASK_OPAQUE, false, "FlashPlayers" ); if ( trace.fraction != 1.0f ) continue; delta = org - trace.endpos; length = delta.length(); dirToExplosion = org - player->origin; playerViewAngles = player->GetVAngles(); playerViewAngles.AngleVectors( &playerViewDir ); dot = dirToExplosion * playerViewDir; if ( dot < 0.0f ) continue; // If alpha is specified, then modify it by the amount of distance away from the flash the player is if ( a != -1.0f ) { if ( length < minradius ) newa = a; else if ( length < radius ) newa = a * ( 1.0f - ( length - minradius ) / ( radius - minradius ) ); else newa = 0.0f; } /* if ( g_gametype->integer == GT_SINGLE_PLAYER ) // Single player uses the global level variables for flashes { level.m_fade_alpha = newa; level.m_fade_color[0] = r; level.m_fade_color[1] = g; level.m_fade_color[2] = b; level.m_fade_time = time; level.m_fade_time_start = time; if ( type == 1 ) level.m_fade_style = additive; else level.m_fade_style = alphablend; } else { player->AddBlend( r,g,b,newa ); } */ player->setFlash( Vector( r, g, b ), newa, mintime, time ); } }