bool hhCenturion::AttackMelee( const char *meleeDefName ) { const idDict *meleeDef; idActor *enemyEnt = enemy.GetEntity(); const char *p; const idSoundShader *shader; meleeDef = gameLocal.FindEntityDefDict( meleeDefName, false ); if ( !meleeDef ) { gameLocal.Error( "Unknown melee '%s'", meleeDefName ); } if ( !enemyEnt ) { p = meleeDef->GetString( "snd_miss" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } return false; } // make sure the trace can actually hit the enemy if ( !TestMelee() ) { // missed p = meleeDef->GetString( "snd_miss" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } return false; } // // do the damage // p = meleeDef->GetString( "snd_hit" ); if ( p && *p ) { shader = declManager->FindSound( p ); StartSoundShader( shader, SND_CHANNEL_DAMAGE, 0, false, NULL ); } idVec3 kickDir; meleeDef->GetVector( "kickDir", "0 0 0", kickDir ); idVec3 globalKickDir; globalKickDir = ( viewAxis * physicsObj.GetGravityAxis() ) * kickDir; enemyEnt->Damage( this, this, globalKickDir, meleeDefName, 1.0f, INVALID_JOINT ); if ( enemyEnt->IsActiveAF() && !enemyEnt->IsType( idPlayer::Type ) ) { PostEventMS( &AI_BackhandImpulse, 0, enemyEnt ); } lastAttackTime = gameLocal.time; return true; }
END_CLASS /* ================ idTarget_EndLevel::Event_Activate ================ */ void idTarget_EndLevel::Event_Activate( idEntity *activator ) { if ( thinkFlags & TH_THINK ) { BecomeInactive( TH_THINK ); } else { // always allow during cinematics cinematic = true; BecomeActive( TH_THINK ); } idPlayer* player; player = gameLocal.GetLocalPlayer(); player->SetInfluenceLevel( 2 ); player->endLevelTime = gameLocal.time; // sikk - End Level time player->doEndLevel = true; // sikk - End Level Flag if ( !player->scoreBoardOpen ) { player->ToggleScoreboard(); } StartSoundShader( declManager->FindSound( "music_endLevel" ), SND_CHANNEL_ANY, 0, false, NULL ); }
/* ================ idSound::DoSound ================ */ void idSound::DoSound( bool play ) { if ( play ) { StartSoundShader( refSound.shader, SND_CHANNEL_ANY, refSound.parms.soundShaderFlags, true, &playingUntilTime ); playingUntilTime += gameLocal.time; } else { StopSound( SND_CHANNEL_ANY, true ); playingUntilTime = 0; } }
/* ================ idLight::On ================ */ void idLight::On( void ) { currentLevel = levels; // offset the start time of the shader to sync it to the game time renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( gameLocal.time ); if ( ( soundWasPlaying || refSound.waitfortrigger ) && refSound.shader ) { StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); soundWasPlaying = false; } SetLightLevel(); BecomeActive( TH_UPDATEVISUALS ); }
/* ================ idLight::UpdateChangeableSpawnArgs ================ */ void idLight::UpdateChangeableSpawnArgs( const idDict *source ) { idEntity::UpdateChangeableSpawnArgs( source ); if( source ) { source->Print(); } FreeSoundEmitter( true ); gameEdit->ParseSpawnArgsToRefSound( source ? source : &spawnArgs, &refSound ); if( refSound.shader && !refSound.waitfortrigger ) { StartSoundShader( refSound.shader, SND_CHANNEL_ANY, 0, false, NULL ); } gameEdit->ParseSpawnArgsToRenderLight( source ? source : &spawnArgs, &renderLight ); UpdateVisuals(); }
/* ================ rvClientMoveable::Collide ================ */ bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { if (mPlayBounceSoundOnce && mHasBounced) { return false; } if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { float speed; speed = velocity.LengthFast ( ); if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { StartSoundShader ( bounceSoundShader, SND_CHANNEL_BODY, 0 ); bounceSoundTime = BOUNCE_SOUND_DELAY; mHasBounced = true; } } return false; }
void hhCenturion::Event_CenturionLaunch( const idList<idStr>* parmList ) { const idDict* projDef = NULL; const idSoundShader* soundShader = NULL; // parms: joint, projectileDef, sound, fx if( !parmList || parmList->Num() != 5 ) { gameLocal.Warning( "Incorrect paramater number" ); return; } //Rbarrel_A projectile_centurion_autocannon snd_fire fx_muzzleFlash const char* jointName = (*parmList)[ 0 ].c_str(); const char* projectileDefName = (*parmList)[ 1 ].c_str(); const char* soundName = (*parmList)[ 2 ].c_str(); const char* fxName = (*parmList)[ 3 ].c_str(); int autoAim = atoi( (*parmList)[ 4 ].c_str() ); if( AI_CENTURION_ARM_MISSING ) { //skip firing if joint is on the severed arm if( !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointA", "")) || !idStr::Icmp(jointName, spawnArgs.GetString("severed_jointB", "")) ) { return; } } projDef = gameLocal.FindEntityDefDict( projectileDefName, false ); HH_ASSERT( !shootTarget.IsValid() ); // If autoAim is true and we're not blending, and we're facing the enemy, auto-aim if ( ( autoAim == 1 || ( autoAim == 2 && gameLocal.random.RandomInt(100) < 50 ) ) && torsoAnim.animBlendFrames == 0 && FacingEnemy( 5.0f ) ) { AimedAttackMissile( jointName, projDef ); } else { // Otherwise do a normal missile attack Event_AttackMissile( jointName, projDef, 1 ); } if(idStr::Cmpn( soundName, "snd_", 4)) { soundShader = declManager->FindSound( soundName ); if( soundShader->GetState() == DS_DEFAULTED ) { gameLocal.Warning( "Sound '%s' not found", soundName ); } StartSoundShader( soundShader, SND_CHANNEL_WEAPON, 0, false, NULL ); } else { if( !StartSound(soundName, SND_CHANNEL_WEAPON, 0, false, NULL) ) { gameLocal.Warning( "Framecommand 'centurionFire' on entity '%s' could not find sound '%s'", GetName(), soundName ); } } BroadcastFxInfoAlongBone( spawnArgs.GetString(fxName), jointName ); }
END_CLASS /*********************************************************************** Events ***********************************************************************/ /* ===================== idActor::Event_EnableEyeFocus ===================== */ void idActor::PlayFootStepSound() { const char* sound = NULL; const idMaterial* material; if( !GetPhysics()->HasGroundContacts() ) { return; } // start footstep sound based on material type material = GetPhysics()->GetContact( 0 ).material; if( material != NULL ) { sound = spawnArgs.GetString( va( "snd_footstep_%s", gameLocal.sufaceTypeNames[ material->GetSurfaceType() ] ) ); } if( sound != NULL && *sound == '\0' ) { sound = spawnArgs.GetString( "snd_footstep" ); } if( sound != NULL && *sound != '\0' ) { StartSoundShader( declManager->FindSound( sound ), SND_CHANNEL_BODY, 0, false, NULL ); } }
/* ================ rvClientMoveable::Collide ================ */ bool rvClientMoveable::Collide ( const trace_t &collision, const idVec3 &velocity ) { if ( firstBounce ) { // first bounce, play effect const char* bounceEffectName = "fx_firstbounce"; if ( effectSet != 0 ) { bounceEffectName = va( "fx_firstbounce_%i", effectSet ); } const idVec3& origin = physicsObj.GetOrigin(); idMat3 axis = ( idVec3( 0.0f, 0.0f, 1.0f ) ).ToMat3(); gameLocal.PlayEffect( *spawnArgs, colorWhite.ToVec3(), bounceEffectName, NULL, origin, axis ); firstBounce = false; } if ( bounceSoundShader && gameLocal.time > bounceSoundTime ) { float speed; speed = velocity.LengthFast ( ); if ( speed > BOUNCE_SOUND_MIN_VELOCITY ) { StartSoundShader ( bounceSoundShader, SND_ANY, 0 ); bounceSoundTime = BOUNCE_SOUND_DELAY; } } return false; }
bool idMoveable::Collide( const trace_t &collision, const idVec3 &velocity ) { // greebo: Check whether we are colliding with the nearly exact same point again bool sameCollisionAgain = ( lastCollision.fraction != -1 && lastCollision.c.point.Compare( collision.c.point, 0.05f ) ); // greebo: Save the collision info for the next call lastCollision = collision; float v = -( velocity * collision.c.normal ); if( !sameCollisionAgain ) { float bounceSoundMinVelocity = cv_bounce_sound_min_vel.GetFloat(); float bounceSoundMaxVelocity = cv_bounce_sound_max_vel.GetFloat(); if( ( v > bounceSoundMinVelocity ) && ( gameLocal.time > nextSoundTime ) ) { // grayman #3331 - some moveables should not bother with bouncing sounds if( !spawnArgs.GetBool( "no_bounce_sound", "0" ) ) { const idMaterial *material = collision.c.material; idStr sndNameLocal; idStr surfaceName; // "tile", "glass", etc. if( material != NULL ) { surfaceName = g_Global.GetSurfName( material ); // Prepend the snd_bounce_ prefix to check for a surface-specific sound idStr sndNameWithSurface = "snd_bounce_" + surfaceName; if( spawnArgs.FindKey( sndNameWithSurface ) != NULL ) { sndNameLocal = sndNameWithSurface; } else { sndNameLocal = "snd_bounce"; } } const char *sound = spawnArgs.GetString( sndNameLocal ); const idSoundShader *sndShader = declManager->FindSound( sound ); //f = v > BOUNCE_SOUND_MAX_VELOCITY ? 1.0f : idMath::Sqrt( v - BOUNCE_SOUND_MIN_VELOCITY ) * ( 1.0f / idMath::Sqrt( BOUNCE_SOUND_MAX_VELOCITY - BOUNCE_SOUND_MIN_VELOCITY ) ); // angua: modify the volume set in the def instead of setting a fixed value. // At minimum velocity, the volume should be "min_velocity_volume_decrease" lower (in db) than the one specified in the def float f = ( v > bounceSoundMaxVelocity ) ? 0.0f : spawnArgs.GetFloat( "min_velocity_volume_decrease", "0" ) * ( idMath::Sqrt( v - bounceSoundMinVelocity ) * ( 1.0f / idMath::Sqrt( bounceSoundMaxVelocity - bounceSoundMinVelocity ) ) - 1 ); float volume = sndShader->GetParms()->volume + f; if( cv_moveable_collision.GetBool() ) { gameRenderWorld->DrawText( va( "Velocity: %f", v ), ( physicsObj.GetOrigin() + idVec3( 0, 0, 20 ) ), 0.25f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 100 * gameLocal.msec ); gameRenderWorld->DrawText( va( "Volume: %f", volume ), ( physicsObj.GetOrigin() + idVec3( 0, 0, 10 ) ), 0.25f, colorGreen, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1, 100 * gameLocal.msec ); gameRenderWorld->DebugArrow( colorMagenta, collision.c.point, ( collision.c.point + 30 * collision.c.normal ), 4.0f, 1 ); } SetSoundVolume( volume ); // greebo: We don't use StartSound() here, we want to do the sound propagation call manually StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); // grayman #2603 - don't propagate a sound if this is a doused torch dropped by an AI if( !spawnArgs.GetBool( "is_torch", "0" ) ) { idStr sndPropName = GetSoundPropNameForMaterial( surfaceName ); // Propagate a suspicious sound, using the "group" convention (soft, hard, small, med, etc.) PropSoundS( NULL, sndPropName, f, 0 ); // grayman #3355 } SetSoundVolume( 0.0f ); nextSoundTime = gameLocal.time + 500; } } // tels: //DM_LOG(LC_ENTITY, LT_INFO)LOGSTRING("Moveable %s might call script_collide %s because m_collideScriptCounter = %i and v = %f and time (%d) > m_nextCollideScriptTime (%d)\r", // name.c_str(), m_scriptCollide.c_str(), m_collideScriptCounter, v, gameLocal.time, m_nextCollideScriptTime ); if( ( m_collideScriptCounter != 0 ) && ( v > m_minScriptVelocity ) && ( gameLocal.time > m_nextCollideScriptTime ) ) { if( m_collideScriptCounter > 0 ) { // if positive, decrement it, so -1 stays as it is (for 0, we never come here) m_collideScriptCounter--; } // call the script const function_t *pScriptFun = scriptObject.GetFunction( m_scriptCollide.c_str() ); if( pScriptFun == NULL ) { // Local function not found, check in global namespace pScriptFun = gameLocal.program.FindFunction( m_scriptCollide.c_str() ); } if( pScriptFun != NULL ) { DM_LOG( LC_ENTITY, LT_INFO )LOGSTRING( "Moveable %s calling script_collide %s.\r", name.c_str(), m_scriptCollide.c_str() ); idThread *pThread = new idThread( pScriptFun ); pThread->CallFunctionArgs( pScriptFun, true, "e", this ); pThread->DelayedStart( 0 ); } else { // script function not found! DM_LOG( LC_ENTITY, LT_ERROR )LOGSTRING( "Moveable %s could not find script_collide %s.\r", name.c_str(), m_scriptCollide.c_str() ); m_collideScriptCounter = 0; } m_nextCollideScriptTime = gameLocal.time + 300; } } idEntity *ent = gameLocal.entities[collision.c.entityNum]; trace_t newCollision = collision; // grayman #2816 - in case we need to modify collision // grayman #2816 - if we hit the world, skip all the damage work if( ent && ( ent != gameLocal.world ) ) { idActor *entActor = NULL; if( ent->IsType( idActor::Type ) ) { entActor = static_cast<idActor *>( ent ); // the object hit an actor directly } else if( ent->IsType( idAFAttachment::Type ) ) { newCollision.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( static_cast<idAFAttachment *>( ent )->GetAttachJoint() ); } // go up the bindMaster chain to see if an Actor is lurking if( entActor == NULL ) { // no actor yet, so ent is an attachment or an attached moveable idEntity *bindMaster = ent->GetBindMaster(); while( bindMaster != NULL ) { if( bindMaster->IsType( idActor::Type ) ) { entActor = static_cast<idActor *>( bindMaster ); // the object hit something attached to an actor // If ent is an idAFAttachment, we can leave ent alone // and pass the damage to it. It will, in turn, pass the // damage to the actor it's attached to. (helmets) // If ent is NOT an attachment, we have to change it to // be the actor we just found. Inventor goggles are an // example of when we have to do this, because they're // an idMoveable, and they DON'T transfer their damage // to the actor they're attached to. if( !ent->IsType( idAFAttachment::Type ) ) { ent = bindMaster; } break; } bindMaster = bindMaster->GetBindMaster(); // go up the chain } } // grayman #2816 - in order to allow knockouts from dropped objects, // we have to allow collisions where the velocity is < minDamageVelocity, // because dropped objects can have low velocity, while at the same time // carrying enough damage to warrant a KO possibility. if( canDamage && damage.Length() && ( gameLocal.time > nextDamageTime ) ) { if( !entActor || !entActor->AI_DEAD ) { float f; if( v < minDamageVelocity ) { f = 0.0f; } else if( v < maxDamageVelocity ) { f = idMath::Sqrt( ( v - minDamageVelocity ) / ( maxDamageVelocity - minDamageVelocity ) ); } else { f = 1.0f; // capped when v >= maxDamageVelocity } // scale the damage by the surface type multiplier, if any idStr SurfTypeName; g_Global.GetSurfName( newCollision.c.material, SurfTypeName ); SurfTypeName = "damage_mult_" + SurfTypeName; f *= spawnArgs.GetFloat( SurfTypeName.c_str(), "1.0" ); idVec3 dir = velocity; dir.NormalizeFast(); // Use a technique similar to what's used for a melee collision // to find a better joint (location), because when the head is // hit, the joint isn't identified correctly w/o it. int location = JOINT_HANDLE_TO_CLIPMODEL_ID( newCollision.c.id ); // If this moveable is attached to an AI, identify that AI. // Otherwise, assume it was put in motion by someone. idEntity *attacker = GetPhysics()->GetClipModel()->GetOwner(); if( attacker == NULL ) { attacker = m_SetInMotionByActor.GetEntity(); } // grayman #3370 - if the entity being hit is the attacker, don't do damage if( attacker != ent ) { int preHealth = ent->health; ent->Damage( this, attacker, dir, damage, f, location, const_cast<trace_t *>( &newCollision ) ); if( ent->health < preHealth ) { // only set the timer if there was damage nextDamageTime = gameLocal.time + 1000; } } } } // Darkmod: Collision stims and a tactile alert if it collides with an AI ProcCollisionStims( ent, newCollision.c.id ); // grayman #2816 - use new collision if( entActor && entActor->IsType( idAI::Type ) ) { static_cast<idAI *>( entActor )->TactileAlert( this ); } } if( fxCollide.Length() && ( gameLocal.time > nextCollideFxTime ) ) { idEntityFx::StartFx( fxCollide, &collision.c.point, NULL, this, false ); nextCollideFxTime = gameLocal.time + 3500; } return false; }
/* ================ idBrittleFracture::ProjectDecal ================ */ void idBrittleFracture::ProjectDecal( const idVec3 &point, const idVec3 &dir, const int time, const char *damageDefName ) { int i, j, bits, clipBits; float a, c, s; idVec2 st[MAX_POINTS_ON_WINDING]; idVec3 origin; idMat3 axis, axistemp; idPlane textureAxis[2]; if ( gameLocal.isServer ) { idBitMsg msg; byte msgBuf[MAX_EVENT_PARAM_SIZE]; msg.Init( msgBuf, sizeof( msgBuf ) ); msg.BeginWriting(); msg.WriteFloat( point[0] ); msg.WriteFloat( point[1] ); msg.WriteFloat( point[2] ); msg.WriteFloat( dir[0] ); msg.WriteFloat( dir[1] ); msg.WriteFloat( dir[2] ); ServerSendEvent( EVENT_PROJECT_DECAL, &msg, true, -1 ); } if ( time >= gameLocal.time ) { // try to get the sound from the damage def const idDeclEntityDef *damageDef = NULL; const idSoundShader *sndShader = NULL; if ( damageDefName ) { damageDef = gameLocal.FindEntityDef( damageDefName, false ); if ( damageDef ) { sndShader = declManager->FindSound( damageDef->dict.GetString( "snd_shatter", "" ) ); } } if ( sndShader ) { StartSoundShader( sndShader, SND_CHANNEL_ANY, 0, false, NULL ); } else { StartSound( "snd_bullethole", SND_CHANNEL_ANY, 0, false, NULL ); } } a = gameLocal.random.RandomFloat() * idMath::TWO_PI; c = cos( a ); s = -sin( a ); axis[2] = -dir; axis[2].Normalize(); axis[2].NormalVectors( axistemp[0], axistemp[1] ); axis[0] = axistemp[ 0 ] * c + axistemp[ 1 ] * s; axis[1] = axistemp[ 0 ] * s + axistemp[ 1 ] * -c; textureAxis[0] = axis[0] * ( 1.0f / decalSize ); textureAxis[0][3] = -( point * textureAxis[0].Normal() ) + 0.5f; textureAxis[1] = axis[1] * ( 1.0f / decalSize ); textureAxis[1][3] = -( point * textureAxis[1].Normal() ) + 0.5f; for ( i = 0; i < shards.Num(); i++ ) { idFixedWinding &winding = shards[i]->winding; origin = shards[i]->clipModel->GetOrigin(); axis = shards[i]->clipModel->GetAxis(); float d0, d1; clipBits = -1; for ( j = 0; j < winding.GetNumPoints(); j++ ) { idVec3 p = origin + winding[j].ToVec3() * axis; st[j].x = d0 = textureAxis[0].Distance( p ); st[j].y = d1 = textureAxis[1].Distance( p ); bits = FLOATSIGNBITSET( d0 ); d0 = 1.0f - d0; bits |= FLOATSIGNBITSET( d1 ) << 2; d1 = 1.0f - d1; bits |= FLOATSIGNBITSET( d0 ) << 1; bits |= FLOATSIGNBITSET( d1 ) << 3; clipBits &= bits; } if ( clipBits ) { continue; } idFixedWinding *decal = new idFixedWinding; shards[i]->decals.Append( decal ); decal->SetNumPoints( winding.GetNumPoints() ); for ( j = 0; j < winding.GetNumPoints(); j++ ) { (*decal)[j].ToVec3() = winding[j].ToVec3(); (*decal)[j].s = st[j].x; (*decal)[j].t = st[j].y; } } BecomeActive( TH_UPDATEVISUALS ); }
void CMeleeWeapon::TestParry( CMeleeWeapon *other, idVec3 dir, trace_t *trace ) { CMeleeStatus *pStatus = &m_Owner.GetEntity()->m_MeleeStatus; CMeleeStatus *pOthStatus = &other->GetOwner()->m_MeleeStatus; EMeleeType otherType = other->GetMeleeType(); if( !(m_MeleeType == MELEETYPE_UNBLOCKABLE) && (otherType == MELEETYPE_BLOCKALL || otherType == m_MeleeType ) ) { // parry was succesful // TODO: Play bounce animation or reverse attack animation? // message the attacking AI pStatus->m_ActionResult = MELEERESULT_AT_PARRIED; pStatus->m_ActionPhase = MELEEPHASE_RECOVERING; DeactivateAttack(); // Hack: Play metal sound for now const idSoundShader *snd = declManager->FindSound( "sword_hit_metal" ); StartSoundShader( snd, SND_CHANNEL_BODY2, 0, true, NULL ); // TODO: Propagate sound to AI DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("Parry was successful\r"); // update the AI that parried // TODO: What happens if they decide to keep parrying, should result // change back to IN_PROGRESS? pOthStatus->m_ActionResult = MELEERESULT_PAR_BLOCKED; if (other->m_bParryStopOnSuccess) other->DeactivateParry(); // Parrying an attack can cause flat-footedness in AI if( other->GetOwner()->IsType(idAI::Type) ) { idAI *otherAI = static_cast<idAI *>(other->GetOwner()); if( otherAI->m_bCanBeFlatFooted ) { int num = otherAI->m_FlatFootParryNum; int max = otherAI->m_FlatFootParryMax; // Only the first few parries induce flatness, then a timer kicks in bool bCanAdd = num < max; bool bTimerDone = (gameLocal.time - otherAI->m_FlatFootParryTimer) > otherAI->m_FlatFootParryTime; if( bCanAdd ) { otherAI->m_bFlatFooted = true; otherAI->m_FlatFootedTimer = gameLocal.time; otherAI->m_FlatFootParryNum++; } else if( bTimerDone ) { otherAI->m_bFlatFooted = true; otherAI->m_FlatFootedTimer = gameLocal.time; // reset to flat-footed parry #1 again otherAI->m_FlatFootParryNum = 1; } // Each successful parry after the max number resets the timer // This is a gameplay tweak so the player can't just spam attack // They must wait until the AI hasn't parried in a while and 'surprise' them again if( otherAI->m_FlatFootParryNum == max ) { otherAI->m_FlatFootParryTimer = gameLocal.time; } } } } else { // Parry failed DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("Parry failed, attack type was %d, parry type was %d\r", m_MeleeType, other->GetMeleeType() ); pOthStatus->m_ActionResult = MELEERESULT_PAR_FAILED; // Try disabling this parry now that it has failed other->DeactivateParry(); // May run into problems later when multiple opponents are attacking, // if one grazes the parry (but no the player), it would deactivate the parry // rendering it unable to block a matching attack from another AI // this is probably not that important, // and could be seen as the other AI beating the blade away // TODO: Make the player weapon auto-stop the parry in this case } }
void CMeleeWeapon::MeleeCollision( idEntity *other, idVec3 dir, trace_t *tr, int location ) { const char *DamageDefName; const idDict *DmgDef; float push(0.0f), DmgScale(1.0f); idVec3 impulse(vec3_zero); idStr hitSound, sndName, surfType; DamageDefName = spawnArgs.GetString( va("def_damage_%s", m_ActionName.c_str()) ); DmgDef = gameLocal.FindEntityDefDict( DamageDefName, false ); if( !DmgDef ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Did not find damage def %s\r", DamageDefName); return; } // Apply physical impulse push = DmgDef->GetFloat( "push" ); impulse = -push * tr->c.normal; // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Applying impulse\r"); other->ApplyImpulse( this, tr->c.id, tr->c.point, impulse ); // uncomment for physics debugging // gameRenderWorld->DebugArrow( colorBlue, tr->c.point, tr->c.point + impulse, 3, 1000 ); // get type of material hit (armor, etc) int type; if( tr->c.material != NULL ) { type = tr->c.material->GetSurfaceType(); } else { type = SURFTYPE_NONE; } if ( type == SURFTYPE_NONE ) { // use a default surface type if we are unable to detect one surfType = DmgDef->GetString("default_surface_inanimate"); } else g_Global.GetSurfName( tr->c.material, surfType ); // scale the damage by owner's ability and surface type hit DmgScale *= m_Owner.GetEntity()->m_MeleeDamageMult; DmgScale *= DmgDef->GetFloat( va("damage_mult_%s",surfType.c_str()), "1.0" ); // Damage // Check for reroute entity (can happen with attachments to AI) if( other->IsType(idAFEntity_Base::Type) ) { idAFEntity_Base *otherAF = static_cast<idAFEntity_Base *>(other); int bodID = otherAF->BodyForClipModelId( tr->c.id ); idAFBody* StruckBody = otherAF->GetAFPhysics()->GetBody( bodID ); if( StruckBody != NULL ) { idEntity* reroute = StruckBody->GetRerouteEnt(); if (reroute != NULL) { // joint and clipmodel id's become invalid if we reroute, but that's okay other = reroute; } } } idActor *otherAct = NULL; if( other->IsType(idActor::Type) ) { otherAct = static_cast<idActor *>(other); } else if( other->IsType(idAFAttachment::Type) ) { otherAct = (idActor *) static_cast<idAFAttachment *>(other)->GetBody(); } // actor-specific stuff if( otherAct != NULL ) { // update melee status CMeleeStatus *pOthMeleeStatus = &otherAct->m_MeleeStatus; pOthMeleeStatus->m_LastHitByType = m_MeleeType; // switch the default surface type to that for actors if ( type == SURFTYPE_NONE ) { // use a default surface type if we are unable to detect one surfType = DmgDef->GetString("default_surface_actor"); } } if ( other->fl.takedamage ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Applying damage at clipmodel id %d, joint handle %d\r", tr->c.id, location ); // TODO: Damage scaling - on the weapon * melee proficiency on the actor other->Damage ( this, m_Owner.GetEntity(), dir, DamageDefName, DmgScale, location, tr ); } // apply a LARGE tactile alert to AI (this must be done AFTER damage, otherwise sneak attacks are broken) if( other->IsType(idAI::Type) ) { idAI *otherAI = static_cast<idAI *>(other); otherAI->TactileAlert( GetOwner(), 100 ); // being hit always causes flat-footedness if( otherAI->m_bCanBeFlatFooted ) { otherAI->m_bFlatFooted = true; otherAI->m_FlatFootedTimer = gameLocal.time; } } // Moved impact_damage_effect to DmgDef instead of weapon ent spawnargs // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Applying impact damage FX\r"); if ( DmgDef->GetBool( "impact_damage_effect" ) ) { // ishtvan: Rewrote this to get rid of old D3 code // that played the sounds in AddDamageEffect for bleeders, that doesn't make sense if ( other->spawnArgs.GetBool( "bleed" ) ) { // stuff that bleeds handle smoke and decal in AddDamageEffect other->AddDamageEffect( *tr, impulse, DmgDef->GetString( "classname" ) ); } // Handle smoke (particles), and decals // NOTE: AddDamageEffect above will handle all wound particles and decals (smoke_wound_*, mtr_wound_*) // So we only have to worry armour and other non-wounding strikes here (smoke_strike_* and mtr_strike_*) // Uncomment for surface type debugging // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Surface hit was %s\r", tr->c.material->GetName() ); // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Material type name was %s\r", surfType.c_str() ); // start impact sound based on material type // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Playing hit sound\r"); sndName = va( "snd_%s", surfType.c_str() ); hitSound = DmgDef->GetString( sndName ); if ( hitSound.IsEmpty() ) { // snd_hit is the default sound sndName = "snd_hit"; hitSound = DmgDef->GetString( sndName ); } // project decal // ishtvan: got rid of min time between decals, let it be up to anim // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Displaying decal\r"); // TODO: Make this read the surface type like smoke_strike_* ? const char *decal; decal = DmgDef->GetString( "mtr_strike" ); if ( decal && *decal ) { gameLocal.ProjectDecal( tr->c.point, -tr->c.normal, 8.0f, true, 6.0, decal, 0.0f, other, true ); } // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Launching smoke\r"); // Strike particle FX (sparks.. blood is handled in AddDamageEffect) const char *smokeName = DmgDef->GetString( va("smoke_strike_%s", surfType.c_str()) ); if ( m_ParticlesMade < cv_melee_max_particles.GetInteger() && smokeName && *smokeName != '\0' ) { const idDeclParticle *smoke = static_cast<const idDeclParticle *>( declManager->FindType( DECL_PARTICLE, smokeName ) ); float chance = DmgDef->GetFloat( va("smoke_chance_%s", surfType.c_str()), "1.0" ); if( gameLocal.random.RandomFloat() > chance ) smoke = NULL; if( smoke ) { gameLocal.smokeParticles->EmitSmoke ( smoke, gameLocal.time, gameLocal.random.RandomFloat(), tr->c.point, -tr->endAxis ); m_ParticlesMade++; } } } // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Sound playback\r"); if ( !hitSound.IsEmpty() ) { const idSoundShader *snd = declManager->FindSound( hitSound.c_str() ); StartSoundShader( snd, SND_CHANNEL_BODY2, 0, true, NULL ); // Propagate the sound to AI, must find global sound first because it's on a different dict sndName.StripLeading("snd_"); sndName = DmgDef->GetString( va("sprS_%s", sndName.c_str()) ); if( !sndName.IsEmpty() ) { // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Propagating AI sound %s\r", sndName.c_str()); PropSoundDirect( sndName.c_str(), false, false, 0.0f, 0 ); // grayman #3355 } } // DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeCollision: Done!\r"); return; }
/* ================ idEntityFx::Run ================ */ void idEntityFx::Run( int time ) { int ieff, j; idEntity *ent = NULL; const idDict *projectileDef = NULL; idProjectile *projectile = NULL; if( !fxEffect ) { return; } for( ieff = 0; ieff < fxEffect->events.Num(); ieff++ ) { const idFXSingleAction &fxaction = fxEffect->events[ieff]; idFXLocalAction &laction = actions[ieff]; // // if we're currently done with this one // if( laction.start == -1 ) { continue; } // // see if it's delayed // if( laction.delay ) { if( laction.start + ( time - laction.start ) < laction.start + ( laction.delay * 1000 ) ) { continue; } } // // each event can have it's own delay and restart // int actualStart = laction.delay ? laction.start + ( int )( laction.delay * 1000 ) : laction.start; float pct = ( float )( time - actualStart ) / ( 1000 * fxaction.duration ); if( pct >= 1.0f ) { laction.start = -1; float totalDelay = 0.0f; if( fxaction.restart ) { if( fxaction.random1 || fxaction.random2 ) { totalDelay = fxaction.random1 + gameLocal.random.RandomFloat() * ( fxaction.random2 - fxaction.random1 ); } else { totalDelay = fxaction.delay; } laction.delay = totalDelay; laction.start = time; } continue; } if( fxaction.fire.Length() ) { for( j = 0; j < fxEffect->events.Num(); j++ ) { if( fxEffect->events[j].name.Icmp( fxaction.fire ) == 0 ) { actions[j].delay = 0; } } } idFXLocalAction *useAction; if( fxaction.sibling == -1 ) { useAction = &laction; } else { useAction = &actions[fxaction.sibling]; } assert( useAction ); switch( fxaction.type ) { case FX_ATTACHLIGHT: case FX_LIGHT: { if( useAction->lightDefHandle == -1 ) { if( fxaction.type == FX_LIGHT ) { memset( &useAction->renderLight, 0, sizeof( renderLight_t ) ); useAction->renderLight.origin = GetPhysics()->GetOrigin() + fxaction.offset; useAction->renderLight.axis = GetPhysics()->GetAxis(); useAction->renderLight.lightRadius[0] = fxaction.lightRadius; useAction->renderLight.lightRadius[1] = fxaction.lightRadius; useAction->renderLight.lightRadius[2] = fxaction.lightRadius; useAction->renderLight.shader = declManager->FindMaterial( fxaction.data, false ); useAction->renderLight.shaderParms[ SHADERPARM_RED ] = fxaction.lightColor.x; useAction->renderLight.shaderParms[ SHADERPARM_GREEN ] = fxaction.lightColor.y; useAction->renderLight.shaderParms[ SHADERPARM_BLUE ] = fxaction.lightColor.z; useAction->renderLight.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; useAction->renderLight.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); useAction->renderLight.referenceSound = refSound.referenceSound; useAction->renderLight.pointLight = true; if( fxaction.noshadows ) { useAction->renderLight.noShadows = true; } useAction->lightDefHandle = gameRenderWorld->AddLightDef( &useAction->renderLight ); } if( fxaction.noshadows ) { for( j = 0; j < fxEffect->events.Num(); j++ ) { idFXLocalAction &laction2 = actions[j]; if( laction2.modelDefHandle != -1 ) { laction2.renderEntity.noShadow = true; } } } } ApplyFade( fxaction, *useAction, time, actualStart ); break; } case FX_SOUND: { if( !useAction->soundStarted ) { useAction->soundStarted = true; const idSoundShader *shader = declManager->FindSound( fxaction.data ); StartSoundShader( shader, SND_CHANNEL_ANY, 0, false, NULL ); for( j = 0; j < fxEffect->events.Num(); j++ ) { idFXLocalAction &laction2 = actions[j]; if( laction2.lightDefHandle != -1 ) { laction2.renderLight.referenceSound = refSound.referenceSound; gameRenderWorld->UpdateLightDef( laction2.lightDefHandle, &laction2.renderLight ); } } } break; } case FX_DECAL: { if( !useAction->decalDropped ) { useAction->decalDropped = true; gameLocal.ProjectDecal( GetPhysics()->GetOrigin(), GetPhysics()->GetGravity(), 8.0f, true, fxaction.size, fxaction.data ); } break; } case FX_SHAKE: { if( !useAction->shakeStarted ) { idDict args; args.Clear(); args.SetFloat( "kick_time", fxaction.shakeTime ); args.SetFloat( "kick_amplitude", fxaction.shakeAmplitude ); for( j = 0; j < gameLocal.numClients; j++ ) { idPlayer *player = gameLocal.GetClientByNum( j ); if( player && ( player->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthSqr() < Square( fxaction.shakeDistance ) ) { if( !gameLocal.isMultiplayer || !fxaction.shakeIgnoreMaster || GetBindMaster() != player ) { player->playerView.DamageImpulse( fxaction.offset, &args ); } } } if( fxaction.shakeImpulse != 0.0f && fxaction.shakeDistance != 0.0f ) { idEntity *ignore_ent = NULL; if( gameLocal.isMultiplayer ) { ignore_ent = this; if( fxaction.shakeIgnoreMaster ) { ignore_ent = GetBindMaster(); } } // lookup the ent we are bound to? gameLocal.RadiusPush( GetPhysics()->GetOrigin(), fxaction.shakeDistance, fxaction.shakeImpulse, this, ignore_ent, 1.0f, true ); } useAction->shakeStarted = true; } break; } case FX_ATTACHENTITY: case FX_PARTICLE: case FX_MODEL: { if( useAction->modelDefHandle == -1 ) { memset( &useAction->renderEntity, 0, sizeof( renderEntity_t ) ); useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset; useAction->renderEntity.axis = ( fxaction.explicitAxis ) ? fxaction.axis : GetPhysics()->GetAxis(); useAction->renderEntity.hModel = renderModelManager->FindModel( fxaction.data ); useAction->renderEntity.shaderParms[ SHADERPARM_RED ] = 1.0f; useAction->renderEntity.shaderParms[ SHADERPARM_GREEN ] = 1.0f; useAction->renderEntity.shaderParms[ SHADERPARM_BLUE ] = 1.0f; useAction->renderEntity.shaderParms[ SHADERPARM_TIMEOFFSET ] = -MS2SEC( time ); useAction->renderEntity.shaderParms[3] = 1.0f; useAction->renderEntity.shaderParms[5] = 0.0f; if( useAction->renderEntity.hModel ) { useAction->renderEntity.bounds = useAction->renderEntity.hModel->Bounds( &useAction->renderEntity ); } useAction->modelDefHandle = gameRenderWorld->AddEntityDef( &useAction->renderEntity ); } else if( fxaction.trackOrigin ) { useAction->renderEntity.origin = GetPhysics()->GetOrigin() + fxaction.offset; useAction->renderEntity.axis = fxaction.explicitAxis ? fxaction.axis : GetPhysics()->GetAxis(); #ifdef _D3XP gameRenderWorld->UpdateEntityDef( useAction->modelDefHandle, &useAction->renderEntity ); #endif } ApplyFade( fxaction, *useAction, time, actualStart ); break; } case FX_LAUNCH: { if( gameLocal.isClient ) { // client never spawns entities outside of ClientReadSnapshot useAction->launched = true; break; } if( !useAction->launched ) { useAction->launched = true; projectile = NULL; // FIXME: may need to cache this if it is slow projectileDef = gameLocal.FindEntityDefDict( fxaction.data, false ); if( !projectileDef ) { gameLocal.Warning( "projectile \'%s\' not found", fxaction.data.c_str() ); } else { gameLocal.SpawnEntityDef( *projectileDef, &ent, false ); if( ent && ent->IsType( idProjectile::Type ) ) { projectile = ( idProjectile * )ent; projectile->Create( this, GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0] ); projectile->Launch( GetPhysics()->GetOrigin(), GetPhysics()->GetAxis()[0], vec3_origin ); } } } break; } #ifdef _D3XP case FX_SHOCKWAVE: { if( gameLocal.isClient ) { useAction->shakeStarted = true; break; } if( !useAction->shakeStarted ) { idStr shockDefName; useAction->shakeStarted = true; shockDefName = fxaction.data; if( !shockDefName.Length() ) { shockDefName = "func_shockwave"; } projectileDef = gameLocal.FindEntityDefDict( shockDefName, false ); if( !projectileDef ) { gameLocal.Warning( "shockwave \'%s\' not found", shockDefName.c_str() ); } else { gameLocal.SpawnEntityDef( *projectileDef, &ent ); ent->SetOrigin( GetPhysics()->GetOrigin() + fxaction.offset ); ent->PostEventMS( &EV_Remove, ent->spawnArgs.GetInt( "duration" ) ); } } break; } #endif } } }