Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions TheForceEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ add_subdirectory(TFE_Archive/)
add_subdirectory(TFE_Asset/)
add_subdirectory(TFE_Audio/)
add_subdirectory(TFE_DarkForces/)
add_subdirectory(TFE_ExternalData/)
add_subdirectory(TFE_FileSystem/)
add_subdirectory(TFE_FrontEndUI/)
add_subdirectory(TFE_Game/)
Expand Down
49 changes: 34 additions & 15 deletions TheForceEngine/TFE_DarkForces/Actor/actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,7 @@ namespace TFE_DarkForces
break;
}

// Melee Attack
if (attackMod->attackFlags & ATTFLAG_MELEE)
{
attackMod->anim.state = STATE_ANIMATE1;
Expand Down Expand Up @@ -1012,23 +1013,41 @@ namespace TFE_DarkForces
SecObject* projObj = proj->logic.obj;
projObj->yaw = obj->yaw;

// Handle x and z fire offset.
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
// Vanilla DF did not handle arcing projectiles with STATE_FIRE1; this has been added
if (attackMod->projType == PROJ_THERMAL_DET || attackMod->projType == PROJ_MORTAR)
{
proj->delta.x = attackMod->fireOffset.x;
proj->delta.z = attackMod->fireOffset.z;
proj_handleMovement(proj);
}

// Aim at the target.
vec3_fixed target = { s_eyePos.x, s_eyePos.y + ONE_16, s_eyePos.z };
proj_aimAtTarget(proj, target);
// TDs are lobbed at an angle that depends on distance from target
proj->bounceCnt = 0;
proj->duration = 0xffffffff;
vec3_fixed target = { s_playerObject->posWS.x, s_eyePos.y + ONE_16, s_playerObject->posWS.z };
proj_aimArcing(proj, target, proj->speed);

if (attackMod->fireSpread)
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
{
proj->delta.x = attackMod->fireOffset.x;
proj->delta.z = attackMod->fireOffset.z;
proj_handleMovement(proj);
}
}
else
{
proj->vel.x += random(attackMod->fireSpread*2) - attackMod->fireSpread;
proj->vel.y += random(attackMod->fireSpread*2) - attackMod->fireSpread;
proj->vel.z += random(attackMod->fireSpread*2) - attackMod->fireSpread;
// Handle x and z fire offset.
if (attackMod->fireOffset.x | attackMod->fireOffset.z)
{
proj->delta.x = attackMod->fireOffset.x;
proj->delta.z = attackMod->fireOffset.z;
proj_handleMovement(proj);
}

// Aim at the target.
vec3_fixed target = { s_eyePos.x, s_eyePos.y + ONE_16, s_eyePos.z };
proj_aimAtTarget(proj, target);
if (attackMod->fireSpread)
{
proj->vel.x += random(attackMod->fireSpread * 2) - attackMod->fireSpread;
proj->vel.y += random(attackMod->fireSpread * 2) - attackMod->fireSpread;
proj->vel.z += random(attackMod->fireSpread * 2) - attackMod->fireSpread;
}
}
} break;
case STATE_ANIMATE1:
Expand Down Expand Up @@ -1058,7 +1077,7 @@ namespace TFE_DarkForces

SecObject* projObj = proj->logic.obj;
projObj->yaw = obj->yaw;
if (attackMod->projType == PROJ_THERMAL_DET)
if (attackMod->projType == PROJ_THERMAL_DET || attackMod->projType == PROJ_MORTAR)
{
proj->bounceCnt = 0;
proj->duration = 0xffffffff;
Expand Down
4 changes: 4 additions & 0 deletions TheForceEngine/TFE_DarkForces/Actor/animTables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ namespace TFE_DarkForces
const s32 s_commandoAnimTable[] =
{ 0, 1, 2, 3, 4, 5, 6, -1, -1, -1, -1, -1, 12, -1, -1, -1 };

// For custom sprites - based on enemies but with all anims available
const s32 s_customAnimTable[] =
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, -1, -1, -1, 12, -1, -1, -1 };

