From 2acefec8f76c4e65e1204d02baecdbcfd95ba50a Mon Sep 17 00:00:00 2001 From: Jereth Date: Sat, 25 Oct 2025 00:36:16 +1100 Subject: [PATCH 1/4] More custom logic options - allow usage of the officer and trooper alert sounds --- TheForceEngine/TFE_DarkForces/Actor/actor.cpp | 4 ++-- TheForceEngine/TFE_DarkForces/Actor/enemies.cpp | 8 ++++++++ TheForceEngine/TFE_DarkForces/generator.cpp | 2 +- TheForceEngine/TFE_ExternalData/dfLogics.cpp | 12 ++++++++++++ TheForceEngine/TFE_ExternalData/dfLogics.h | 11 +++++++++-- 5 files changed, 32 insertions(+), 5 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index f804e1aac..7314d7175 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp @@ -2013,7 +2013,7 @@ namespace TFE_DarkForces { dispatch->alertSndID = sound_playCued(s_officerAlertSndSrc[s_actorState.officerAlertIndex], obj->posWS); s_actorState.officerAlertIndex++; - if (s_actorState.officerAlertIndex >= 4) + if (s_actorState.officerAlertIndex >= OFFICER_ALERT_COUNT) { s_actorState.officerAlertIndex = 0; } @@ -2022,7 +2022,7 @@ namespace TFE_DarkForces { dispatch->alertSndID = sound_playCued(s_stormAlertSndSrc[s_actorState.stormtrooperAlertIndex], obj->posWS); s_actorState.stormtrooperAlertIndex++; - if (s_actorState.stormtrooperAlertIndex >= 8) + if (s_actorState.stormtrooperAlertIndex >= STORM_ALERT_COUNT) { s_actorState.stormtrooperAlertIndex = 0; } diff --git a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp index 947f0e8c3..4b785340c 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp @@ -193,6 +193,14 @@ namespace TFE_DarkForces dispatch->fov = floatToAngle((f32)cust->fov); dispatch->awareRange = FIXED(cust->awareRange); + if (cust->officerAlerts) + { + dispatch->flags |= ACTOR_OFFIC_ALERT; + } + if (cust->troopAlerts) + { + dispatch->flags |= ACTOR_TROOP_ALERT; + } // Damage Module DamageModule* damageMod = actor_createDamageModule(dispatch); damageMod->hp = FIXED(cust->hitPoints); diff --git a/TheForceEngine/TFE_DarkForces/generator.cpp b/TheForceEngine/TFE_DarkForces/generator.cpp index 1672867e7..e6a462100 100644 --- a/TheForceEngine/TFE_DarkForces/generator.cpp +++ b/TheForceEngine/TFE_DarkForces/generator.cpp @@ -131,7 +131,7 @@ namespace TFE_DarkForces if (!head) { break; } // JK: This is to prevent a crash happening when an invalid logic is set to a generator ActorDispatch* actorLogic = *((ActorDispatch**)head); - actorLogic->flags &= ~1; + actorLogic->flags &= ~ACTOR_IDLE; actorLogic->freeTask = task_getCurrent(); gen->aliveCount++; gen->numTerminate--; diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.cpp b/TheForceEngine/TFE_ExternalData/dfLogics.cpp index ab52fec08..d89be2006 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.cpp +++ b/TheForceEngine/TFE_ExternalData/dfLogics.cpp @@ -370,6 +370,18 @@ namespace TFE_ExternalData return true; } + if (cJSON_IsBool(data) && strcasecmp(data->string, "officerAlerts") == 0) + { + customLogic.officerAlerts = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "troopAlerts") == 0) + { + customLogic.troopAlerts = cJSON_IsTrue(data); + return true; + } + // When it comes to offsets these are considered from the perspective of the actor. // // Projectile spawn details guide. diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.h b/TheForceEngine/TFE_ExternalData/dfLogics.h index 4eacfff72..a7254138c 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.h +++ b/TheForceEngine/TFE_ExternalData/dfLogics.h @@ -7,6 +7,7 @@ namespace TFE_ExternalData { + // Numeric defaults are based on what is set by default in the original code struct CustomActorLogic { const char* logicName; @@ -15,18 +16,23 @@ namespace TFE_ExternalData u32 fov = 210; // 9557 in DF angle u32 awareRange = 20; + // Sound effects const char* alertSound = ""; const char* painSound = ""; const char* attack1Sound = ""; const char* attack2Sound = ""; const char* dieSound = ""; - // Defaults are based on what is set by default in the original code + bool officerAlerts = false; + bool troopAlerts = false; + + // Damage u32 hitPoints = 4; s32 dropItem = -1; s32 dieEffect = -1; bool stopOnDamage = true; + // Attack bool hasMeleeAttack = false; bool hasRangedAttack = true; bool litWithMeleeAttack = false; @@ -43,6 +49,7 @@ namespace TFE_ExternalData u32 fireSpread = 30; vec3_float fireOffset = { 0, -1000, 0 }; // (y = -1000) will be treated as default + // Thinker and movement u32 speed = 4; u32 verticalSpeed = 10; u32 rotationSpeed = 720; // 0x7fff in DF angle @@ -62,4 +69,4 @@ namespace TFE_ExternalData ExternalLogics* getExternalLogics(); void loadCustomLogics(); void parseLogicData(char* data, const char* filename, std::vector& actorLogics); -} \ No newline at end of file +} From 3a9df0e53614b51d44318d72ec44e14ccacca1f9 Mon Sep 17 00:00:00 2001 From: Jereth Date: Sun, 26 Oct 2025 16:43:12 +1100 Subject: [PATCH 2/4] Refactor - give correct names to CollisionInfo variables stepUpHeight (formerly botOffset) stepDownHeight (formerly yPos) Rename ACTORCOL_BIT2 to ACTORCOL_SLIDE_RESPONSE --- TheForceEngine/TFE_DarkForces/Actor/actor.cpp | 22 ++++++++++--------- .../TFE_DarkForces/Actor/actorModule.h | 8 +++---- .../Actor/actorSerialization.cpp | 4 ++-- .../TFE_DarkForces/Actor/bobaFett.cpp | 12 +++++----- .../TFE_DarkForces/Actor/dragon.cpp | 4 ++-- .../TFE_DarkForces/Actor/enemies.cpp | 4 ++-- .../TFE_DarkForces/Actor/exploders.cpp | 4 ++-- .../TFE_DarkForces/Actor/flyers.cpp | 12 +++++----- .../TFE_DarkForces/Actor/mousebot.cpp | 6 ++--- .../TFE_DarkForces/Actor/phaseOne.cpp | 2 +- .../TFE_DarkForces/Actor/phaseThree.cpp | 6 ++--- .../TFE_DarkForces/Actor/phaseTwo.cpp | 10 ++++----- TheForceEngine/TFE_DarkForces/Actor/sewer.cpp | 4 ++-- .../TFE_DarkForces/Actor/turret.cpp | 4 ++-- .../TFE_DarkForces/Actor/welder.cpp | 4 ++-- .../TFE_Jedi/Collision/collision.cpp | 22 ++++++++++++------- TheForceEngine/TFE_Jedi/Collision/collision.h | 13 +++++++---- 17 files changed, 77 insertions(+), 64 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index 7314d7175..6e0a7abff 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp @@ -442,9 +442,9 @@ namespace TFE_DarkForces { angle14_32 rAngle = moveMod->physics.responseAngle + (s_curTick & 0xff) - 128; angle14_32 angleDiff = getAngleDifference(obj->yaw, rAngle) & 8191; - if (angleDiff > 4095) + if (angleDiff > 4095) // 90 degrees { - newAngle = moveMod->physics.responseAngle - 8191; + newAngle = moveMod->physics.responseAngle - 8191; // 180 degrees } else { @@ -632,7 +632,7 @@ namespace TFE_DarkForces { item, // obj FIXED(2), 0, FIXED(2), // offset - ONE_16, COL_INFINITY, ONE_16, 0, // botOffset, yPos, height, u1c + ONE_16, COL_INFINITY, ONE_16, 0, // stepUpHeight, stepDownHeight, height, u1c nullptr, 0, nullptr, item->worldWidth, 0, JFALSE, @@ -1427,6 +1427,7 @@ namespace TFE_DarkForces vec3_fixed desiredMove = { 0, 0, 0 }; vec3_fixed move = { 0, 0, 0 }; + // First, handle active movement - movement that the actor "intends" to do moveMod->collisionWall = nullptr; if (!(moveMod->target.flags & TARGET_FREEZE)) { @@ -1494,11 +1495,11 @@ namespace TFE_DarkForces RSector* triggerSector = (nextSector) ? nextSector : wall->sector; if (obj->entityFlags & ETFLAG_SMART_OBJ) { - message_sendToSector(triggerSector, obj, 0, MSG_TRIGGER); + message_sendToSector(triggerSector, obj, 0, MSG_TRIGGER); // smart object will try to open a door or activate an elevator that it collides with } } // Handles a single collision response + resolution step. - if (moveMod->collisionFlags & ACTORCOL_BIT2) + if (moveMod->collisionFlags & ACTORCOL_SLIDE_RESPONSE) { moveMod->collisionWall = wall; dirX = physics->responseDir.x; @@ -1509,17 +1510,18 @@ namespace TFE_DarkForces } } + // Now handle passive movement - movement caused by being pushed, eg. by explosions or projectile impacts // Apply the per-frame delta computed from the actor's velocity. if (moveMod->delta.x | moveMod->delta.z) { - physics->flags |= 1; + physics->flags |= COLINFO_INFINITE_DROP; // actor can be pushed off a cliff physics->offsetX = moveMod->delta.x; physics->offsetY = 0; physics->offsetZ = moveMod->delta.z; handleCollision(physics); // Handles a single collision response + resolution step from velocity delta. - if ((moveMod->collisionFlags & ACTORCOL_BIT2) && physics->responseStep) + if ((moveMod->collisionFlags & ACTORCOL_SLIDE_RESPONSE) && physics->responseStep) { moveMod->collisionWall = physics->wall; dirX = physics->responseDir.x; @@ -1649,8 +1651,8 @@ namespace TFE_DarkForces { SecObject* obj = moveMod->header.obj; - moveMod->physics.botOffset = 0x38000; // 3.5 - moveMod->physics.yPos = FIXED(4); + moveMod->physics.stepUpHeight = 0x38000; // 3.5 units + moveMod->physics.stepDownHeight = FIXED(4); // 4 units moveMod->physics.height = obj->worldHeight; moveMod->physics.width = obj->worldWidth; moveMod->physics.responseStep = JFALSE; @@ -1662,7 +1664,7 @@ namespace TFE_DarkForces moveMod->collisionWall = nullptr; moveMod->unused = 0; - moveMod->collisionFlags = (moveMod->collisionFlags | (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)) & ~ACTORCOL_BIT2; // Set bits 0, 1 and clear bit 2. This creates a non-flying AI by default. + moveMod->collisionFlags = (moveMod->collisionFlags | (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)) & ~ACTORCOL_SLIDE_RESPONSE; // Set bits 0, 1 and clear bit 2. This creates a non-flying AI by default. obj->entityFlags |= ETFLAG_SMART_OBJ; } diff --git a/TheForceEngine/TFE_DarkForces/Actor/actorModule.h b/TheForceEngine/TFE_DarkForces/Actor/actorModule.h index ab99f4dbe..6fd8b32da 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actorModule.h +++ b/TheForceEngine/TFE_DarkForces/Actor/actorModule.h @@ -109,10 +109,10 @@ enum AttackFlags enum ActorCollisionFlags { - ACTORCOL_NO_Y_MOVE = FLAG_BIT(0), // When _not_ set, an actor can move vertically. Set for non-flying enemies. - ACTORCOL_GRAVITY = FLAG_BIT(1), - ACTORCOL_BIT2 = FLAG_BIT(2), // Alters the way collision is handled. This is generally set for flying enemies and bosses - ACTORCOL_ALL = ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY | ACTORCOL_BIT2 + ACTORCOL_NO_Y_MOVE = FLAG_BIT(0), // When _not_ set, an actor can move vertically. Set for non-flying enemies. + ACTORCOL_GRAVITY = FLAG_BIT(1), + ACTORCOL_SLIDE_RESPONSE = FLAG_BIT(2), // Actor will "slide" along a wall that they collide with. This is generally set for flying enemies and bosses. + ACTORCOL_ALL = ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY | ACTORCOL_SLIDE_RESPONSE }; struct ActorModule diff --git a/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp b/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp index e67c9850a..3a55e9f81 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp @@ -191,8 +191,8 @@ namespace TFE_DarkForces SERIALIZE(SaveVersionInit, colInfo->offsetX, 0); SERIALIZE(SaveVersionInit, colInfo->offsetY, 0); SERIALIZE(SaveVersionInit, colInfo->offsetZ, 0); - SERIALIZE(SaveVersionInit, colInfo->botOffset, 0); - SERIALIZE(SaveVersionInit, colInfo->yPos, 0); + SERIALIZE(SaveVersionInit, colInfo->stepUpHeight, 0); + SERIALIZE(SaveVersionInit, colInfo->stepDownHeight, 0); SERIALIZE(SaveVersionInit, colInfo->height, 0); SERIALIZE(SaveVersionInit, colInfo->u24, 0); SERIALIZE(SaveVersionInit, colInfo->width, 0); diff --git a/TheForceEngine/TFE_DarkForces/Actor/bobaFett.cpp b/TheForceEngine/TFE_DarkForces/Actor/bobaFett.cpp index c7d9da348..225e07ac3 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/bobaFett.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/bobaFett.cpp @@ -671,7 +671,7 @@ namespace TFE_DarkForces local(nextCheckForPlayerTick) = 0; local(changeStateTick) = s_curTick + SEARCH_DURATION; local(nextChangePhaseTick) = s_curTick + SEARCH_PHASE_INTERVAL; - local(physicsActor)->moveMod.collisionFlags |= ACTORCOL_BIT2; + local(physicsActor)->moveMod.collisionFlags |= ACTORCOL_SLIDE_RESPONSE; while (local(physicsActor)->state == BOBASTATE_SEARCH) { @@ -723,7 +723,7 @@ namespace TFE_DarkForces } } // while (state == BOBASTATE_SEARCH) - local(physicsActor)->moveMod.collisionFlags |= ACTORCOL_BIT2; + local(physicsActor)->moveMod.collisionFlags |= ACTORCOL_SLIDE_RESPONSE; task_end; } @@ -955,11 +955,11 @@ namespace TFE_DarkForces actor_setupSmartObj(&physicsActor->moveMod); physicsActor->moveMod.physics.width = FIXED(2); - physicsActor->moveMod.physics.botOffset = 0; + physicsActor->moveMod.physics.stepUpHeight = 0; physicsActor->moveMod.collisionFlags &= ~ACTORCOL_ALL; - physicsActor->moveMod.collisionFlags |= (ACTORCOL_GRAVITY | ACTORCOL_BIT2); - physicsActor->moveMod.physics.yPos = COL_INFINITY; + physicsActor->moveMod.collisionFlags |= (ACTORCOL_GRAVITY | ACTORCOL_SLIDE_RESPONSE); + physicsActor->moveMod.physics.stepDownHeight = COL_INFINITY; physicsActor->moveMod.physics.height = obj->worldHeight; LogicAnimation* anim = &physicsActor->anim; @@ -981,4 +981,4 @@ namespace TFE_DarkForces } return (Logic*)bobaFett; } -} // namespace TFE_DarkForces \ No newline at end of file +} // namespace TFE_DarkForces diff --git a/TheForceEngine/TFE_DarkForces/Actor/dragon.cpp b/TheForceEngine/TFE_DarkForces/Actor/dragon.cpp index dd454050b..c65943ea4 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/dragon.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/dragon.cpp @@ -1102,8 +1102,8 @@ namespace TFE_DarkForces obj->flags |= OBJ_FLAG_MOVABLE; CollisionInfo* physics = &physicsActor->moveMod.physics; - physics->botOffset = 0x60000; - physics->yPos = 0x80000; + physics->stepUpHeight = 0x60000; // 6 units + physics->stepDownHeight = 0x80000; // 8 units physics->width = obj->worldWidth; physicsActor->moveMod.collisionFlags |= ACTORCOL_ALL; physics->height = obj->worldHeight + HALF_16; diff --git a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp index 4b785340c..d884eb336 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp @@ -264,8 +264,8 @@ namespace TFE_DarkForces if (cust->isFlying) { - moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_BIT2; // Remove bits 0, 1 and set bit 2 - moveMod->physics.yPos = FIXED(200); + moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_SLIDE_RESPONSE; // Remove bits 0, 1 and set bit 2 + moveMod->physics.stepDownHeight = FIXED(200); } else { diff --git a/TheForceEngine/TFE_DarkForces/Actor/exploders.cpp b/TheForceEngine/TFE_DarkForces/Actor/exploders.cpp index 2730aff69..f21e35e7b 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/exploders.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/exploders.cpp @@ -104,8 +104,8 @@ namespace TFE_DarkForces colInfo.offsetY = 0; colInfo.offsetX = mul16(actorLogic->vel.x, 0x4000); colInfo.offsetZ = mul16(actorLogic->vel.z, 0x4000); - colInfo.botOffset = ONE_16; - colInfo.yPos = COL_INFINITY; + colInfo.stepUpHeight = ONE_16; + colInfo.stepDownHeight = COL_INFINITY; colInfo.height = ONE_16; colInfo.unused = 0; colInfo.width = colInfo.obj->worldWidth; diff --git a/TheForceEngine/TFE_DarkForces/Actor/flyers.cpp b/TheForceEngine/TFE_DarkForces/Actor/flyers.cpp index 8bf1b34f3..7f04f45ba 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/flyers.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/flyers.cpp @@ -159,8 +159,8 @@ namespace TFE_DarkForces MovementModule* moveMod = actor_createMovementModule(dispatch); dispatch->moveMod = moveMod; - moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_BIT2; - moveMod->physics.yPos = FIXED(200); + moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_SLIDE_RESPONSE; + moveMod->physics.stepDownHeight = FIXED(200); moveMod->physics.width = obj->worldWidth; // Setup the animation. @@ -209,8 +209,8 @@ namespace TFE_DarkForces MovementModule* moveMod = actor_createMovementModule(dispatch); dispatch->moveMod = moveMod; - moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_BIT2; - moveMod->physics.yPos = FIXED(200); + moveMod->collisionFlags = (moveMod->collisionFlags & ~ACTORCOL_ALL) | ACTORCOL_SLIDE_RESPONSE; + moveMod->physics.stepDownHeight = FIXED(200); moveMod->physics.width = obj->worldWidth; // Setup the animation. @@ -264,8 +264,8 @@ namespace TFE_DarkForces MovementModule* moveMod = actor_createMovementModule(dispatch); dispatch->moveMod = moveMod; moveMod->collisionFlags &= ~ACTORCOL_ALL; - moveMod->collisionFlags |= ACTORCOL_BIT2; - moveMod->physics.yPos = FIXED(200); + moveMod->collisionFlags |= ACTORCOL_SLIDE_RESPONSE; + moveMod->physics.stepDownHeight = FIXED(200); // should be: 0xa7ec moveMod->physics.width = obj->worldWidth; diff --git a/TheForceEngine/TFE_DarkForces/Actor/mousebot.cpp b/TheForceEngine/TFE_DarkForces/Actor/mousebot.cpp index 0b4df2b09..5a0d42efe 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/mousebot.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/mousebot.cpp @@ -487,9 +487,9 @@ namespace TFE_DarkForces obj->worldWidth = width; obj->worldHeight = width >> 1; - physActor->moveMod.physics.botOffset = 0; - physActor->moveMod.physics.yPos = 0; - physActor->moveMod.collisionFlags = (physActor->moveMod.collisionFlags | (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)) & ~ACTORCOL_BIT2; + physActor->moveMod.physics.stepUpHeight = 0; + physActor->moveMod.physics.stepDownHeight = 0; + physActor->moveMod.collisionFlags = (physActor->moveMod.collisionFlags | (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)) & ~ACTORCOL_SLIDE_RESPONSE; physActor->moveMod.physics.height = obj->worldHeight + HALF_16; physActor->moveMod.target.speed = FIXED(22); physActor->moveMod.target.speedRotation = FIXED(3185); diff --git a/TheForceEngine/TFE_DarkForces/Actor/phaseOne.cpp b/TheForceEngine/TFE_DarkForces/Actor/phaseOne.cpp index 04a2553b0..57d0c670d 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/phaseOne.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/phaseOne.cpp @@ -941,7 +941,7 @@ namespace TFE_DarkForces actor_setupSmartObj(&physicsActor->moveMod); physicsActor->moveMod.collisionFlags |= ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY; - physicsActor->moveMod.collisionFlags &= ~ACTORCOL_BIT2; + physicsActor->moveMod.collisionFlags &= ~ACTORCOL_SLIDE_RESPONSE; ActorTarget* target = &physicsActor->moveMod.target; target->flags &= ~TARGET_ALL; diff --git a/TheForceEngine/TFE_DarkForces/Actor/phaseThree.cpp b/TheForceEngine/TFE_DarkForces/Actor/phaseThree.cpp index 9f29531ea..792466dbe 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/phaseThree.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/phaseThree.cpp @@ -314,7 +314,7 @@ namespace TFE_DarkForces local(target)->speed = FIXED(70); local(flying) = JTRUE; local(physicsActor)->moveMod.collisionFlags &= (~(ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)); - local(physicsActor)->moveMod.physics.yPos = COL_INFINITY; + local(physicsActor)->moveMod.physics.stepDownHeight = COL_INFINITY; } else { @@ -355,7 +355,7 @@ namespace TFE_DarkForces } while (msg != MSG_RUN_TASK || !(local(anim)->flags & AFLAG_READY)); local(physicsActor)->moveMod.collisionFlags |= (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY); - local(physicsActor)->moveMod.physics.yPos = COL_INFINITY; + local(physicsActor)->moveMod.physics.stepDownHeight = COL_INFINITY; local(target)->speed = FIXED(25); local(target)->flags &= ~TARGET_MOVE_Y; sound_stop(local(trooper)->rocketSndId); @@ -1051,7 +1051,7 @@ namespace TFE_DarkForces actor_setupSmartObj(&physicsActor->moveMod); physicsActor->moveMod.collisionFlags |= ACTORCOL_ALL; - physicsActor->moveMod.physics.yPos = COL_INFINITY; + physicsActor->moveMod.physics.stepDownHeight = COL_INFINITY; ActorTarget* target = &physicsActor->moveMod.target; target->flags &= ~TARGET_ALL; diff --git a/TheForceEngine/TFE_DarkForces/Actor/phaseTwo.cpp b/TheForceEngine/TFE_DarkForces/Actor/phaseTwo.cpp index 04d2487b4..b43ece746 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/phaseTwo.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/phaseTwo.cpp @@ -307,7 +307,7 @@ namespace TFE_DarkForces local(target)->speed = FIXED(60); local(flying) = JTRUE; local(physicsActor)->moveMod.collisionFlags &= (~(ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY)); // flying - local(physicsActor)->moveMod.physics.yPos = COL_INFINITY; + local(physicsActor)->moveMod.physics.stepDownHeight = COL_INFINITY; } else { @@ -347,7 +347,7 @@ namespace TFE_DarkForces } while (msg != MSG_RUN_TASK || !(local(anim)->flags & AFLAG_READY)); local(physicsActor)->moveMod.collisionFlags |= (ACTORCOL_NO_Y_MOVE | ACTORCOL_GRAVITY); - local(physicsActor)->moveMod.physics.yPos = COL_INFINITY; + local(physicsActor)->moveMod.physics.stepDownHeight = COL_INFINITY; local(target)->speed = FIXED(15); local(target)->flags &= ~TARGET_MOVE_Y; sound_stop(local(trooper)->rocketSndId); @@ -728,7 +728,7 @@ namespace TFE_DarkForces { item, FIXED(3), 0, FIXED(3), // offset - ONE_16, COL_INFINITY, ONE_16, 0, // botOffset, yPos, height, 0x1c + ONE_16, COL_INFINITY, ONE_16, 0, // stepUpHeight, stepDownHeight, height, 0x1c nullptr, 0, nullptr, // wall, u24, collidedObj item->worldWidth, 0, 0, // width, u30, responseStep {0,0}, {0,0}, 0 // responseDir, responsePos, responseAngle. @@ -752,7 +752,7 @@ namespace TFE_DarkForces { item, 0, FIXED(3), FIXED(3), // offset - ONE_16, COL_INFINITY, ONE_16, 0, // botOffset, yPos, height, 0x1c + ONE_16, COL_INFINITY, ONE_16, 0, // stepUpHeight, stepDownHeight, height, 0x1c nullptr, 0, nullptr, // wall, u24, collidedObj item->worldWidth, 0, 0, // width, u30, responseStep {0,0}, {0,0}, 0 // responseDir, responsePos, responseAngle. @@ -1090,7 +1090,7 @@ namespace TFE_DarkForces actor_setupSmartObj(&physicsActor->moveMod); physicsActor->moveMod.collisionFlags |= ACTORCOL_ALL; - physicsActor->moveMod.physics.yPos = COL_INFINITY; + physicsActor->moveMod.physics.stepDownHeight = COL_INFINITY; ActorTarget* target = &physicsActor->moveMod.target; target->flags &= ~TARGET_ALL; diff --git a/TheForceEngine/TFE_DarkForces/Actor/sewer.cpp b/TheForceEngine/TFE_DarkForces/Actor/sewer.cpp index 4ca1384e8..09ccc0c9c 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/sewer.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/sewer.cpp @@ -277,8 +277,8 @@ namespace TFE_DarkForces obj->entityFlags &= ~ETFLAG_SMART_OBJ; moveMod->collisionFlags = (moveMod->collisionFlags | ACTORCOL_NO_Y_MOVE) & ~ACTORCOL_GRAVITY; // gravity is removed so they remain on the surface of water (floor height) rather than sinking down (second height) - moveMod->physics.yPos = 0; - moveMod->physics.botOffset = 0; + moveMod->physics.stepDownHeight = 0; + moveMod->physics.stepUpHeight = 0; moveMod->physics.width = obj->worldWidth; actor_setupInitAnimation(); diff --git a/TheForceEngine/TFE_DarkForces/Actor/turret.cpp b/TheForceEngine/TFE_DarkForces/Actor/turret.cpp index cfe992bb7..9cf59801b 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/turret.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/turret.cpp @@ -596,8 +596,8 @@ namespace TFE_DarkForces physics->height = obj->worldHeight + HALF_16; physics->wall = nullptr; physics->u24 = 0; - physics->botOffset = 0; - physics->yPos = 0; + physics->stepUpHeight = 0; + physics->stepDownHeight = 0; MovementModule* moveMod = &physicsActor->moveMod; moveMod->header.obj = obj; diff --git a/TheForceEngine/TFE_DarkForces/Actor/welder.cpp b/TheForceEngine/TFE_DarkForces/Actor/welder.cpp index 2e25f01cb..3e8024d98 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/welder.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/welder.cpp @@ -532,8 +532,8 @@ namespace TFE_DarkForces physics->width = obj->worldWidth; physics->wall = nullptr; physics->u24 = 0; - physics->botOffset = 0; - physics->yPos = 0; + physics->stepUpHeight = 0; + physics->stepDownHeight = 0; physics->height = obj->worldHeight + HALF_16; physicsActor->moveMod.delta = { 0, 0, 0 }; diff --git a/TheForceEngine/TFE_Jedi/Collision/collision.cpp b/TheForceEngine/TFE_Jedi/Collision/collision.cpp index 01be2c2ef..c3e331eec 100644 --- a/TheForceEngine/TFE_Jedi/Collision/collision.cpp +++ b/TheForceEngine/TFE_Jedi/Collision/collision.cpp @@ -656,6 +656,9 @@ namespace TFE_Jedi static vec3_fixed s_hcolSrcPos; static SecObject* s_hcolObj; + // Gets the dot product of the movement vector and response direction vector, and applies it to the response direction + // vector to obtain the resulting movement vector + // Causes NPCs to slide against a wall that they collide with void handleCollisionResponseSimple(fixed16_16 dirX, fixed16_16 dirZ, fixed16_16* moveX, fixed16_16* moveZ) { fixed16_16 proj = mul16(*moveX, dirX) + mul16(*moveZ, dirZ); @@ -680,11 +683,11 @@ namespace TFE_Jedi s_hcolDstPos.z = s_hcolSrcPos.z + colInfo->offsetZ; fixed16_16 top = s_hcolSrcPos.y - colInfo->height; - fixed16_16 bot = s_hcolSrcPos.y - colInfo->botOffset; + fixed16_16 bot = s_hcolSrcPos.y - colInfo->stepUpHeight; colInfo->responseStep = JFALSE; - fixed16_16 yMax = (colInfo->flags & 1) ? (s_hcolSrcPos.y + COL_INFINITY) : (s_hcolSrcPos.y + colInfo->yPos); - colInfo->flags &= ~1; + fixed16_16 yMin = (colInfo->flags & COLINFO_INFINITE_DROP) ? (s_hcolSrcPos.y + COL_INFINITY) : (s_hcolSrcPos.y + colInfo->stepDownHeight); + colInfo->flags &= ~COLINFO_INFINITE_DROP; // Cross walls until the collision path hits something solid. // Build a list of crossed walls in order to send INF events later. @@ -699,24 +702,27 @@ namespace TFE_Jedi { if ((s_hcolObj->entityFlags & ETFLAG_AI_ACTOR) && (wall->flags3 & WF3_PLAYER_WALK_ONLY)) { - break; + break; // AI actors cannot cross a wall with the WF3_PLAYER_WALK_ONLY flag } fixed16_16 ceilHeight, floorHeight; sector_getObjFloorAndCeilHeight(next, s_hcolSrcPos.y, &floorHeight, &ceilHeight); - if (floorHeight < bot || floorHeight > yMax || ceilHeight > top) + if (floorHeight < bot // next sector's floor is too high (factoring in the entity's stepUpHeight) + || floorHeight > yMin // next sector's floor is too low (factoring in the entity's stepDownHeight) - this prevents actors from walking off cliffs + || ceilHeight > top) // next sector's ceiling is too low { break; } + // Successfully crossed the wall - add to the list and move on to the next sector wallCrossList[wallCrossCount++] = wall; wall = collision_pathWallCollision(next); curSector = next; } else { - break; + break; // wall is unadjoined or has the WF3_SOLID_WALL flag } } @@ -752,7 +758,7 @@ namespace TFE_Jedi s_colHeightBase = s_hcolObj->worldHeight; s_colBottom = bot; s_colTop = top; - s_colY1 = yMax; + s_colY1 = yMin; s_colWall0 = wall; s_colObj1.wall = wall; @@ -784,7 +790,7 @@ namespace TFE_Jedi if (curSector != s_hcolSector) { - sector_addObject(curSector, s_hcolObj); + sector_addObject(curSector, s_hcolObj); // object has crossed into a new sector } // Update the object XZ position. diff --git a/TheForceEngine/TFE_Jedi/Collision/collision.h b/TheForceEngine/TFE_Jedi/Collision/collision.h index 6110424ce..5295d7976 100644 --- a/TheForceEngine/TFE_Jedi/Collision/collision.h +++ b/TheForceEngine/TFE_Jedi/Collision/collision.h @@ -38,22 +38,27 @@ struct CollisionInterval fixed16_16 dirZ; }; +enum CollisionInfoFlags : u32 +{ + COLINFO_INFINITE_DROP = FLAG_BIT(0), // when set, the entity can move over an "infinitely" high cliff edge (up to 9999 units) +}; + struct CollisionInfo { SecObject* obj; fixed16_16 offsetX; fixed16_16 offsetY; fixed16_16 offsetZ; - fixed16_16 botOffset; - fixed16_16 yPos; - fixed16_16 height; + fixed16_16 stepUpHeight; // entity will be able to "climb" up this height (same as player's stepHeight) + fixed16_16 stepDownHeight; // entity will be able to "step down" from this height + fixed16_16 height; // entity height for the purposes of collision detection s32 unused; RWall* wall; s32 u24; SecObject* collidedObj; - fixed16_16 width; + fixed16_16 width; // entity width (half-width, i.e. radius) for the purposes of collision detection u32 flags; JBool responseStep; vec2_fixed responseDir; From df5925c4c469adc8527e47bd6adfc691a34b6efa Mon Sep 17 00:00:00 2001 From: Jereth Date: Sun, 26 Oct 2025 21:34:57 +1100 Subject: [PATCH 3/4] Allow setting of step heights for custom logics --- TheForceEngine/TFE_DarkForces/Actor/enemies.cpp | 2 ++ TheForceEngine/TFE_ExternalData/dfLogics.cpp | 12 ++++++++++++ TheForceEngine/TFE_ExternalData/dfLogics.h | 2 ++ 3 files changed, 16 insertions(+) diff --git a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp index d884eb336..b781c49e0 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp @@ -261,6 +261,8 @@ namespace TFE_DarkForces dispatch->moveMod = moveMod; moveMod->physics.width = cust->collisionWidth < 0 ? obj->worldWidth : floatToFixed16(cust->collisionWidth); moveMod->physics.height = cust->collisionHeight < 0 ? moveMod->physics.height : floatToFixed16(cust->collisionHeight); + moveMod->physics.stepUpHeight = floatToFixed16(cust->stepUpHeight); + moveMod->physics.stepDownHeight = floatToFixed16(cust->stepDownHeight); if (cust->isFlying) { diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.cpp b/TheForceEngine/TFE_ExternalData/dfLogics.cpp index d89be2006..2bd2c0b22 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.cpp +++ b/TheForceEngine/TFE_ExternalData/dfLogics.cpp @@ -382,6 +382,18 @@ namespace TFE_ExternalData return true; } + if (cJSON_IsNumber(data) && strcasecmp(data->string, "stepUpHeight") == 0) + { + customLogic.stepUpHeight = data->valuedouble; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "stepDownHeight") == 0) + { + customLogic.stepDownHeight = data->valuedouble; + return true; + } + // When it comes to offsets these are considered from the perspective of the actor. // // Projectile spawn details guide. diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.h b/TheForceEngine/TFE_ExternalData/dfLogics.h index a7254138c..97ec63aa5 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.h +++ b/TheForceEngine/TFE_ExternalData/dfLogics.h @@ -59,6 +59,8 @@ namespace TFE_ExternalData f32 collisionWidth = -1; f32 collisionHeight = -1; + f32 stepUpHeight = 3.5; + f32 stepDownHeight = 4; }; struct ExternalLogics From 073d90757e2250cfc226f459b1e5aef63201acc2 Mon Sep 17 00:00:00 2001 From: Jereth Date: Thu, 30 Oct 2025 22:39:50 +1100 Subject: [PATCH 4/4] Allow changing of the collision slide response flag --- TheForceEngine/TFE_DarkForces/Actor/enemies.cpp | 10 ++++++++++ TheForceEngine/TFE_ExternalData/dfLogics.cpp | 6 ++++++ TheForceEngine/TFE_ExternalData/dfLogics.h | 1 + 3 files changed, 17 insertions(+) diff --git a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp index b781c49e0..599d23591 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp @@ -201,6 +201,7 @@ namespace TFE_DarkForces { dispatch->flags |= ACTOR_TROOP_ALERT; } + // Damage Module DamageModule* damageMod = actor_createDamageModule(dispatch); damageMod->hp = FIXED(cust->hitPoints); @@ -274,6 +275,15 @@ namespace TFE_DarkForces moveMod->collisionFlags |= ACTORCOL_NO_Y_MOVE; } + if (cust->slideOnCollision == 0) + { + moveMod->collisionFlags &= ~ACTORCOL_SLIDE_RESPONSE; + } + else if (cust->slideOnCollision == 1) + { + moveMod->collisionFlags |= ACTORCOL_SLIDE_RESPONSE; + } + dispatch->animTable = s_customAnimTable; actor_setupInitAnimation(); diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.cpp b/TheForceEngine/TFE_ExternalData/dfLogics.cpp index 2bd2c0b22..f7aea11d5 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.cpp +++ b/TheForceEngine/TFE_ExternalData/dfLogics.cpp @@ -394,6 +394,12 @@ namespace TFE_ExternalData return true; } + if (cJSON_IsBool(data) && strcasecmp(data->string, "slideOnCollision") == 0) + { + customLogic.slideOnCollision = cJSON_IsTrue(data) ? 1 : 0; + return true; + } + // When it comes to offsets these are considered from the perspective of the actor. // // Projectile spawn details guide. diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.h b/TheForceEngine/TFE_ExternalData/dfLogics.h index 97ec63aa5..a19945c77 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.h +++ b/TheForceEngine/TFE_ExternalData/dfLogics.h @@ -61,6 +61,7 @@ namespace TFE_ExternalData f32 collisionHeight = -1; f32 stepUpHeight = 3.5; f32 stepDownHeight = 4; + s32 slideOnCollision = -1; // -1 = "null", 0 = false, 1 = true }; struct ExternalLogics