BT410 5.3.102: ProcessCollision -- trees fall, walls do not, and both sides of a ram hurt
The per-contact responder (@004abb40), overriding the engine's protected
virtual per the MECH4.NOTES.md decode:
THE SEPARATING GATE: a contact whose relative velocity points away from
the surface ((normal . rel) < -1e-4) is skipped entirely -- no damage
while pulling apart.
MECH-VS-MECH: another mech takes the collision damage too, zone -1, its
own cylinder resolving where the ram landed. Both sides of a ram hurt,
and both route through the 5.3.100 economy, so a ram rattles rather than
executes.
THE CRUSHABLE SENTINEL: a CulturalIcon takes its crunch dispatch, and one
WITHOUT the StoppingCollisionVolume flag then sets the amount to exactly
0.00123f -- Simulate's response block reads that value as "the move
stands", restores the post-snap saves, and drives on through. Trees
fall; walls do not.
THE CONTACT ACCUMULATOR feeds the audio-bound collisionState indicator
(0->1 fires the impact sound once per contact, not per frame) and
captures the impact speed for the authored volume/brightness scales.
[T3 simplification, tagged in source: the authentic InitialHit/Slide
split keys StaticBounce's OUT coefficients against an unidentified
literal @0x4ac048.]
LIVE, one arena run, all three behaviours in the wild and zero deaths:
[crash] CRUNCH (crushable) at (371.551,-102.862) <- drove through it
[colldmg] rattle 1.72691 pts in 3 sub-hits
[crash] BLOCK dmg=47969.6 iv2=932.659 KNOCKDOWN <- wall still walls
[crash] BLOCK dmg=630 iv2=12.5 (x5, free taps) <- grind still free
STAGED remainder, named in MECH4.NOTES.md: the gyro crunch feed (torque 0.4
/ impulse 0.2 -- the gyro is a feel-wave stub), and the authentic
InitialHit/Slide split pending the @0x4ac048 literal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,6 +59,9 @@
|
|||||||
#if !defined(RANDOM_HPP)
|
#if !defined(RANDOM_HPP)
|
||||||
# include <random.hpp> // Random -- the collision-rattle draw
|
# include <random.hpp> // Random -- the collision-rattle draw
|
||||||
#endif
|
#endif
|
||||||
|
#if !defined(CULTURAL_HPP)
|
||||||
|
# include <cultural.hpp> // CulturalIcon -- the crunch / crushable sentinel
|
||||||
|
#endif
|
||||||
#include <dmgtable.hpp> // DamageLookupTable -- the cylinder hit table
|
#include <dmgtable.hpp> // DamageLookupTable -- the cylinder hit table
|
||||||
#include <player.hpp> // Player::VehicleDeadMessage -- the death notification
|
#include <player.hpp> // Player::VehicleDeadMessage -- the death notification
|
||||||
#include <btplayer.hpp> // BTPlayer::ScoreMessage -- the kill credit
|
#include <btplayer.hpp> // BTPlayer::ScoreMessage -- the kill credit
|
||||||
@@ -256,6 +259,7 @@ Mech::Mech(
|
|||||||
legResetLatch = 0;
|
legResetLatch = 0;
|
||||||
bodyResetLatch = 0;
|
bodyResetLatch = 0;
|
||||||
limpModeOverride = 0;
|
limpModeOverride = 0;
|
||||||
|
collisionTemporaryState = 0;
|
||||||
{
|
{
|
||||||
const char *fl = getenv("BT_FORCE_LIMP");
|
const char *fl = getenv("BT_FORCE_LIMP");
|
||||||
if (fl != NULL && (*fl == '3' || *fl == '4'))
|
if (fl != NULL && (*fl == '3' || *fl == '4'))
|
||||||
@@ -781,6 +785,166 @@ Mech::~Mech()
|
|||||||
// Entity handler which routes damageZones[zone]->TakeDamage.
|
// Entity handler which routes damageZones[zone]->TakeDamage.
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
//
|
//
|
||||||
|
//
|
||||||
|
//#############################################################################
|
||||||
|
// ProcessCollision (binary @004abb40) -- the per-contact collision
|
||||||
|
// responder, overriding the engine's protected virtual. The base head is
|
||||||
|
// Mover::ProcessCollision verbatim (MOVER.CPP:1354); what the Mech adds is
|
||||||
|
// WHO it hit:
|
||||||
|
//
|
||||||
|
// * A separating contact -- one whose relative velocity points away from
|
||||||
|
// the surface -- is skipped entirely (the -1e-4 gate). No damage while
|
||||||
|
// pulling apart.
|
||||||
|
//
|
||||||
|
// * Another MECH takes the collision damage too (zone -1: the victim's
|
||||||
|
// own cylinder resolves it). Both sides of a ram hurt.
|
||||||
|
//
|
||||||
|
// * A CulturalIcon takes the crunch, and a CRUSHABLE one (no
|
||||||
|
// StoppingCollisionVolume flag) then sets the amount to the 0.00123f
|
||||||
|
// WALK-THROUGH SENTINEL -- Simulate's response block reads exactly that
|
||||||
|
// value as "the move stands": the mech drives THROUGH the prop while
|
||||||
|
// the prop takes the damage. Trees fall, walls do not.
|
||||||
|
//#############################################################################
|
||||||
|
//
|
||||||
|
void
|
||||||
|
Mech::ProcessCollision(
|
||||||
|
Scalar time_slice,
|
||||||
|
BoxedSolidCollision &collision,
|
||||||
|
const Point3D &old_position,
|
||||||
|
Damage *damage
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Check(this);
|
||||||
|
Verify(time_slice > 0.0f);
|
||||||
|
Check(&collision);
|
||||||
|
Check_Pointer(damage);
|
||||||
|
|
||||||
|
Scalar
|
||||||
|
penetration;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!collisionVolume->ProcessCollision(
|
||||||
|
collision,
|
||||||
|
worldLinearVelocity,
|
||||||
|
lastCollisionList,
|
||||||
|
&damage->surfaceNormal,
|
||||||
|
&penetration)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Max_Clamp(penetration, time_slice);
|
||||||
|
Scalar
|
||||||
|
ratio = penetration / time_slice,
|
||||||
|
elasticity = elasticityCoefficient,
|
||||||
|
friction = frictionCoefficient;
|
||||||
|
|
||||||
|
Simulation
|
||||||
|
*owner_simulation = collision.GetTreeVolume()->GetOwningSimulation();
|
||||||
|
|
||||||
|
damage->damageAmount =
|
||||||
|
StaticBounce(
|
||||||
|
old_position,
|
||||||
|
time_slice,
|
||||||
|
ratio,
|
||||||
|
damage->surfaceNormal,
|
||||||
|
&elasticity,
|
||||||
|
minimumBounceSpeed,
|
||||||
|
&friction);
|
||||||
|
|
||||||
|
Entity
|
||||||
|
*other = (Entity *)owner_simulation;
|
||||||
|
|
||||||
|
if (other != NULL && other->IsDerivedFrom(Mover::ClassDerivations))
|
||||||
|
{
|
||||||
|
Vector3D
|
||||||
|
relative;
|
||||||
|
relative.Subtract(
|
||||||
|
worldLinearVelocity,
|
||||||
|
((Mover *)other)->GetWorldLinearVelocity());
|
||||||
|
if (damage->surfaceNormal * relative < -1.0e-4f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (other->IsDerivedFrom(Mech::ClassDerivations))
|
||||||
|
{
|
||||||
|
Damage
|
||||||
|
ram = *damage;
|
||||||
|
ram.impactPoint = Point3D(
|
||||||
|
(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,
|
||||||
|
(collision.collisionSlice.minY + collision.collisionSlice.maxY) * 0.5f,
|
||||||
|
(collision.collisionSlice.minZ + collision.collisionSlice.maxZ) * 0.5f);
|
||||||
|
|
||||||
|
Entity::TakeDamageMessage
|
||||||
|
hit(
|
||||||
|
Entity::TakeDamageMessageID,
|
||||||
|
sizeof(Entity::TakeDamageMessage),
|
||||||
|
GetEntityID(),
|
||||||
|
-1,
|
||||||
|
ram
|
||||||
|
);
|
||||||
|
other->Dispatch(&hit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other != NULL && other->IsDerivedFrom(CulturalIcon::ClassDerivations))
|
||||||
|
{
|
||||||
|
Logical
|
||||||
|
stopping =
|
||||||
|
((CulturalIcon *)other)->IsStoppingCollisionVolume();
|
||||||
|
|
||||||
|
//
|
||||||
|
// The separating gate against a STATIC icon (its velocity is zero,
|
||||||
|
// which the 1995 compiler inlined away).
|
||||||
|
//
|
||||||
|
if (damage->surfaceNormal * worldLinearVelocity < -1.0e-4f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The crunch goes out BEFORE the sentinel overwrites the amount.
|
||||||
|
//
|
||||||
|
{
|
||||||
|
Damage
|
||||||
|
crunch = *damage;
|
||||||
|
crunch.impactPoint = Point3D(
|
||||||
|
(collision.collisionSlice.minX + collision.collisionSlice.maxX) * 0.5f,
|
||||||
|
(collision.collisionSlice.minY + collision.collisionSlice.maxY) * 0.5f,
|
||||||
|
(collision.collisionSlice.minZ + collision.collisionSlice.maxZ) * 0.5f);
|
||||||
|
|
||||||
|
Entity::TakeDamageMessage
|
||||||
|
hit(
|
||||||
|
Entity::TakeDamageMessageID,
|
||||||
|
sizeof(Entity::TakeDamageMessage),
|
||||||
|
GetEntityID(),
|
||||||
|
-1,
|
||||||
|
crunch
|
||||||
|
);
|
||||||
|
other->Dispatch(&hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stopping)
|
||||||
|
{
|
||||||
|
damage->damageAmount = 0.00123f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// The contact-state accumulator. [T3 simplification, per the decomp's
|
||||||
|
// dominant branch: the authentic InitialHit(1)/Slide(2) split keys the
|
||||||
|
// StaticBounce OUT coefficients against an unidentified literal
|
||||||
|
// (@0x4ac048); a resolved contact IS an impact, so accumulate 1 and
|
||||||
|
// capture the impact speed for the audio scales.]
|
||||||
|
//
|
||||||
|
if (collisionTemporaryState == 0)
|
||||||
|
{
|
||||||
|
collisionTemporaryState = 1;
|
||||||
|
collisionSpeed = worldLinearVelocity.Length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
//#############################################################################
|
//#############################################################################
|
||||||
// DistributeCollisionDamage (binary @0049ffcc, reached ONLY from the damage
|
// DistributeCollisionDamage (binary @0049ffcc, reached ONLY from the damage
|
||||||
@@ -1741,11 +1905,17 @@ void
|
|||||||
{
|
{
|
||||||
Vector3D
|
Vector3D
|
||||||
impact_velocity = worldLinearVelocity;
|
impact_velocity = worldLinearVelocity;
|
||||||
|
Vector3D
|
||||||
|
post_snap_velocity = worldLinearVelocity;
|
||||||
|
Point3D
|
||||||
|
post_snap_position = localOrigin.linearPosition;
|
||||||
BoxedSolidCollisionList
|
BoxedSolidCollisionList
|
||||||
*collisions = GetCurrentCollisions();
|
*collisions = GetCurrentCollisions();
|
||||||
Damage
|
Damage
|
||||||
collision_damage;
|
collision_damage;
|
||||||
|
|
||||||
|
collisionTemporaryState = 0;
|
||||||
|
|
||||||
if (collisions != NULL)
|
if (collisions != NULL)
|
||||||
{
|
{
|
||||||
ProcessCollisionList(
|
ProcessCollisionList(
|
||||||
@@ -1753,7 +1923,36 @@ void
|
|||||||
&collision_damage);
|
&collision_damage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (collision_damage.damageAmount > 0.0f)
|
//
|
||||||
|
// 0->1 on a fresh contact fires the impact sound; SetState only
|
||||||
|
// fires on change, so a pressed-against contact plays once.
|
||||||
|
//
|
||||||
|
collisionState.SetState((unsigned)collisionTemporaryState);
|
||||||
|
|
||||||
|
if (collision_damage.damageAmount == 0.00123f)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// The crushable-icon WALK-THROUGH sentinel (ProcessCollision):
|
||||||
|
// the move STANDS -- restore the post-snap saves and undo the
|
||||||
|
// bounce. The prop took its crunch in the dispatch; the mech
|
||||||
|
// drives on through. (The gyro crunch feed -- torque 0.4 along
|
||||||
|
// the normalized force, upward impulse 0.2, binary
|
||||||
|
// @4aa81e/@4aa86c -- is STAGED with the gyro feel wave.)
|
||||||
|
//
|
||||||
|
worldLinearVelocity = post_snap_velocity;
|
||||||
|
localOrigin.linearPosition = post_snap_position;
|
||||||
|
localToWorld = localOrigin;
|
||||||
|
MoveCollisionVolume();
|
||||||
|
collision_damage.damageAmount = 0.0f;
|
||||||
|
|
||||||
|
if (getenv("BT_MECH_LOG"))
|
||||||
|
{
|
||||||
|
DEBUG_STREAM << "[crash] CRUNCH (crushable) at ("
|
||||||
|
<< post_snap_position.x << ","
|
||||||
|
<< post_snap_position.z << ")" << endl << flush;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (collision_damage.damageAmount > 0.0f)
|
||||||
{
|
{
|
||||||
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
worldLinearVelocity = Vector3D(0.0f, 0.0f, 0.0f);
|
||||||
localOrigin.linearPosition = frame_start_position;
|
localOrigin.linearPosition = frame_start_position;
|
||||||
|
|||||||
@@ -386,6 +386,24 @@
|
|||||||
void
|
void
|
||||||
DistributeCollisionDamage(Damage *damage);
|
DistributeCollisionDamage(Damage *damage);
|
||||||
|
|
||||||
|
//
|
||||||
|
// The per-contact collision responder (binary @004abb40) -- the
|
||||||
|
// override of the engine's protected virtual. Base resolution plus:
|
||||||
|
// the separating-contact gate, the mech-vs-mech ram dispatch, the
|
||||||
|
// CulturalIcon crunch, and the 0.00123f walk-through sentinel for
|
||||||
|
// crushable props. See MECH4.NOTES.md for the decode.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
ProcessCollision(
|
||||||
|
Scalar time_slice,
|
||||||
|
BoxedSolidCollision &collision,
|
||||||
|
const Point3D &old_position,
|
||||||
|
Damage *damage);
|
||||||
|
|
||||||
|
int collisionTemporaryState; // binary mech+0x44c -- the frame's
|
||||||
|
// contact accumulator; pushed into
|
||||||
|
// collisionState once per frame
|
||||||
|
|
||||||
static Scalar
|
static Scalar
|
||||||
BodyClipFinished(
|
BodyClipFinished(
|
||||||
Mech *mech,
|
Mech *mech,
|
||||||
|
|||||||
Reference in New Issue
Block a user