// Internal State.
static const s32* const s_animTables[] =
{
Expand Down
3 changes: 3 additions & 0 deletions TheForceEngine/TFE_DarkForces/Actor/animTables.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ namespace TFE_DarkForces
extern const s32 s_officerAnimTable[];
extern const s32 s_troopAnimTable[];
extern const s32 s_commandoAnimTable[];

// Custom
extern const s32 s_customAnimTable[];
} // namespace TFE_DarkForces
79 changes: 79 additions & 0 deletions TheForceEngine/TFE_DarkForces/Actor/enemies.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "enemies.h"
#include "flyers.h"
#include "actorModule.h"
#include "animTables.h"
#include "../logic.h"
Expand Down Expand Up @@ -183,4 +184,82 @@ namespace TFE_DarkForces

return (Logic*)dispatch;
}

// SETUP CUSTOM LOGIC
Logic* custom_actor_setup(SecObject* obj, TFE_ExternalData::CustomActorLogic* cust, LogicSetupFunc* setupFunc)
{
ActorDispatch* dispatch = actor_createDispatch(obj, setupFunc);
dispatch->alertSndSrc = sound_load(cust->alertSound, SOUND_PRIORITY_MED5);
dispatch->fov = cust->fov;
dispatch->awareRange = FIXED(cust->awareRange);

// Damage Module
DamageModule* damageMod = actor_createDamageModule(dispatch);
damageMod->hp = FIXED(cust->hitPoints);
damageMod->itemDropId = (ItemId)cust->dropItem;
damageMod->dieEffect = (HitEffectID)cust->dieEffect;
damageMod->hurtSndSrc = sound_load(cust->painSound, SOUND_PRIORITY_MED5);
damageMod->dieSndSrc = sound_load(cust->dieSound, SOUND_PRIORITY_MED5);
actor_addModule(dispatch, (ActorModule*)damageMod);

// Attack Module
AttackModule* attackMod = actor_createAttackModule(dispatch);
attackMod->attackFlags = 0;
if (cust->hasMeleeAttack) { attackMod->attackFlags |= ATTFLAG_MELEE; }
if (cust->hasRangedAttack) { attackMod->attackFlags |= ATTFLAG_RANGED; }
if (cust->litWithMeleeAttack) { attackMod->attackFlags |= ATTFLAG_LIT_MELEE; }
if (cust->litWithRangedAttack) { attackMod->attackFlags |= ATTFLAG_LIT_RNG; }
attackMod->projType = (ProjectileType)cust->projectile;
attackMod->attackPrimSndSrc = sound_load(cust->attack1Sound, SOUND_PRIORITY_LOW0);
attackMod->attackSecSndSrc = sound_load(cust->attack2Sound, SOUND_PRIORITY_LOW0);
attackMod->timing.rangedDelay = cust->rangedAttackDelay;
attackMod->timing.meleeDelay = cust->meleeAttackDelay;
attackMod->timing.losDelay = cust->losDelay;
attackMod->maxDist = FIXED(cust->maxAttackDist);
attackMod->meleeRange = FIXED(cust->meleeRange);
attackMod->meleeDmg = FIXED(cust->meleeDamage);
attackMod->meleeRate = FIXED(cust->meleeRate);
attackMod->minDist = FIXED(cust->minAttackDist);
attackMod->fireSpread = FIXED(cust->fireSpread);
s_actorState.attackMod = attackMod;
actor_addModule(dispatch, (ActorModule*)attackMod);

// Thinker Module
ThinkerModule* thinkerMod = actor_createThinkerModule(dispatch);
thinkerMod->target.speedRotation = cust->rotationSpeed;
thinkerMod->target.speed = FIXED(cust->speed);
thinkerMod->anim.flags &= 0xfffffffe;
thinkerMod->startDelay = TICKS(2);
actor_addModule(dispatch, (ActorModule*)thinkerMod);

// Flying Thinker Module (if flying enemy)
if (cust->isFlying)
{
ThinkerModule* flyingMod = actor_createFlyingModule((Logic*)dispatch);
flyingMod->target.speedRotation = cust->rotationSpeed;
flyingMod->target.speed = FIXED(cust->speed);
flyingMod->target.speedVert = FIXED(cust->verticalSpeed);
actor_addModule(dispatch, (ActorModule*)flyingMod);
}

// Movement Module
MovementModule* moveMod = actor_createMovementModule(dispatch);
dispatch->moveMod = moveMod;
moveMod->physics.width = obj->worldWidth;
if (cust->isFlying)
{
moveMod->collisionFlags = (moveMod->collisionFlags & 0xfffffff8) | 4;
moveMod->physics.yPos = FIXED(200);
}
else
{
moveMod->collisionFlags |= 1;
}

dispatch->animTable = s_customAnimTable;
actor_setupInitAnimation();

return (Logic*)dispatch;
}

} // namespace TFE_DarkForces
2 changes: 2 additions & 0 deletions TheForceEngine/TFE_DarkForces/Actor/enemies.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ namespace TFE_DarkForces
Logic* reeyees2_setup(SecObject* obj, LogicSetupFunc* setupFunc);
Logic* bossk_setup(SecObject* obj, LogicSetupFunc* setupFunc);
Logic* gamor_setup(SecObject* obj, LogicSetupFunc* setupFunc);

