/* ============ idClip::TraceRenderModel ============ */ void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const { trace.fraction = 1.0f; // if the trace is passing through the bounds if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) { modelTrace_t modelTrace; // test with exact render model and modify trace_t structure accordingly if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) { trace.fraction = modelTrace.fraction; trace.endAxis = axis; trace.endpos = modelTrace.point; trace.c.normal = modelTrace.normal; trace.c.dist = modelTrace.point * modelTrace.normal; trace.c.point = modelTrace.point; trace.c.type = CONTACT_TRMVERTEX; trace.c.modelFeature = 0; trace.c.trmFeature = 0; trace.c.contents = modelTrace.material->GetContentFlags(); trace.c.material = modelTrace.material; // NOTE: trace.c.id will be the joint number touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber ); } } }
void CMeleeWeapon::HandleValidCollision(trace_t &tr, idVec3 trOrigin, idVec3 PointVelDir) { int location; idEntity *other = gameLocal.entities[ tr.c.entityNum ]; DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit entity %s\r", other->name.c_str()); // Show the initial trace collision point if( cv_melee_debug.GetBool() ) gameRenderWorld->DebugArrow( colorBlue, trOrigin, tr.c.point, 3, 1000 ); location = JOINT_HANDLE_TO_CLIPMODEL_ID( tr.c.id ); // Secondary trace for when we hit the AF structure of an AI and want // to see where we would hit on the actual model if( (tr.c.contents & CONTENTS_CORPSE) && other->IsType(idAFEntity_Base::Type) ) { idAFEntity_Base *otherAF = static_cast<idAFEntity_Base *>(other); trace_t tr2; // NOTE: Just extrapolating along the velocity can fail when the AF // extends outside of the rendermodel // Just using the AF face normal can fail when hit at a corner // So take an equal average of both idVec3 trDir = ( PointVelDir - tr.c.normal ) / 2.0f; trDir.Normalize(); idVec3 start = tr.c.point - 8.0f * trDir; int contentsEnt = GetPhysics()->GetContents(); GetPhysics()->SetContents( CONTENTS_FLASHLIGHT_TRIGGER ); gameLocal.clip.TracePoint ( tr2, start, tr.c.point + 8.0f * trDir, CONTENTS_RENDERMODEL, m_Owner.GetEntity() ); GetPhysics()->SetContents( contentsEnt ); // Ishtvan: Ignore secondary trace if we hit a swapped-in head model on the first pass // we want to register a hit on that swapped in head body for gameplay reasons bool bHitSwappedHead = false; if( otherAF->IsType(idAI::Type) ) { idAI *otherAI = static_cast<idAI *>(otherAF); if( otherAI->m_bHeadCMSwapped && (tr.c.id == otherAI->m_HeadBodyID) ) bHitSwappedHead = true; } if( tr2.fraction < 1.0f && !bHitSwappedHead ) { tr = tr2; other = gameLocal.entities[ tr.c.entityNum ]; DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: CONTENTS_CORPSE secondary trace hit entity %s\r", other->name.c_str()); // Draw the new collision point if( cv_melee_debug.GetBool() ) { gameRenderWorld->DebugArrow( colorRed, start, tr2.c.point, 3, 1000 ); } other = gameLocal.entities[tr2.c.entityNum]; // update location location = JOINT_HANDLE_TO_CLIPMODEL_ID( tr.c.id ); } else { // failed to connect with the rendermodel. // Last ditch effort to find the correct AF body location = otherAF->JointForBody(tr.c.id); // If we failed to find anything, draw the attempted trace in green if( cv_melee_debug.GetBool() ) gameRenderWorld->DebugArrow( colorGreen, start, tr.c.point + 8.0f * PointVelDir, 3, 1000 ); } // Uncomment for translational and angular velocity debugging if( cv_melee_debug.GetBool() ) { // no longer works with new function format // if we really want to separate angular and linear, // we could do this same operation in outer function /* idVec3 vLinDir = vLinearTrans; idVec3 vSpinDir = vSpinTrans; vLinDir.Normalize(); vSpinDir.Normalize(); gameRenderWorld->DebugArrow ( colorCyan, (tr.c.point - 8.0f * vLinDir), (tr.c.point + 8.0f * vLinDir), 3, 1000 ); // Uncomment for translational and angular velocity debugging gameRenderWorld->DebugArrow ( colorPurple, (tr.c.point - 8.0f * vSpinDir), (tr.c.point + 8.0f * vSpinDir), 3, 1000 ); */ // instead, show combined velocity gameRenderWorld->DebugArrow ( colorCyan, (tr.c.point - 8.0f * PointVelDir), (tr.c.point + 8.0f * PointVelDir), 3, 1000 ); } } // Direction of the velocity of point that hit (renamed it for brevity) idVec3 dir = PointVelDir; idActor *AttachOwner(NULL); idEntity *OthBindMaster(NULL); if( other->IsType(idAFAttachment::Type) ) { idEntity *othBody = static_cast<idAFAttachment *>(other)->GetBody(); if( othBody->IsType(idActor::Type) ) AttachOwner = static_cast<idActor *>(othBody); } // Also check for any object bound to an actor (helmets, etc) else if( (OthBindMaster = other->GetBindMaster()) != NULL && OthBindMaster->IsType(idActor::Type) ) AttachOwner = static_cast<idActor *>(OthBindMaster); CMeleeStatus *pStatus = &m_Owner.GetEntity()->m_MeleeStatus; // Check if we hit a melee parry or held object // (for some reason tr.c.contents erroneously returns CONTENTS_MELEEWEAP for everything except the world) // if( (tr.c.contents & CONTENTS_MELEEWEAP) != 0 ) // this doesn't always work either, it misses linked clipmodels on melee weapons // if( other->GetPhysics() && ( other->GetPhysics()->GetContents(tr.c.id) & CONTENTS_MELEEWEAP) ) // have to do this to catch all cases: if ( (other->GetPhysics() && ( other->GetPhysics()->GetContents(tr.c.id) & CONTENTS_MELEEWEAP)) || ( other->IsType(CMeleeWeapon::Type) && static_cast<CMeleeWeapon *>(other)->GetOwner() ) ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit someting with CONTENTS_MELEEWEAP\r"); // hit a parry (make sure we don't hit our own other melee weapons) if( other->IsType(CMeleeWeapon::Type) && static_cast<CMeleeWeapon *>(other)->GetOwner() && static_cast<CMeleeWeapon *>(other)->GetOwner() != m_Owner.GetEntity() ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING ("MeleeWeapon: Hit a melee parry put up by %s\r", static_cast<CMeleeWeapon *>(other)->GetOwner()->name.c_str() ); // Test our attack against their parry TestParry( static_cast<CMeleeWeapon *>(other), dir, &tr ); } // hit a held object else if( other == gameLocal.m_Grabber->GetSelected() ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit an object held by the player\r"); MeleeCollision( other, dir, &tr, location ); // TODO: Message the grabber that the grabbed object has been hit // So that it can fly out of player's hands if desired // update AI status (consider this a miss) pStatus->m_ActionResult = MELEERESULT_AT_MISSED; pStatus->m_ActionPhase = MELEEPHASE_RECOVERING; // TODO: Message the attacking AI to play a bounce off animation if appropriate DeactivateAttack(); } else { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit something with CONTENTS_MELEEWEAP that's not an active parry or a held object (this shouldn't normally happen).\r"); MeleeCollision( other, dir, &tr, location ); // update AI status (consider this a miss) pStatus->m_ActionResult = MELEERESULT_AT_MISSED; pStatus->m_ActionPhase = MELEEPHASE_RECOVERING; DeactivateAttack(); } } // Hit an actor, or an AF attachment that is part of an actor else if( other->IsType(idActor::Type) || AttachOwner != NULL ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit actor or part of actor %s\r", other->name.c_str()); idActor *owner = m_Owner.GetEntity(); idActor *otherAct; if( AttachOwner ) otherAct = AttachOwner; else otherAct = static_cast<idActor *>(other); // Don't do anything if we hit our own AF attachment // AI also don't do damage to friendlies/neutral if( AttachOwner != owner && !(owner->IsType(idAI::Type) && !(static_cast<idAI *>(owner)->IsEnemy(otherAct))) ) { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit AI other than ourselves.\r"); // TODO: Scale damage with instantaneous velocity of the blade? MeleeCollision( other, dir, &tr, location ); // update actor's melee status pStatus->m_ActionResult = MELEERESULT_AT_HIT; pStatus->m_ActionPhase = MELEEPHASE_RECOVERING; DeactivateAttack(); } else { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit yourself. Stop hitting yourself!\r"); } } // Hit something else in the world (only happens to the player) else { DM_LOG(LC_WEAPON,LT_DEBUG)LOGSTRING("MeleeWeapon: Hit a non-AI object: %s\r", other->name.c_str()); MeleeCollision( other, dir, &tr, location ); // TODO: Message the attacking actor to play a bounce off animation if appropriate // keep the attack going if the object hit is a moveable below a certain mass // TODO: Handle moveables bound to other things and use the total mass of the system? if( !( other->IsType(idMoveable::Type) && other->GetPhysics()->GetMass() < m_StopMass ) ) { // message the AI, consider this a miss pStatus->m_ActionResult = MELEERESULT_AT_MISSED; pStatus->m_ActionPhase = MELEEPHASE_RECOVERING; DeactivateAttack(); } } }
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; }
/* ============== idDragEntity::Update ============== */ void idDragEntity::Update( idPlayer *player ) { idVec3 viewPoint, origin; idMat3 viewAxis, axis; trace_t trace; idEntity *newEnt; idAngles angles; jointHandle_t newJoint; idStr newBodyName; player->GetViewPos( viewPoint, viewAxis ); // if no entity selected for dragging if ( !dragEnt.GetEntity() ) { if ( player->usercmd.buttons & BUTTON_ATTACK ) { gameLocal.clip.TracePoint( trace, viewPoint, viewPoint + viewAxis[0] * MAX_DRAG_TRACE_DISTANCE, (CONTENTS_SOLID|CONTENTS_RENDERMODEL|CONTENTS_BODY), player ); if ( trace.fraction < 1.0f ) { newEnt = gameLocal.entities[ trace.c.entityNum ]; if ( newEnt ) { if ( newEnt->GetBindMaster() ) { if ( newEnt->GetBindJoint() ) { trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( newEnt->GetBindJoint() ); } else { trace.c.id = newEnt->GetBindBody(); } newEnt = newEnt->GetBindMaster(); } if ( newEnt->IsType( idAFEntity_Base::Type ) && static_cast<idAFEntity_Base *>(newEnt)->IsActiveAF() ) { idAFEntity_Base *af = static_cast<idAFEntity_Base *>(newEnt); // joint being dragged newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); // get the body id from the trace model id which might be a joint handle trace.c.id = af->BodyForClipModelId( trace.c.id ); // get the name of the body being dragged newBodyName = af->GetAFPhysics()->GetBody( trace.c.id )->GetName(); } else if ( !newEnt->IsType( idWorldspawn::Type ) ) { if ( trace.c.id < 0 ) { newJoint = CLIPMODEL_ID_TO_JOINT_HANDLE( trace.c.id ); } else { newJoint = INVALID_JOINT; } newBodyName = ""; } else { newJoint = INVALID_JOINT; newEnt = NULL; } } if ( newEnt ) { dragEnt = newEnt; selected = newEnt; joint = newJoint; id = trace.c.id; bodyName = newBodyName; if ( !cursor ) { cursor = ( idCursor3D * )gameLocal.SpawnEntityType( idCursor3D::Type ); } idPhysics *phys = dragEnt.GetEntity()->GetPhysics(); localPlayerPoint = ( trace.c.point - viewPoint ) * viewAxis.Transpose(); origin = phys->GetOrigin( id ); axis = phys->GetAxis( id ); localEntityPoint = ( trace.c.point - origin ) * axis.Transpose(); cursor->drag.Init( g_dragDamping.GetFloat() ); cursor->drag.SetPhysics( phys, id, localEntityPoint ); cursor->Show(); if ( phys->IsType( idPhysics_AF::Type ) || phys->IsType( idPhysics_RigidBody::Type ) || phys->IsType( idPhysics_Monster::Type ) ) { cursor->BecomeActive( TH_THINK ); } } } } } // if there is an entity selected for dragging idEntity *drag = dragEnt.GetEntity(); if ( drag ) { if ( !( player->usercmd.buttons & BUTTON_ATTACK ) ) { StopDrag(); return; } cursor->SetOrigin( viewPoint + localPlayerPoint * viewAxis ); cursor->SetAxis( viewAxis ); cursor->drag.SetDragPosition( cursor->GetPhysics()->GetOrigin() ); renderEntity_t *renderEntity = drag->GetRenderEntity(); idAnimator *dragAnimator = drag->GetAnimator(); if ( joint != INVALID_JOINT && renderEntity && dragAnimator ) { dragAnimator->GetJointTransform( joint, gameLocal.time, cursor->draggedPosition, axis ); cursor->draggedPosition = renderEntity->origin + cursor->draggedPosition * renderEntity->axis; gameRenderWorld->DrawText( va( "%s\n%s\n%s, %s", drag->GetName(), drag->GetType()->classname, dragAnimator->GetJointName( joint ), bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); } else { cursor->draggedPosition = cursor->GetPhysics()->GetOrigin(); gameRenderWorld->DrawText( va( "%s\n%s\n%s", drag->GetName(), drag->GetType()->classname, bodyName.c_str() ), cursor->GetPhysics()->GetOrigin(), 0.1f, colorWhite, viewAxis, 1 ); } } // if there is a selected entity if ( selected.GetEntity() && g_dragShowSelection.GetBool() ) { // draw the bbox of the selected entity renderEntity_t *renderEntity = selected.GetEntity()->GetRenderEntity(); if ( renderEntity ) { gameRenderWorld->DebugBox( colorYellow, idBox( renderEntity->bounds, renderEntity->origin, renderEntity->axis ) ); } } }