From 9e9452daafa673e324b2dd2c0ae627fc8c352d89 Mon Sep 17 00:00:00 2001 From: Jereth Date: Sat, 27 Sep 2025 12:42:48 +1000 Subject: [PATCH 1/5] Add burst fire behaviour to Actor Attack Func --- TheForceEngine/TFE_DarkForces/Actor/actor.cpp | 42 ++++++++++++++++++- .../TFE_DarkForces/Actor/actorModule.h | 13 ++++++ .../TFE_DarkForces/Actor/animTables.h | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index 6e0a7abff..da9bf5564 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp @@ -305,6 +305,14 @@ namespace TFE_DarkForces attackMod->meleeDmg = 0; attackMod->meleeRate = FIXED(230); attackMod->attackFlags = ATTFLAG_RANGED | ATTFLAG_LIT_RNG; + + // new to TFE - burstfire + attackMod->hasBurstFire = JFALSE; + attackMod->burstFire.burstNumber = 5; + attackMod->burstFire.variation = 2; + attackMod->burstFire.interval = 29; + attackMod->burstFire.shotCount = 5; + attackMod->burstFire.lastShot = 0; // Why is this being returned? This function maybe should be a void? return attackMod->fireOffset.y; @@ -1049,7 +1057,39 @@ namespace TFE_DarkForces obj->flags |= OBJ_FLAG_FULLBRIGHT; } - attackMod->anim.state = STATE_ANIMATE1; + // Burst fire option - set via custom logics + if (attackMod->hasBurstFire) + { + if (s_curTick < attackMod->burstFire.lastShot + attackMod->burstFire.interval) + { + break; + } + + if (attackMod->burstFire.shotCount <= 1) + { + // Burst is finished, reset the shot count + attackMod->anim.state = STATE_ANIMATE1; + + s32 var = random(attackMod->burstFire.variation * 2); + s32 nextBurstNumber = attackMod->burstFire.burstNumber - attackMod->burstFire.variation + var; + attackMod->burstFire.shotCount = max(nextBurstNumber, 2); + } + else + { + // Reorient towards the player + attackMod->target.yaw = vec2ToAngle(s_playerObject->posWS.x - obj->posWS.x, s_playerObject->posWS.z - obj->posWS.z); + + // Fire the next shot in the burst + attackMod->burstFire.lastShot = s_curTick; + attackMod->burstFire.shotCount--; + } + } + else + { + // No burst fire -- vanilla logic + attackMod->anim.state = STATE_ANIMATE1; + } + vec3_fixed fireOffset = {}; // Calculate the X,Z fire offsets based on where the enemy is facing. It doesn't matter for Y. diff --git a/TheForceEngine/TFE_DarkForces/Actor/actorModule.h b/TheForceEngine/TFE_DarkForces/Actor/actorModule.h index 6fd8b32da..5ae331d0f 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actorModule.h +++ b/TheForceEngine/TFE_DarkForces/Actor/actorModule.h @@ -142,6 +142,15 @@ struct ActorTarget u32 flags; }; +struct ActorBurstFire +{ + u32 burstNumber; + u32 variation; + Tick interval; + Tick lastShot; + s32 shotCount; +}; + struct AttackModule { ActorModule header; @@ -162,6 +171,10 @@ struct AttackModule fixed16_16 meleeDmg; fixed16_16 meleeRate; u32 attackFlags; // see AttackFlags above. + + // New in TFE - Burst fire properties + JBool hasBurstFire; + ActorBurstFire burstFire; }; struct MovementModule diff --git a/TheForceEngine/TFE_DarkForces/Actor/animTables.h b/TheForceEngine/TFE_DarkForces/Actor/animTables.h index 4a42d33c8..8d4995253 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/animTables.h +++ b/TheForceEngine/TFE_DarkForces/Actor/animTables.h @@ -13,7 +13,7 @@ namespace TFE_DarkForces { - enum + enum ActorAnimation { ANIM_MOVE = 0, ANIM_ATTACK1 = 1, From a46a97a535c3f583c80ba6958ccf082b7d73229d Mon Sep 17 00:00:00 2001 From: Jereth Date: Sat, 27 Sep 2025 13:39:37 +1000 Subject: [PATCH 2/5] Set burst properties via custom logics --- .../TFE_DarkForces/Actor/enemies.cpp | 7 ++++++ TheForceEngine/TFE_ExternalData/dfLogics.cpp | 24 +++++++++++++++++++ TheForceEngine/TFE_ExternalData/dfLogics.h | 6 +++++ 3 files changed, 37 insertions(+) diff --git a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp index 599d23591..462042e32 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/enemies.cpp @@ -234,6 +234,13 @@ namespace TFE_DarkForces attackMod->fireOffset.x = floatToFixed16(cust->fireOffset.x); attackMod->fireOffset.y = cust->fireOffset.y < -999 ? attackMod->fireOffset.y : floatToFixed16(cust->fireOffset.y); // if -1000 use the default value attackMod->fireOffset.z = floatToFixed16(cust->fireOffset.z); + + attackMod->hasBurstFire = cust->hasBurstFire ? JTRUE : JFALSE; + attackMod->burstFire.burstNumber = cust->burstNumber; + attackMod->burstFire.shotCount = cust->burstNumber; + attackMod->burstFire.variation = cust->burstVariation; + attackMod->burstFire.interval = cust->burstInterval; + s_actorState.attackMod = attackMod; actor_addModule(dispatch, (ActorModule*)attackMod); diff --git a/TheForceEngine/TFE_ExternalData/dfLogics.cpp b/TheForceEngine/TFE_ExternalData/dfLogics.cpp index f7aea11d5..90ce94a9f 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.cpp +++ b/TheForceEngine/TFE_ExternalData/dfLogics.cpp @@ -400,6 +400,30 @@ namespace TFE_ExternalData return true; } + if (cJSON_IsBool(data) && strcasecmp(data->string, "hasBurstFire") == 0) + { + customLogic.hasBurstFire = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "burstNumber") == 0) + { + customLogic.burstNumber = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "burstVariation") == 0) + { + customLogic.burstVariation = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "burstInterval") == 0) + { + customLogic.burstInterval = (u32)(data->valuedouble * TICKS_PER_SECOND); + 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 a19945c77..16cf85253 100644 --- a/TheForceEngine/TFE_ExternalData/dfLogics.h +++ b/TheForceEngine/TFE_ExternalData/dfLogics.h @@ -49,6 +49,12 @@ namespace TFE_ExternalData u32 fireSpread = 30; vec3_float fireOffset = { 0, -1000, 0 }; // (y = -1000) will be treated as default + // burst fire + bool hasBurstFire = false; + u32 burstNumber = 5; + u32 burstVariation = 2; + u32 burstInterval = 29; + // Thinker and movement u32 speed = 4; u32 verticalSpeed = 10; From 059b206dc27f57ab785c37b9a645cd74fc7e1df4 Mon Sep 17 00:00:00 2001 From: Jereth Date: Sat, 27 Sep 2025 19:59:12 +1000 Subject: [PATCH 3/5] Serialise the new data --- TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp | 7 +++++++ TheForceEngine/TFE_Jedi/Level/robjData.h | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp b/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp index 3a55e9f81..1aa663ce2 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actorSerialization.cpp @@ -298,6 +298,13 @@ namespace TFE_DarkForces SERIALIZE(SaveVersionInit, attackMod->meleeDmg, 0); SERIALIZE(SaveVersionInit, attackMod->meleeRate, 0); SERIALIZE(SaveVersionInit, attackMod->attackFlags, 0); + + SERIALIZE(ObjState_BurstFire, attackMod->hasBurstFire, JFALSE); + SERIALIZE(ObjState_BurstFire, attackMod->burstFire.burstNumber, 5); + SERIALIZE(ObjState_BurstFire, attackMod->burstFire.variation, 2); + SERIALIZE(ObjState_BurstFire, attackMod->burstFire.interval, 29); + SERIALIZE(ObjState_BurstFire, attackMod->burstFire.shotCount, 5); + SERIALIZE(ObjState_BurstFire, attackMod->burstFire.lastShot, 0); } void actor_serializeAttackModule(Stream* stream, ActorModule*& mod, ActorDispatch* dispatch) diff --git a/TheForceEngine/TFE_Jedi/Level/robjData.h b/TheForceEngine/TFE_Jedi/Level/robjData.h index 24721a31c..3a01a98a0 100644 --- a/TheForceEngine/TFE_Jedi/Level/robjData.h +++ b/TheForceEngine/TFE_Jedi/Level/robjData.h @@ -69,7 +69,8 @@ enum ObjStateVersion : u32 ObjState_RefList = 9, ObjState_ExternalCamera = 10, ObjState_LogicScriptCallV1 = 11, // adds ScriptCalls on pickup, death, alert - ObjState_CurVersion = ObjState_LogicScriptCallV1, + ObjState_BurstFire = 12, + ObjState_CurVersion = ObjState_BurstFire, }; // TFE Scripting From f7a542eae733249f0c0fdd986ccf788f7ffee507 Mon Sep 17 00:00:00 2001 From: Jereth Date: Fri, 3 Oct 2025 14:08:43 +1000 Subject: [PATCH 4/5] Loop the attack animation during burst --- TheForceEngine/TFE_DarkForces/Actor/actor.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index da9bf5564..178e14176 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp @@ -995,6 +995,11 @@ namespace TFE_DarkForces // Do ranged attack (primary) attackMod->anim.state = STATE_ATTACK1; attackMod->timing.delay = attackMod->timing.rangedDelay; + + if (attackMod->hasBurstFire) + { + attackMod->anim.flags &= ~AFLAG_PLAYONCE; // if logic has burst fire, allow attack anim to loop + } } if (obj->type == OBJ_TYPE_SPRITE) @@ -1028,7 +1033,7 @@ namespace TFE_DarkForces } break; case STATE_ATTACK1: { - if (!(attackMod->anim.flags & AFLAG_READY)) + if (!(attackMod->anim.flags & AFLAG_READY) && !attackMod->hasBurstFire) { break; } @@ -1067,8 +1072,9 @@ namespace TFE_DarkForces if (attackMod->burstFire.shotCount <= 1) { - // Burst is finished, reset the shot count + // Burst is finished, end the looping & reset the shot count attackMod->anim.state = STATE_ANIMATE1; + attackMod->anim.flags |= AFLAG_PLAYONCE; s32 var = random(attackMod->burstFire.variation * 2); s32 nextBurstNumber = attackMod->burstFire.burstNumber - attackMod->burstFire.variation + var; From ba379addc7936916cafa52614a2841d342ab300d Mon Sep 17 00:00:00 2001 From: Jereth Date: Thu, 30 Oct 2025 21:29:51 +1100 Subject: [PATCH 5/5] Create a "dying" flag for actors, to stop burst fire when actor is dying --- TheForceEngine/TFE_DarkForces/Actor/actor.cpp | 4 ++++ TheForceEngine/TFE_DarkForces/Actor/actor.h | 3 +++ 2 files changed, 7 insertions(+) diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index 178e14176..24825b207 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp @@ -326,6 +326,8 @@ namespace TFE_DarkForces moveMod->collisionFlags |= ACTORCOL_GRAVITY; // Added to disable auto-aim when dying. logic->logic.obj->flags &= ~OBJ_FLAG_AIM; + + logic->flags |= ACTOR_DYING; // added to stop burst fire when actor is dying } // Returns JTRUE if the object is on the floor, or JFALSE is not on the floor or moving too fast. @@ -1070,6 +1072,8 @@ namespace TFE_DarkForces break; } + if (logic->flags & ACTOR_DYING) { break; } + if (attackMod->burstFire.shotCount <= 1) { // Burst is finished, end the looping & reset the shot count diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.h b/TheForceEngine/TFE_DarkForces/Actor/actor.h index 7d137c20d..739353840 100644 --- a/TheForceEngine/TFE_DarkForces/Actor/actor.h +++ b/TheForceEngine/TFE_DarkForces/Actor/actor.h @@ -92,6 +92,9 @@ enum ActorDispatchFlags ACTOR_PLAYER_VISIBLE = FLAG_BIT(3), ACTOR_OFFIC_ALERT = FLAG_BIT(4), // use officer alert sounds ACTOR_TROOP_ALERT = FLAG_BIT(5), // use stormtrooper alert sounds + + // Added for TFE + ACTOR_DYING = FLAG_BIT(6), }; // Forward Declarations.