Logic* custom_actor_setup(SecObject* obj, TFE_ExternalData::CustomActorLogic* customEnemy, LogicSetupFunc* setupFunc);
} // namespace TFE_DarkForces
1 change: 1 addition & 0 deletions TheForceEngine/TFE_DarkForces/Actor/flyers.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

namespace TFE_DarkForces
{
ThinkerModule* actor_createFlyingModule(Logic* logic);
Logic* intDroid_setup(SecObject* obj, LogicSetupFunc* setupFunc);
Logic* probeDroid_setup(SecObject* obj, LogicSetupFunc* setupFunc);
Logic* remote_setup(SecObject* obj, LogicSetupFunc* setupFunc);
Expand Down
22 changes: 22 additions & 0 deletions TheForceEngine/TFE_DarkForces/darkForcesMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ namespace TFE_DarkForces
const char* name = zipArchive.getFileName(i);
const size_t nameLen = strlen(name);
const char* zext = &name[nameLen - 3];
const char* zext4 = &name[nameLen - 4];
if (strcasecmp(zext, "gob") == 0)
{
// Avoid MacOS references, they aren't real files.
Expand All @@ -933,6 +934,25 @@ namespace TFE_DarkForces
lfdIndex[lfdCount++] = i;
}
}
else if (strcasecmp(zext4, "json") == 0)
{
char name2[TFE_MAX_PATH];
strcpy(name2, name);
const char* subdir = strtok(name2, "/");

// If in logics subdirectory, attempt to load logics from JSON
if (strcasecmp(subdir, "logics") == 0)
{
u32 bufferLen = (u32)zipArchive.getFileLength(i);
char* buffer = (char*)malloc(bufferLen);
zipArchive.openFile(i);
zipArchive.readFile(buffer, bufferLen);
zipArchive.closeFile();

TFE_ExternalData::ExternalLogics* logics = TFE_ExternalData::getExternalLogics();
TFE_ExternalData::parseLogicData(buffer, name, logics->actorLogics);
}
}
}

// If there is only 1 LFD, assume it is mission briefings.
Expand Down Expand Up @@ -1219,6 +1239,8 @@ namespace TFE_DarkForces
weapon_startup();
loadLangHotkeys();

TFE_ExternalData::loadCustomLogics();

