diff --git a/TheForceEngine/TFE_DarkForces/Actor/actor.cpp b/TheForceEngine/TFE_DarkForces/Actor/actor.cpp index 6e0a7abff..24825b207 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; @@ -318,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. @@ -987,6 +997,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) @@ -1020,7 +1035,7 @@ namespace TFE_DarkForces } break; case STATE_ATTACK1: { - if (!(attackMod->anim.flags & AFLAG_READY)) + if (!(attackMod->anim.flags & AFLAG_READY) && !attackMod->hasBurstFire) { break; } @@ -1049,7 +1064,42 @@ 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 (logic->flags & ACTOR_DYING) { break; } + + if (attackMod->burstFire.shotCount <= 1) + { + // 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; + 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/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. 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/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_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, 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; 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