FilePath filePath;
TFE_Paths::getFilePath("swfont1.fnt", &filePath);
s_sharedState.swFont1 = font_load(&filePath);
Expand Down
24 changes: 22 additions & 2 deletions TheForceEngine/TFE_DarkForces/generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
#include <TFE_FileSystem/paths.h>
#include <TFE_FileSystem/filestream.h>
#include <TFE_Jedi/Serialization/serialization.h>
#include <TFE_DarkForces/logic.h>
#include <TFE_Settings/settings.h>

using namespace TFE_Jedi;

Expand All @@ -38,6 +40,8 @@ namespace TFE_DarkForces
Tick wanderTime;
Wax* wax;
JBool active;

string logicName; // JK: added to store a custom logic name
};

void generatorTaskFunc(MessageType msg)
Expand Down Expand Up @@ -104,13 +108,28 @@ namespace TFE_DarkForces

assert(gen->entities && allocator_validate(gen->entities));

TFE_Settings_Game* gameSettings = TFE_Settings::getGameSettings();

fixed16_16 dy = TFE_Jedi::abs(s_playerObject->posWS.y - spawn->posWS.y);
fixed16_16 dist = dy + distApprox(spawn->posWS.x, spawn->posWS.z, s_playerObject->posWS.x, s_playerObject->posWS.z);
if (dist >= gen->minDist && dist <= gen->maxDist && !actor_canSeeObject(spawn, s_playerObject))
{
sprite_setData(spawn, gen->wax);
obj_setEnemyLogic(spawn, gen->type);

// Search the externally defined logics for a match
TFE_ExternalData::CustomActorLogic* customLogic;
customLogic = tryFindCustomActorLogic(gen->logicName.c_str());
if (customLogic && gameSettings->df_jsonAiLogics)
{
obj_setCustomActorLogic(spawn, customLogic);
}
else if (gen->type != -1)
{
obj_setEnemyLogic(spawn, gen->type);
}

Logic** head = (Logic**)allocator_getHead_noIterUpdate((Allocator*)spawn->logic);
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;
Expand Down Expand Up @@ -211,12 +230,13 @@ namespace TFE_DarkForces
return JFALSE;
}

Logic* obj_createGenerator(SecObject* obj, LogicSetupFunc* setupFunc, KEYWORD genType)
Logic* obj_createGenerator(SecObject* obj, LogicSetupFunc* setupFunc, KEYWORD genType, const char* logicName)
{
Generator* generator = (Generator*)level_alloc(sizeof(Generator));
memset(generator, 0, sizeof(Generator));

generator->type = genType;
generator->logicName = string(logicName);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This crashes with gcc-14/15; changing it to "generator->logicName = logicName" fixes it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlauss2 thanks for picking this up - are you able to raise a fix?

const char* will implicitly cast to string?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should change this member from "string logicName" to "char logicName[16]" and do "strncpy(generator->logicName, logicName, 15);" (or whatever the max length of a logic name is).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add something like this on top of this patchset, it survived a few hours of playtesting.
Don't use std::string since the only place you use this member you do ".c_str()" on it anyway.

index e5b9878b..83b47218 100644
--- a/TheForceEngine/TFE_DarkForces/generator.cpp
+++ b/TheForceEngine/TFE_DarkForces/generator.cpp
@@ -40,8 +40,7 @@ namespace TFE_DarkForces
                Tick       wanderTime;
                Wax*       wax;
                JBool      active;
-
-               string          logicName;              // JK: added to store a custom logic name
+               char       logicName[32];               // JK: added to store a custom logic name
        };
 
        void generatorTaskFunc(MessageType msg)
@@ -118,7 +117,7 @@ namespace TFE_DarkForces
 
                                        // Search the externally defined logics for a match
                                        TFE_ExternalData::CustomActorLogic* customLogic;
-                                       customLogic = tryFindCustomActorLogic(gen->logicName.c_str());
+                                       customLogic = tryFindCustomActorLogic(gen->logicName);
                                        if (customLogic && gameSettings->df_jsonAiLogics)
                                        {
                                                obj_setCustomActorLogic(spawn, customLogic);
@@ -236,7 +235,8 @@ namespace TFE_DarkForces
                memset(generator, 0, sizeof(Generator));
 
                generator->type   = genType;
-               generator->logicName = string(logicName);
+               memset(generator->logicName, 0, 32);
+               strncpy(generator->logicName, logicName, 31);
                generator->active = 1;
                generator->delay  = 0;
 

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlauss2 thanks for that recommendation -- I've got a couple other PRs waiting review, I'll look at this as soon as those have gone through.

generator->active = 1;
generator->delay = 0;

Expand Down
2 changes: 1 addition & 1 deletion TheForceEngine/TFE_DarkForces/generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

namespace TFE_DarkForces
{
Logic* obj_createGenerator(SecObject* obj, LogicSetupFunc* setupFunc, KEYWORD genType);
Logic* obj_createGenerator(SecObject* obj, LogicSetupFunc* setupFunc, KEYWORD genType, const char* logicName);

// Serialization
void generatorLogic_serialize(Logic*& logic, SecObject* obj, Stream* stream);
Expand Down
49 changes: 47 additions & 2 deletions TheForceEngine/TFE_DarkForces/logic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,20 @@ namespace TFE_DarkForces
if (key == KW_TYPE || key == KW_LOGIC)
{
KEYWORD logicId = getKeywordIndex(s_objSeqArg1);
if (logicId == KW_PLAYER) // Player Logic.

// First, search the externally defined logics for a match (if the setting is enabled)
TFE_Settings_Game* gameSettings = TFE_Settings::getGameSettings();
TFE_ExternalData::CustomActorLogic* customLogic = (logicId != KW_PLAYER)
? tryFindCustomActorLogic(s_objSeqArg1)
: nullptr; // do not allow "LOGIC: PLAYER" to be overridden !!

if (gameSettings->df_jsonAiLogics && customLogic)
{
newLogic = obj_setCustomActorLogic(obj, customLogic);
setupFunc = nullptr;
}
// Then go to the hardcoded logics
else if (logicId == KW_PLAYER) // Player Logic.
{
player_setupObject(obj);
setupFunc = nullptr;
Expand All @@ -157,7 +170,7 @@ namespace TFE_DarkForces
else if (logicId == KW_GENERATOR) // Enemy generator, used for in-level enemy spawning.
{
KEYWORD genType = getKeywordIndex(s_objSeqArg2);
newLogic = obj_createGenerator(obj, &setupFunc, genType);
newLogic = obj_createGenerator(obj, &setupFunc, genType, s_objSeqArg2);
}
else if (logicId == KW_DISPATCH)
{
Expand Down Expand Up @@ -447,4 +460,36 @@ namespace TFE_DarkForces
assert(0);
}
}


///////////////////////////////////////////////////
// Custom logics
///////////////////////////////////////////////////
Logic* obj_setCustomActorLogic(SecObject* obj, TFE_ExternalData::CustomActorLogic* customLogic)
{
obj->flags |= OBJ_FLAG_AIM;
obj->entityFlags = ETFLAG_AI_ACTOR;

if (customLogic->isFlying) { obj->entityFlags |= ETFLAG_FLYING; }
if (customLogic->hasGravity) { obj->entityFlags |= ETFLAG_HAS_GRAVITY; }

LogicSetupFunc* setupFunc = nullptr;
return custom_actor_setup(obj, customLogic, setupFunc);
}

TFE_ExternalData::CustomActorLogic* tryFindCustomActorLogic(const char* logicName)
{
TFE_ExternalData::ExternalLogics* externalLogics = TFE_ExternalData::getExternalLogics();
u32 actorCount = externalLogics->actorLogics.size();

for (u32 a = 0; a < actorCount; a++)
{
if (strcasecmp(logicName, externalLogics->actorLogics[a].logicName) == 0)
{
return &externalLogics->actorLogics[a];
}
}

return nullptr;
}
} // TFE_DarkForces
Loading
Loading