From 4c4f43f502e84796856e2b9b1085ce07bfec7ac1 Mon Sep 17 00:00:00 2001 From: Jereth Date: Wed, 16 Oct 2024 22:11:48 +1100 Subject: [PATCH] Externalise projectile and effects data to JSON files --- TheForceEngine/ExternalData/effects.json | 220 ++++++ TheForceEngine/ExternalData/projectiles.json | 442 +++++++++++ .../TFE_DarkForces/darkForcesMain.cpp | 111 ++- TheForceEngine/TFE_DarkForces/hitEffect.cpp | 218 +----- TheForceEngine/TFE_DarkForces/projectile.cpp | 689 ++++++------------ TheForceEngine/TFE_DarkForces/projectile.h | 6 +- .../TFE_ExternalData/logicTables.cpp | 1 + .../TFE_ExternalData/weaponExternal.cpp | 526 +++++++++++++ .../TFE_ExternalData/weaponExternal.h | 67 ++ TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp | 3 + TheForceEngine/TheForceEngine.vcxproj | 2 + TheForceEngine/TheForceEngine.vcxproj.filters | 8 +- 12 files changed, 1625 insertions(+), 668 deletions(-) create mode 100644 TheForceEngine/ExternalData/effects.json create mode 100644 TheForceEngine/ExternalData/projectiles.json create mode 100644 TheForceEngine/TFE_ExternalData/weaponExternal.cpp create mode 100644 TheForceEngine/TFE_ExternalData/weaponExternal.h diff --git a/TheForceEngine/ExternalData/effects.json b/TheForceEngine/ExternalData/effects.json new file mode 100644 index 000000000..1a60dae05 --- /dev/null +++ b/TheForceEngine/ExternalData/effects.json @@ -0,0 +1,220 @@ +{ + "effects": [ + { + "type": "SMALL_EXP", + "data": { + "wax": "exptiny.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 40, + "soundEffect": "ex-tiny1.voc", + "soundPriority": 1 + } + }, + { + "type": "THERMDET_EXP", + "data": { + "wax": "detexp.wax", + "force": 50, + "damage": 60, + "explosiveRange": 30, + "wakeupRange": 50, + "soundEffect": "ex-small.voc", + "soundPriority": 1 + } + }, + { + "type": "PLASMA_EXP", + "data": { + "wax": "emisexp.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 40, + "soundEffect": "ex-tiny1.voc", + "soundPriority": 1 + } + }, + { + "type": "MORTAR_EXP", + "data": { + "wax": "mortexp.wax", + "force": 35, + "damage": 50, + "explosiveRange": 40, + "wakeupRange": 60, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + + } + }, + { + "type": "CONCUSSION", + "data": { + "wax": "concexp.wax", + "force": 30, + "damage": 30, + "explosiveRange": 25, + "wakeupRange": 60, + "soundEffect": "ex-lrg1.voc", + "soundPriority": 115 + } + }, + { + "type": "CONCUSSION2", + "data": { + "wax": "", + "force": 30, + "damage": 30, + "explosiveRange": 25, + "wakeupRange": 60, + "soundEffect": "ex-lrg1.voc", + "soundPriority": 115 + } + }, + { + "type": "MISSILE_EXP", + "data": { + "wax": "missexp.wax", + "force": 70, + "damage": 70, + "explosiveRange": 40, + "wakeupRange": 70, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + } + }, + { + "type": "MISSILE_WEAK", + "data": { + "wax": "missexp.wax", + "force": 50, + "damage": 25, + "explosiveRange": 40, + "wakeupRange": 70, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + } + }, + { + "type": "PUNCH", + "data": { + "wax": "", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 10, + "soundEffect": "punch.voc", + "soundPriority": 4 + } + }, + { + "type": "CANNON_EXP", + "data": { + "wax": "plasexp.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 50, + "soundEffect": "ex-med1.voc", + "soundPriority": 1 + } + }, + { + "type": "REPEATER_EXP", + "data": { + "wax": "bullexp.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 50, + "soundEffect": "ex-tiny1.voc", + "soundPriority": 1 + } + }, + { + "type": "LARGE_EXP", + "data": { + "wax": "mineexp.wax", + "force": 80, + "damage": 90, + "explosiveRange": 45, + "wakeupRange": 90, + "soundEffect": "ex-lrg1.voc", + "soundPriority": 115 + } + }, + { + "type": "EXP_BARREL", + "data": { + "wax": "", + "force": 120, + "damage": 60, + "explosiveRange": 40, + "wakeupRange": 60, + "soundEffect": "ex-med1.voc", + "soundPriority": 76 + } + }, + { + "type": "EXP_INVIS", + "data": { + "wax": "", + "force": 60, + "damage": 40, + "explosiveRange": 20, + "wakeupRange": 60, + "soundEffect": "" + } + }, + { + "type": "SPLASH", + "data": { + "wax": "splash.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 40, + "soundEffect": "swim-in.voc", + "soundPriority": 12 + } + }, + { + "type": "EXP_35", + "data": { + "wax": "genexp.wax", + "force": 30, + "damage": 35, + "explosiveRange": 30, + "wakeupRange": 50, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + } + }, + { + "type": "EXP_NO_DMG", + "data": { + "wax": "genexp.wax", + "force": 0, + "damage": 0, + "explosiveRange": 0, + "wakeupRange": 50, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + } + }, + { + "type": "EXP_25", + "data": { + "wax": "genexp.wax", + "force": 20, + "damage": 25, + "explosiveRange": 20, + "wakeupRange": 50, + "soundEffect": "ex-med1.voc", + "soundPriority": 115 + } + } + ] +} diff --git a/TheForceEngine/ExternalData/projectiles.json b/TheForceEngine/ExternalData/projectiles.json new file mode 100644 index 000000000..be989a626 --- /dev/null +++ b/TheForceEngine/ExternalData/projectiles.json @@ -0,0 +1,442 @@ +{ + "projectiles": [ + { + "type": "PUNCH", + "data": { + "assetType": "spirit", + "updateFunc": "standard", + "damage": 6, + "falloffAmount": 0, + "force": 0.02, + "speed": 230, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": null, + "duration": 0 + } + }, + { + "type": "PISTOL_BOLT", + "data": { + "assetType": "3d", + "asset": "wrbolt.3do", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 10, + "minDamage": 1, + "falloffAmount": 1, + "nextFalloffTick": 14, + "damageFalloffDelta": 14, + "force": 0.01, + "speed": 250, + "horzBounciness": 1, + "vertBounciness": 1, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": "SMALL_EXP", + "hitEffect": "SMALL_EXP", + "duration": 3.0, + "cameraPassSound": "lasrby.voc", + "reflectSound": "boltref1.voc" + } + }, + { + "type": "RIFLE_BOLT", + "data": { + "assetType": "3d", + "asset": "wrbolt.3do", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 10, + "minDamage": 1, + "falloffAmount": 0.8, + "nextFalloffTick": 14, + "damageFalloffDelta": 14, + "force": 0.01, + "speed": 250, + "horzBounciness": 1, + "vertBounciness": 1, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": "SMALL_EXP", + "hitEffect": "SMALL_EXP", + "duration": 4.0, + "cameraPassSound": "lasrby.voc", + "reflectSound": "boltref1.voc" + } + }, + { + "type": "THERMAL_DET", + "data": { + "assetType": "frame", + "asset": "wdet.fme", + "zeroWidth": true, + "movable": true, + "updateFunc": "arcing", + "damage": 0, + "minDamage": 1, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.1, + "speed": 80, + "horzBounciness": 0.45, + "vertBounciness": 0.89, + "bounceCount": -1, + "reflectEffect": null, + "hitEffect": "thermdet_exp", + "duration": 3.0, + "cameraPassSound": null, + "reflectSound": "thermal1.voc", + "explodeOnTimeout": true + } + }, + { + "type": "REPEATER", + "data": { + "assetType": "frame", + "asset": "bullet.fme", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 10, + "minDamage": 1, + "falloffAmount": 0.3, + "nextFalloffTick": 19660, + "damageFalloffDelta": 19660, + "force": 0.03, + "speed": 270, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": null, + "hitEffect": "REPEATER_EXP", + "duration": 4.0, + "cameraPassSound": "lasrby.voc", + "reflectSound": "boltref1.voc" + } + }, + { + "type": "PLASMA", + "data": { + "assetType": "sprite", + "asset": "wemiss.wax", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 15, + "minDamage": 1, + "falloffAmount": 0.6, + "nextFalloffTick": 29, + "damageFalloffDelta": 29, + "force": 0.05, + "speed": 100, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": null, + "hitEffect": "PLASMA_EXP", + "duration": 10.0, + "cameraPassSound": "emisby.voc", + "reflectSound": "bigrefl1.voc" + } + }, + { + "type": "MORTAR", + "data": { + "assetType": "sprite", + "asset": "wshell.wax", + "zeroWidth": true, + "updateFunc": "arcing", + "damage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.2, + "speed": 110, + "horzBounciness": 0.4, + "vertBounciness": 0.6, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": "MORTAR_EXP", + "duration": 4.0 + } + }, + { + "type": "LAND_MINE", + "data": { + "assetType": "frame", + "asset": "wmine.fme", + "zeroWidth": true, + "movable": true, + "updateFunc": "arcing", + "damage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.2, + "speed": 0, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": -1, + "reflectEffect": null, + "hitEffect": "LARGE_EXP", + "duration": 3.0, + "explodeOnTimeout": true + } + }, + { + "type": "LAND_MINE_PROX", + "data": { + "assetType": "frame", + "asset": "wmine.fme", + "zeroWidth": true, + "movable": true, + "updateFunc": "arcing", + "damage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.2, + "speed": 0, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": -1, + "reflectEffect": null, + "hitEffect": "LARGE_EXP", + "duration": 3.0, + "explodeOnTimeout": true + } + }, + { + "type": "LAND_MINE_PLACED", + "data": { + "assetType": "frame", + "asset": "wlmine.fme", + "zeroWidth": true, + "movable": true, + "updateFunc": "landmine", + "damage": 0, + "falloffAmount": 0, + "force": 0.2, + "speed": 0, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": -1, + "reflectEffect": null, + "hitEffect": "LARGE_EXP", + "duration": 0, + "explodeOnTimeout": true + } + }, + { + "type": "CONCUSSION", + "data": { + "assetType": "spirit", + "zeroWidth": true, + "updateFunc": "standard", + "damage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.06, + "speed": 190, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": "concussion", + "duration": 5.0 + } + }, + { + "type": "CANNON", + "data": { + "assetType": "sprite", + "asset": "wplasma.wax", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 30, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0.061523438, + "speed": 100, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 3, + "reflectVariation": 18, + "reflectEffect": null, + "hitEffect": "CANNON_EXP", + "duration": 4.0, + "cameraPassSound": "emisby.voc", + "reflectSound": "bigrefl1.voc" + } + }, + { + "type": "MISSILE", + "data": { + "assetType": "sprite", + "asset": "wmsl.wax", + "zeroWidth": true, + "updateFunc": "standard", + "damage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 1, + "speed": 74, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": "MISSILE_EXP", + "duration": 7.0, + "flightSound": "rocket-1.voc" + } + }, + { + "type": "TURRET_BOLT", + "data": { + "assetType": "3d", + "asset": "wgbolt.3do", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 17, + "minDamage": 1, + "falloffAmount": 1, + "nextFalloffTick": 14, + "damageFalloffDelta": 14, + "force": 0.03, + "speed": 300, + "horzBounciness": 1, + "vertBounciness": 1, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": "SMALL_EXP", + "hitEffect": "SMALL_EXP", + "duration": 3.0, + "cameraPassSound": "lasrby.voc", + "reflectSound": "boltref1.voc" + } + }, + { + "type": "REMOTE_BOLT", + "data": { + "assetType": "3d", + "asset": "wgbolt.3do", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 1, + "minDamage": 0, + "falloffAmount": 1, + "nextFalloffTick": 14, + "damageFalloffDelta": 14, + "force": 0.01, + "speed": 300, + "horzBounciness": 1, + "vertBounciness": 1, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": "SMALL_EXP", + "hitEffect": "SMALL_EXP", + "duration": 3.0, + "cameraPassSound": "lasrby.voc", + "reflectSound": "boltref1.voc" + } + }, + { + "type": "EXP_BARREL", + "data": { + "assetType": "spirit", + "updateFunc": "standard", + "damage": 0, + "minDamage": 0, + "falloffAmount": 0, + "damageFalloffDelta": 0, + "force": 0, + "speed": 0, + "horzBounciness": 0, + "vertBounciness": 0, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": "EXP_BARREL", + "duration": 0, + "explodeOnTimeout": true + } + }, + { + "type": "HOMING_MISSILE", + "data": { + "assetType": "sprite", + "asset": "wdt3msl.wax", + "autoAim": true, + "updateFunc": "homing", + "damage": 0, + "minDamage": 1, + "falloffAmount": 0, + "damageFalloffDelta": 65536, + "force": 1, + "speed": 29, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 0, + "angularSpeed": 10, + "reflectEffect": null, + "hitEffect": "MISSILE_EXP", + "duration": 10.0, + "reflectSound": "", + "flightSound": "tracker.voc", + "explodeOnTimeout": true + } + }, + { + "type": "PROBE_PROJ", + "data": { + "assetType": "sprite", + "asset": "widball.wax", + "fullBright": true, + "zeroWidth": true, + "updateFunc": "standard", + "damage": 10, + "minDamage": 1, + "falloffAmount": 1, + "nextFalloffTick": 14, + "damageFalloffDelta": 14, + "force": 0.04, + "speed": 100, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 3, + "reflectVariation": 9, + "reflectEffect": null, + "hitEffect": "PLASMA_EXP", + "duration": 8.0, + "cameraPassSound": "emisby.voc", + "reflectSound": "bigrefl1.voc" + } + }, + { + "type": "BOBAFETT_BALL", + "data": { + "assetType": "sprite", + "asset": "bobaball.wax", + "zeroWidth": true, + "updateFunc": "standard", + "damage": 0, + "falloffAmount": 0, + "force": 1, + "speed": 90, + "horzBounciness": 0.9, + "vertBounciness": 0.9, + "bounceCount": 0, + "reflectEffect": null, + "hitEffect": "EXP_25", + "duration": 10.0, + "cameraPassSound": "fireball.voc" + } + } + ] +} diff --git a/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp b/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp index 76da19e5a..a1a0002fa 100644 --- a/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp +++ b/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp @@ -53,6 +53,7 @@ #include #include #include +#include #include // Add texture callbacks. @@ -883,6 +884,17 @@ namespace TFE_DarkForces s_runGameState.startLevel = agent_getLevelIndexFromName(levelName); } + char* extractTextFileFromZip(ZipArchive& zip, u32 fileIndex) + { + u32 bufferLen = (u32)zip.getFileLength(fileIndex); + char* buffer = (char*)malloc(bufferLen); + zip.openFile(fileIndex); + zip.readFile(buffer, bufferLen); + zip.closeFile(); + + return buffer; + } + void loadCustomGob(const char* gobName) { FilePath archivePath; @@ -936,21 +948,36 @@ namespace TFE_DarkForces } else if (strcasecmp(zext4, "json") == 0) { - char name2[TFE_MAX_PATH]; - strcpy(name2, name); - const char* subdir = strtok(name2, "/"); + // Load external data overrides + char fname[TFE_MAX_PATH]; + FileUtil::getFileNameFromPath(name, fname, true); - // If in logics subdirectory, attempt to load logics from JSON - if (strcasecmp(subdir, "logics") == 0) + if (strcasecmp(fname, "projectiles.json") == 0) + { + char* buffer = extractTextFileFromZip(zipArchive, i); + TFE_ExternalData::parseExternalProjectiles(buffer, true); + free(buffer); + } + else if (strcasecmp(fname, "effects.json") == 0) + { + char* buffer = extractTextFileFromZip(zipArchive, i); + TFE_ExternalData::parseExternalEffects(buffer, true); + free(buffer); + } + else { - 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); + 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) + { + char* buffer = extractTextFileFromZip(zipArchive, i); + TFE_ExternalData::ExternalLogics* logics = TFE_ExternalData::getExternalLogics(); + TFE_ExternalData::parseLogicData(buffer, name, logics->actorLogics); + free(buffer); + } } } } @@ -1089,6 +1116,45 @@ namespace TFE_DarkForces sprintf(lfdPath, "%s%s", modPath, lfdName[i]); TFE_Paths::addSingleFilePath(lfdName[i], lfdPath); } + + // Load external data overrides + char projectilesJsonPath[TFE_MAX_PATH]; + sprintf(projectilesJsonPath, "%s%s", modPath, "projectiles.json"); + if (FileUtil::exists(projectilesJsonPath)) + { + FileStream file; + if (!file.open(projectilesJsonPath, FileStream::MODE_READ)) { return; } + const size_t size = file.getSize(); + char* data = (char*)malloc(size + 1); + + if (size > 0 && data) + { + file.readBuffer(data, (u32)size); + data[size] = 0; + file.close(); + TFE_ExternalData::parseExternalProjectiles(data, true); + free(data); + } + } + + char effectsJsonPath[TFE_MAX_PATH]; + sprintf(effectsJsonPath, "%s%s", modPath, "effects.json"); + if (FileUtil::exists(effectsJsonPath)) + { + FileStream file; + if (!file.open(effectsJsonPath, FileStream::MODE_READ)) { return; } + const size_t size = file.getSize(); + char* data = (char*)malloc(size + 1); + + if (size > 0 && data) + { + file.readBuffer(data, (u32)size); + data[size] = 0; + file.close(); + TFE_ExternalData::parseExternalEffects(data, true); + free(data); + } + } } } } @@ -1237,13 +1303,26 @@ namespace TFE_DarkForces player_init(); actor_allocatePhysicsActorList(); loadCutsceneList(); - projectile_startup(); - hitEffect_startup(); weapon_startup(); loadLangHotkeys(); TFE_ExternalData::loadCustomLogics(); + TFE_ExternalData::loadExternalProjectiles(); + if (!TFE_ExternalData::validateExternalProjectiles()) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Warning: Projectile data is incomplete. PROJECTILES.JSON may have been altered. Projectiles may not behave as expected."); + } + + TFE_ExternalData::loadExternalEffects(); + if (!TFE_ExternalData::validateExternalEffects()) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Warning: Effect data is incomplete. EFFECTS.JSON may have been altered. Effects may not behave as expected."); + } + + projectile_startup(); + hitEffect_startup(); + FilePath filePath; TFE_Paths::getFilePath("swfont1.fnt", &filePath); s_sharedState.swFont1 = font_load(&filePath); @@ -1414,4 +1493,4 @@ namespace TFE_DarkForces } return true; } -} \ No newline at end of file +} diff --git a/TheForceEngine/TFE_DarkForces/hitEffect.cpp b/TheForceEngine/TFE_DarkForces/hitEffect.cpp index 56d89a663..e2bf995a2 100644 --- a/TheForceEngine/TFE_DarkForces/hitEffect.cpp +++ b/TheForceEngine/TFE_DarkForces/hitEffect.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace TFE_Jedi; @@ -40,6 +41,7 @@ namespace TFE_DarkForces vec3_fixed s_explodePos; EffectData* s_curEffectData = nullptr; + EffectData setEffectData(HitEffectID type, TFE_ExternalData::ExternalEffect* extEffects); void hitEffectWakeupFunc(SecObject* obj); void hitEffectExplodeFunc(SecObject* obj); void hitEffectTaskFunc(MessageType msg); @@ -54,192 +56,44 @@ namespace TFE_DarkForces void hitEffect_startup() { - // TODO: Move Hit Effect data to an external file instead of hardcoding here. - s_effectData[HEFFECT_SMALL_EXP] = - { - HEFFECT_SMALL_EXP, // type - TFE_Sprite_Jedi::getWax("exptiny.wax", POOL_GAME), - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(40), // wakeupRange - sound_load("ex-tiny1.voc", SOUND_PRIORITY_LOW0), // soundEffect - }; - s_effectData[HEFFECT_THERMDET_EXP] = - { - HEFFECT_THERMDET_EXP, // type - TFE_Sprite_Jedi::getWax("detexp.wax", POOL_GAME), - FIXED(50), // force - FIXED(60), // damage - FIXED(30), // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-small.voc", SOUND_PRIORITY_LOW0), // soundEffect - }; - s_effectData[HEFFECT_PLASMA_EXP] = - { - HEFFECT_PLASMA_EXP, // type - TFE_Sprite_Jedi::getWax("emisexp.wax", POOL_GAME), - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(40), // wakeupRange - sound_load("ex-tiny1.voc", SOUND_PRIORITY_LOW0), // soundEffect - }; - s_effectData[HEFFECT_MORTAR_EXP] = - { - HEFFECT_MORTAR_EXP, // type - TFE_Sprite_Jedi::getWax("mortexp.wax", POOL_GAME), - FIXED(35), // force - FIXED(50), // damage - FIXED(40), // explosiveRange - FIXED(60), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_CONCUSSION] = - { - HEFFECT_CONCUSSION, // type - TFE_Sprite_Jedi::getWax("concexp.wax", POOL_GAME), - FIXED(30), // force - FIXED(30), // damage - FIXED(25), // explosiveRange - FIXED(60), // wakeupRange - sound_load("ex-lrg1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_CONCUSSION2] = - { - HEFFECT_CONCUSSION2, // type - nullptr, // spriteData - FIXED(30), // force - FIXED(30), // damage - FIXED(25), // explosiveRange - FIXED(60), // wakeupRange - sound_load("ex-lrg1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_MISSILE_EXP] = - { - HEFFECT_MISSILE_EXP, // type - TFE_Sprite_Jedi::getWax("missexp.wax", POOL_GAME), - FIXED(70), // force - FIXED(70), // damage - FIXED(40), // explosiveRange - FIXED(70), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_MISSILE_WEAK] = - { - HEFFECT_MISSILE_WEAK, // type - TFE_Sprite_Jedi::getWax("missexp.wax", POOL_GAME), - FIXED(50), // force - FIXED(25), // damage - FIXED(40), // explosiveRange - FIXED(70), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_PUNCH] = - { - HEFFECT_PUNCH, // type - nullptr, // spriteData - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(10), // wakeupRange - sound_load("punch.voc", SOUND_PRIORITY_LOW1), // soundEffect - }; - s_effectData[HEFFECT_CANNON_EXP] = - { - HEFFECT_CANNON_EXP, // type - TFE_Sprite_Jedi::getWax("plasexp.wax", POOL_GAME), - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_LOW0), // soundEffect - }; - s_effectData[HEFFECT_REPEATER_EXP] = - { - HEFFECT_REPEATER_EXP, // type - TFE_Sprite_Jedi::getWax("bullexp.wax", POOL_GAME), - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-tiny1.voc", SOUND_PRIORITY_LOW0), // soundEffect - }; - s_effectData[HEFFECT_LARGE_EXP] = - { - HEFFECT_LARGE_EXP, // type - TFE_Sprite_Jedi::getWax("mineexp.wax", POOL_GAME), - FIXED(80), // force - FIXED(90), // damage - FIXED(45), // explosiveRange - FIXED(90), // wakeupRange - sound_load("ex-lrg1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_EXP_BARREL] = - { - HEFFECT_EXP_BARREL, // type - nullptr, // spriteData - FIXED(120), // force - FIXED(60), // damage - FIXED(40), // explosiveRange - FIXED(60), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_MED3), // soundEffect - }; - s_effectData[HEFFECT_EXP_INVIS] = - { - HEFFECT_EXP_INVIS, // type - nullptr, // spriteData - FIXED(60), // force - FIXED(40), // damage - FIXED(20), // explosiveRange - FIXED(60), // wakeupRange - 0, // soundEffect - }; - s_effectData[HEFFECT_SPLASH] = - { - HEFFECT_SPLASH, // type - TFE_Sprite_Jedi::getWax("splash.wax", POOL_GAME), - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(40), // wakeupRange - sound_load("swim-in.voc", SOUND_PRIORITY_LOW3), // soundEffect - }; + // TFE: Effect data is now defined externally. These were hardcoded in vanilla DF. + TFE_ExternalData::ExternalEffect* externalEffects = TFE_ExternalData::getExternalEffects(); - s_genExplosion = TFE_Sprite_Jedi::getWax("genexp.wax", POOL_GAME); - // Different types of explosions that use the above animation. - s_effectData[HEFFECT_EXP_35] = - { - HEFFECT_EXP_35, // type - s_genExplosion, // spriteData - FIXED(30), // force - FIXED(35), // damage - FIXED(30), // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_EXP_NO_DMG] = - { - HEFFECT_EXP_NO_DMG, // type - s_genExplosion, // spriteData - 0, // force - 0, // damage - 0, // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect - }; - s_effectData[HEFFECT_EXP_25] = + s_effectData[HEFFECT_SMALL_EXP] = setEffectData(HEFFECT_SMALL_EXP, externalEffects); + s_effectData[HEFFECT_THERMDET_EXP] = setEffectData(HEFFECT_THERMDET_EXP, externalEffects); + s_effectData[HEFFECT_PLASMA_EXP] = setEffectData(HEFFECT_PLASMA_EXP, externalEffects); + s_effectData[HEFFECT_MORTAR_EXP] = setEffectData(HEFFECT_MORTAR_EXP, externalEffects); + s_effectData[HEFFECT_CONCUSSION] = setEffectData(HEFFECT_CONCUSSION, externalEffects); + s_effectData[HEFFECT_CONCUSSION2] = setEffectData(HEFFECT_CONCUSSION2, externalEffects); + s_effectData[HEFFECT_MISSILE_EXP] = setEffectData(HEFFECT_MISSILE_EXP, externalEffects); + s_effectData[HEFFECT_MISSILE_WEAK] = setEffectData(HEFFECT_MISSILE_WEAK, externalEffects); + s_effectData[HEFFECT_PUNCH] = setEffectData(HEFFECT_PUNCH, externalEffects); + s_effectData[HEFFECT_CANNON_EXP] = setEffectData(HEFFECT_CANNON_EXP, externalEffects); + s_effectData[HEFFECT_REPEATER_EXP] = setEffectData(HEFFECT_REPEATER_EXP, externalEffects); + s_effectData[HEFFECT_LARGE_EXP] = setEffectData(HEFFECT_LARGE_EXP, externalEffects); + s_effectData[HEFFECT_EXP_BARREL] = setEffectData(HEFFECT_EXP_BARREL, externalEffects); + s_effectData[HEFFECT_EXP_INVIS] = setEffectData(HEFFECT_EXP_INVIS, externalEffects); + s_effectData[HEFFECT_SPLASH] = setEffectData(HEFFECT_SPLASH, externalEffects); + s_effectData[HEFFECT_EXP_35] = setEffectData(HEFFECT_EXP_35, externalEffects); + s_effectData[HEFFECT_EXP_NO_DMG] = setEffectData(HEFFECT_EXP_NO_DMG, externalEffects); + s_effectData[HEFFECT_EXP_25] = setEffectData(HEFFECT_EXP_25, externalEffects); + } + + // TFE: Set up effect from external data. + EffectData setEffectData(HitEffectID type, TFE_ExternalData::ExternalEffect* extEffects) + { + return { - HEFFECT_EXP_25, // type - s_genExplosion, // spriteData - FIXED(20), // force - FIXED(25), // damage - FIXED(20), // explosiveRange - FIXED(50), // wakeupRange - sound_load("ex-med1.voc", SOUND_PRIORITY_HIGH2), // soundEffect + type, // type + TFE_Sprite_Jedi::getWax(extEffects[type].wax, POOL_GAME), // spriteData + FIXED(extEffects[type].force), // force + FIXED(extEffects[type].damage), // damage + FIXED(extEffects[type].explosiveRange), // explosiveRange + FIXED(extEffects[type].wakeupRange), // wakeupRange + sound_load(extEffects[type].soundEffect, SoundPriority(extEffects[type].soundPriority)), // soundEffect & priority }; } - + void spawnHitEffect(HitEffectID hitEffectId, RSector* sector, vec3_fixed pos, SecObject* excludeObj) { if (hitEffectId != HEFFECT_NONE) diff --git a/TheForceEngine/TFE_DarkForces/projectile.cpp b/TheForceEngine/TFE_DarkForces/projectile.cpp index 99a372959..6eaaa8f6b 100644 --- a/TheForceEngine/TFE_DarkForces/projectile.cpp +++ b/TheForceEngine/TFE_DarkForces/projectile.cpp @@ -3,6 +3,7 @@ #include "random.h" #include "player.h" #include "sound.h" +#include #include #include #include @@ -13,6 +14,7 @@ #include #include #include +#include using namespace TFE_Jedi; @@ -40,6 +42,15 @@ namespace TFE_DarkForces ////////////////////////////////////////////////////////////// static Allocator* s_projectiles = nullptr; + // Arrays to hold projectile assets + static JediModel* s_projectileModels[PROJ_COUNT]; + static WaxFrame* s_projectileFrames[PROJ_COUNT]; + static Wax* s_projectileWaxes[PROJ_COUNT]; + + static SoundSourceId s_projectileCameraSnd[PROJ_COUNT]; + static SoundSourceId s_projectileReflectSnd[PROJ_COUNT]; + static SoundSourceId s_projectileFlightSnd[PROJ_COUNT]; + static JediModel* s_boltModel; static JediModel* s_greenBoltModel; static Wax* s_plasmaProj; @@ -98,6 +109,8 @@ namespace TFE_DarkForces ProjectileHitType landMineUpdateFunc(ProjectileLogic* logic); ProjectileHitType arcingProjectileUpdateFunc(ProjectileLogic* logic); ProjectileHitType homingMissileProjectileUpdateFunc(ProjectileLogic* logic); + + ProjectileFunc getUpdateFunc(const char* type); void projectileTaskFunc(MessageType msg); @@ -114,29 +127,58 @@ namespace TFE_DarkForces ////////////////////////////////////////////////////////////// void projectile_startup() { - s_boltModel = TFE_Model_Jedi::get("wrbolt.3do", POOL_GAME); - s_thermalDetProj = TFE_Sprite_Jedi::getFrame("wdet.fme", POOL_GAME); - s_repeaterProj = TFE_Sprite_Jedi::getFrame("bullet.fme", POOL_GAME); - s_plasmaProj = TFE_Sprite_Jedi::getWax("wemiss.wax", POOL_GAME); - s_mortarProj = TFE_Sprite_Jedi::getWax("wshell.wax", POOL_GAME); - s_landmineWpnFrame = TFE_Sprite_Jedi::getFrame("wmine.fme", POOL_GAME); - s_cannonProj = TFE_Sprite_Jedi::getWax("wplasma.wax", POOL_GAME); - s_missileProj = TFE_Sprite_Jedi::getWax("wmsl.wax", POOL_GAME); - s_landmineFrame = TFE_Sprite_Jedi::getFrame("wlmine.fme", POOL_GAME); - s_greenBoltModel = TFE_Model_Jedi::get("wgbolt.3do", POOL_GAME); - s_probeProj = TFE_Sprite_Jedi::getWax("widball.wax", POOL_GAME); - s_homingMissileProj = TFE_Sprite_Jedi::getWax("wdt3msl.wax", POOL_GAME); - s_bobafetBall = TFE_Sprite_Jedi::getWax("bobaball.wax", POOL_GAME); - - s_stdProjReflectSnd = sound_load("boltref1.voc", SOUND_PRIORITY_LOW1); - s_thermalDetReflectSnd = sound_load("thermal1.voc", SOUND_PRIORITY_LOW1); - s_plasmaReflectSnd = sound_load("bigrefl1.voc", SOUND_PRIORITY_LOW1); - s_stdProjCameraSnd = sound_load("lasrby.voc", SOUND_PRIORITY_LOW3); - s_plasmaCameraSnd = sound_load("emisby.voc", SOUND_PRIORITY_LOW3); - s_missileLoopingSnd = sound_load("rocket-1.voc", SOUND_PRIORITY_LOW3); - s_homingMissileFlightSnd = sound_load("tracker.voc", SOUND_PRIORITY_LOW3); - s_bobaBallCameraSnd = sound_load("fireball.voc", SOUND_PRIORITY_LOW3); - s_landMineTriggerSnd = sound_load("beep-10.voc", SOUND_PRIORITY_HIGH3); + // TFE: Assets are now listed externally. + TFE_ExternalData::ExternalProjectile* externalProjectiles = TFE_ExternalData::getExternalProjectiles(); + for (u32 p = 0; p < PROJ_COUNT; p++) + { + // Frame + if (strcasecmp(externalProjectiles[p].assetType, "frame") == 0) + { + s_projectileFrames[p] = TFE_Sprite_Jedi::getFrame(externalProjectiles[p].asset, POOL_GAME); + } + + // Wax + if (strcasecmp(externalProjectiles[p].assetType, "sprite") == 0) + { + s_projectileWaxes[p] = TFE_Sprite_Jedi::getWax(externalProjectiles[p].asset, POOL_GAME); + } + + // 3D model + if (strcasecmp(externalProjectiles[p].assetType, "3d") == 0) + { + s_projectileModels[p] = TFE_Model_Jedi::get(externalProjectiles[p].asset, POOL_GAME); + } + + // Sounds + s_projectileCameraSnd[p] = sound_load(externalProjectiles[p].cameraPassSound, SOUND_PRIORITY_LOW3); + s_projectileReflectSnd[p] = sound_load(externalProjectiles[p].reflectSound, SOUND_PRIORITY_LOW1); + s_projectileFlightSnd[p] = sound_load(externalProjectiles[p].flightSound, SOUND_PRIORITY_LOW3); + } + + // This is the original list of hardcoded assets. + // s_boltModel = TFE_Model_Jedi::get("wrbolt.3do", POOL_GAME); + // s_thermalDetProj = TFE_Sprite_Jedi::getFrame("wdet.fme", POOL_GAME); + // s_repeaterProj = TFE_Sprite_Jedi::getFrame("bullet.fme", POOL_GAME); + // s_plasmaProj = TFE_Sprite_Jedi::getWax("wemiss.wax", POOL_GAME); + // s_mortarProj = TFE_Sprite_Jedi::getWax("wshell.wax", POOL_GAME); + // s_landmineWpnFrame = TFE_Sprite_Jedi::getFrame("wmine.fme", POOL_GAME); + // s_cannonProj = TFE_Sprite_Jedi::getWax("wplasma.wax", POOL_GAME); + // s_missileProj = TFE_Sprite_Jedi::getWax("wmsl.wax", POOL_GAME); + // s_landmineFrame = TFE_Sprite_Jedi::getFrame("wlmine.fme", POOL_GAME); + // s_greenBoltModel = TFE_Model_Jedi::get("wgbolt.3do", POOL_GAME); + // s_probeProj = TFE_Sprite_Jedi::getWax("widball.wax", POOL_GAME); + // s_homingMissileProj = TFE_Sprite_Jedi::getWax("wdt3msl.wax", POOL_GAME); + // s_bobafetBall = TFE_Sprite_Jedi::getWax("bobaball.wax", POOL_GAME); + // + // s_stdProjReflectSnd = sound_load("boltref1.voc", SOUND_PRIORITY_LOW1); + // s_thermalDetReflectSnd = sound_load("thermal1.voc", SOUND_PRIORITY_LOW1); + // s_plasmaReflectSnd = sound_load("bigrefl1.voc", SOUND_PRIORITY_LOW1); + // s_stdProjCameraSnd = sound_load("lasrby.voc", SOUND_PRIORITY_LOW3); + // s_plasmaCameraSnd = sound_load("emisby.voc", SOUND_PRIORITY_LOW3); + // s_missileLoopingSnd = sound_load("rocket-1.voc", SOUND_PRIORITY_LOW3); + // s_homingMissileFlightSnd = sound_load("tracker.voc", SOUND_PRIORITY_LOW3); + // s_bobaBallCameraSnd = sound_load("fireball.voc", SOUND_PRIORITY_LOW3); + s_landMineTriggerSnd = sound_load("beep-10.voc", SOUND_PRIORITY_HIGH3); // still used! } void projectile_clearState() @@ -153,7 +195,93 @@ namespace TFE_DarkForces s_projectileTask = createSubTask("projectiles", projectileTaskFunc); } - // TODO: Move projectile data to an external file to avoid hardcoding it for TFE. + // TFE: Set the Projectile object from external data. These were hardcoded in vanilla DF. + void setProjectileObject(SecObject*& projObj, ProjectileType type, TFE_ExternalData::ExternalProjectile* externalProjectiles) + { + if (strcasecmp(externalProjectiles[type].assetType, "spirit") == 0) + { + spirit_setData(projObj); + } + else if (strcasecmp(externalProjectiles[type].assetType, "frame") == 0) + { + if (s_projectileFrames[type]) + { + frame_setData(projObj, s_projectileFrames[type]); + } + } + else if (strcasecmp(externalProjectiles[type].assetType, "sprite") == 0) + { + if (s_projectileWaxes[type]) + { + sprite_setData(projObj, s_projectileWaxes[type]); + obj_setSpriteAnim(projObj); // Setup the looping wax animation. + } + } + else if (strcasecmp(externalProjectiles[type].assetType, "3d") == 0) + { + if (s_projectileModels[type]) + { + obj3d_setData(projObj, s_projectileModels[type]); + } + } + else + { + spirit_setData(projObj); // default to spirit + } + + if (externalProjectiles[type].fullBright) + { + projObj->flags |= OBJ_FLAG_FULLBRIGHT; + } + + if (externalProjectiles[type].autoAim) + { + projObj->flags |= OBJ_FLAG_AIM; // this is only meaningful for homing missiles, which can be shot down + } + + if (externalProjectiles[type].movable) + { + projObj->flags |= OBJ_FLAG_MOVABLE; // thermal detonators & mines will be moved by elevators + } + + if (externalProjectiles[type].zeroWidth) + { + projObj->worldWidth = 0; + } + } + + // TFE: Set the Projectile logic from external data. These were hardcoded in vanilla DF. + void setProjectileLogic(ProjectileLogic*& projLogic, ProjectileType type, TFE_ExternalData::ExternalProjectile* externalProjectiles) + { + projLogic->type = type; + projLogic->updateFunc = getUpdateFunc(externalProjectiles[type].updateFunc); + projLogic->dmg = FIXED(externalProjectiles[type].damage); + projLogic->falloffAmt = externalProjectiles[type].falloffAmount; + projLogic->nextFalloffTick = s_curTick + externalProjectiles[type].nextFalloffTick; + projLogic->dmgFalloffDelta = externalProjectiles[type].damageFalloffDelta; + projLogic->minDmg = FIXED(externalProjectiles[type].minDamage); + + projLogic->projForce = externalProjectiles[type].force; + projLogic->speed = FIXED(externalProjectiles[type].speed); + projLogic->horzBounciness = externalProjectiles[type].horzBounciness; + projLogic->vertBounciness = externalProjectiles[type].vertBounciness; + projLogic->bounceCnt = externalProjectiles[type].bounceCount; + projLogic->reflVariation = externalProjectiles[type].reflectVariation; + projLogic->reflectEffectId = (HitEffectID)externalProjectiles[type].reflectEffectId; + projLogic->hitEffectId = (HitEffectID)externalProjectiles[type].hitEffectId;; + projLogic->duration = s_curTick + externalProjectiles[type].duration; + projLogic->homingAngleSpd = externalProjectiles[type].homingAngularSpeed; // Starting homing rate + + projLogic->cameraPassSnd = s_projectileCameraSnd[type]; + projLogic->reflectSnd = s_projectileReflectSnd[type]; + projLogic->flightSndSource = s_projectileFlightSnd[type]; + + if (externalProjectiles[type].explodeOnTimeout) + { + projLogic->flags |= PROJFLAG_EXPLODE; + } + } + Logic* createProjectile(ProjectileType type, RSector* sector, fixed16_16 x, fixed16_16 y, fixed16_16 z, SecObject* obj) { ProjectileLogic* projLogic = (ProjectileLogic*)allocator_newItem(s_projectiles); @@ -186,508 +314,112 @@ namespace TFE_DarkForces obj_addLogic(projObj, (Logic*)projLogic, LOGIC_PROJECTILE, s_projectileTask, projectileLogicCleanupFunc); + TFE_ExternalData::ExternalProjectile* externalProjectiles = TFE_ExternalData::getExternalProjectiles(); + switch (type) { case PROJ_PUNCH: { - spirit_setData(projObj); - - projLogic->type = PROJ_PUNCH; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(6); - projLogic->falloffAmt = 0; - projLogic->projForce = 1310; - projLogic->speed = FIXED(230); - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_NONE; - projLogic->duration = s_curTick; + setProjectileObject(projObj, PROJ_PUNCH, externalProjectiles); + setProjectileLogic(projLogic, PROJ_PUNCH, externalProjectiles); } break; case PROJ_PISTOL_BOLT: { - if (s_boltModel) - { - obj3d_setData(projObj, s_boltModel); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - - projLogic->type = PROJ_PISTOL_BOLT; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(10); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = ONE_16; - projLogic->dmgFalloffDelta = 14; - - projLogic->projForce = 655; - projLogic->speed = FIXED(250); - projLogic->horzBounciness = ONE_16; - projLogic->vertBounciness = ONE_16; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 14; // ~0.1 seconds - projLogic->reflectEffectId = HEFFECT_SMALL_EXP; - projLogic->hitEffectId = HEFFECT_SMALL_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds - projLogic->cameraPassSnd = s_stdProjCameraSnd; - projLogic->reflectSnd = s_stdProjReflectSnd; + setProjectileObject(projObj, PROJ_PISTOL_BOLT, externalProjectiles); + setProjectileLogic(projLogic, PROJ_PISTOL_BOLT, externalProjectiles); } break; case PROJ_RIFLE_BOLT: { - if (s_boltModel) - { - obj3d_setData(projObj, s_boltModel); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - - projLogic->type = PROJ_RIFLE_BOLT; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(10); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = 52428; // 0.8 damage - projLogic->dmgFalloffDelta = 14; - - projLogic->projForce = 655; - projLogic->speed = FIXED(250); - projLogic->horzBounciness = ONE_16; - projLogic->vertBounciness = ONE_16; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 14; // ~0.1 seconds - projLogic->reflectEffectId = HEFFECT_SMALL_EXP; - projLogic->hitEffectId = HEFFECT_SMALL_EXP; - projLogic->duration = s_curTick + 582; // ~4 seconds - projLogic->cameraPassSnd = s_stdProjCameraSnd; - projLogic->reflectSnd = s_stdProjReflectSnd; + setProjectileObject(projObj, PROJ_RIFLE_BOLT, externalProjectiles); + setProjectileLogic(projLogic, PROJ_RIFLE_BOLT, externalProjectiles); } break; case PROJ_THERMAL_DET: { - if (s_thermalDetProj) - { - frame_setData(projObj, s_thermalDetProj); - } - projObj->flags |= OBJ_FLAG_MOVABLE; - projObj->worldWidth = 0; - - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->type = PROJ_THERMAL_DET; - projLogic->updateFunc = arcingProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 6553; - projLogic->speed = FIXED(80); - projLogic->horzBounciness = 29491; // 0.45 - projLogic->vertBounciness = 58327; // 0.89 - projLogic->bounceCnt = -1; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_THERMDET_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds - projLogic->cameraPassSnd = NULL_SOUND; - projLogic->reflectSnd = s_thermalDetReflectSnd; + setProjectileObject(projObj, PROJ_THERMAL_DET, externalProjectiles); + setProjectileLogic(projLogic, PROJ_THERMAL_DET, externalProjectiles); } break; case PROJ_REPEATER: { - if (s_repeaterProj) - { - frame_setData(projObj, s_repeaterProj); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - - projLogic->type = PROJ_REPEATER; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(10); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = 19660; // 0.3 damage - projLogic->dmgFalloffDelta = 19660; // 135 seconds, this probably should have been 0.3 seconds, or 43 - - projLogic->projForce = 1966; - projLogic->speed = FIXED(270); - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 19660; // ~135 seconds, should have been ~0.3 seconds. - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_REPEATER_EXP; - projLogic->duration = s_curTick + 582; // ~4 seconds - projLogic->cameraPassSnd = s_stdProjCameraSnd; - projLogic->reflectSnd = s_stdProjReflectSnd; + setProjectileObject(projObj, PROJ_REPEATER, externalProjectiles); + setProjectileLogic(projLogic, PROJ_REPEATER, externalProjectiles); + + // dmgFalloffDelta was 19660 in the code; this probably should have been 0.3 seconds, or 43 ticks } break; case PROJ_PLASMA: { - if (s_plasmaProj) - { - sprite_setData(projObj, s_plasmaProj); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - - projLogic->type = PROJ_PLASMA; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(15); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = 39321; // 0.6 damage - projLogic->dmgFalloffDelta = 29; // 0.2 seconds - - projLogic->projForce = 3276; - projLogic->speed = FIXED(100); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 29; // ~0.2 second - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_PLASMA_EXP; - projLogic->duration = s_curTick + 1456; // ~10 seconds - projLogic->cameraPassSnd = s_plasmaCameraSnd; - projLogic->reflectSnd = s_plasmaReflectSnd; + setProjectileObject(projObj, PROJ_PLASMA, externalProjectiles); + setProjectileLogic(projLogic, PROJ_PLASMA, externalProjectiles); } break; case PROJ_MORTAR: { - if (s_mortarProj) - { - sprite_setData(projObj, s_mortarProj); - } - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - projObj->worldWidth = 0; - - projLogic->type = PROJ_MORTAR; - projLogic->updateFunc = arcingProjectileUpdateFunc; - projLogic->dmg = 0; // Damage is set to 0 for some reason. - projLogic->falloffAmt = 0; // No falloff - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 13107; - projLogic->speed = FIXED(110); - projLogic->horzBounciness = 26214; // 0.4 - projLogic->vertBounciness = 39321; // 0.6 - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_MORTAR_EXP; - projLogic->duration = s_curTick + 582; // ~4 seconds -> ~440 units + setProjectileObject(projObj, PROJ_MORTAR, externalProjectiles); + setProjectileLogic(projLogic, PROJ_MORTAR, externalProjectiles); } break; case PROJ_LAND_MINE: { - if (s_landmineWpnFrame) - { - frame_setData(projObj, s_landmineWpnFrame); - } + setProjectileObject(projObj, PROJ_LAND_MINE, externalProjectiles); projObj->entityFlags |= ETFLAG_LANDMINE_WPN; - projObj->flags |= OBJ_FLAG_MOVABLE; - projObj->worldWidth = 0; - - projLogic->type = PROJ_LAND_MINE; - projLogic->updateFunc = arcingProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 13107; - projLogic->speed = 0; - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = -1; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->hitEffectId = HEFFECT_LARGE_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds + setProjectileLogic(projLogic, PROJ_LAND_MINE, externalProjectiles); } break; case PROJ_LAND_MINE_PROX: { - if (s_landmineWpnFrame) - { - frame_setData(projObj, s_landmineWpnFrame); - } + setProjectileObject(projObj, PROJ_LAND_MINE_PROX, externalProjectiles); projObj->entityFlags |= ETFLAG_LANDMINE_WPN; - projObj->flags |= OBJ_FLAG_MOVABLE; - projObj->worldWidth = 0; - - projLogic->type = PROJ_LAND_MINE_PROX; - projLogic->updateFunc = arcingProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 13107; - projLogic->speed = 0; - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = -1; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->hitEffectId = HEFFECT_LARGE_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds + setProjectileLogic(projLogic, PROJ_LAND_MINE_PROX, externalProjectiles); } break; case PROJ_LAND_MINE_PLACED: { - if (s_landmineFrame) - { - frame_setData(projObj, s_landmineFrame); - } - projLogic->type = PROJ_LAND_MINE_PLACED; - projObj->flags |= OBJ_FLAG_MOVABLE; - projObj->worldWidth = 0; - - projLogic->updateFunc = landMineUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - projLogic->projForce = 13107; - projLogic->speed = 0; - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = -1; - projLogic->duration = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->hitEffectId = HEFFECT_LARGE_EXP; + setProjectileObject(projObj, PROJ_LAND_MINE_PLACED, externalProjectiles); + setProjectileLogic(projLogic, PROJ_LAND_MINE_PLACED, externalProjectiles); } break; case PROJ_CONCUSSION: { - spirit_setData(projObj); - projObj->worldWidth = 0; - - projLogic->type = PROJ_CONCUSSION; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 3932; - projLogic->speed = FIXED(190); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_CONCUSSION; - projLogic->duration = s_curTick + 728; // ~5 seconds + setProjectileObject(projObj, PROJ_CONCUSSION, externalProjectiles); + setProjectileLogic(projLogic, PROJ_CONCUSSION, externalProjectiles); } break; case PROJ_CANNON: { - if (s_cannonProj) - { - sprite_setData(projObj, s_cannonProj); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - - projLogic->type = PROJ_CANNON; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(30); - projLogic->falloffAmt = 0; // No falloff - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 4032; - projLogic->speed = FIXED(100); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 18; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_CANNON_EXP; - projLogic->duration = s_curTick + 582; // ~4 seconds -> ~400 units - projLogic->cameraPassSnd = s_plasmaCameraSnd; - projLogic->reflectSnd = s_plasmaReflectSnd; + setProjectileObject(projObj, PROJ_CANNON, externalProjectiles); + setProjectileLogic(projLogic, PROJ_CANNON, externalProjectiles); } break; case PROJ_MISSILE: { - if (s_missileProj) - { - sprite_setData(projObj, s_missileProj); - } - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - projObj->worldWidth = 0; - - projLogic->type = PROJ_MISSILE; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = 0; // Damage is set to 0 for some reason. - projLogic->falloffAmt = 0; // No falloff - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = ONE_16; - projLogic->speed = FIXED(74); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_MISSILE_EXP; - projLogic->duration = s_curTick + 1019; // ~7 seconds -> ~518 units - projLogic->flightSndSource = s_missileLoopingSnd; + setProjectileObject(projObj, PROJ_MISSILE, externalProjectiles); + setProjectileLogic(projLogic, PROJ_MISSILE, externalProjectiles); } break; case PROJ_TURRET_BOLT: { - if (s_greenBoltModel) - { - obj3d_setData(projObj, s_greenBoltModel); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - - projLogic->type = PROJ_TURRET_BOLT; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(17); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = ONE_16; - projLogic->dmgFalloffDelta = 14; - - projLogic->projForce = 1966; - projLogic->speed = FIXED(300); - projLogic->horzBounciness = ONE_16; - projLogic->vertBounciness = ONE_16; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 14; // ~0.1 seconds - projLogic->reflectEffectId = HEFFECT_SMALL_EXP; - projLogic->hitEffectId = HEFFECT_SMALL_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds - projLogic->cameraPassSnd = s_stdProjCameraSnd; - projLogic->reflectSnd = s_stdProjReflectSnd; + setProjectileObject(projObj, PROJ_TURRET_BOLT, externalProjectiles); + setProjectileLogic(projLogic, PROJ_TURRET_BOLT, externalProjectiles); } break; case PROJ_REMOTE_BOLT: { - if (s_greenBoltModel) - { - obj3d_setData(projObj, s_greenBoltModel); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - - projLogic->type = PROJ_REMOTE_BOLT; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = ONE_16; - projLogic->minDmg = 0; - projLogic->falloffAmt = ONE_16; - projLogic->dmgFalloffDelta = 14; - - projLogic->projForce = 655; - projLogic->speed = FIXED(300); - projLogic->horzBounciness = ONE_16; - projLogic->vertBounciness = ONE_16; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 14; // ~0.1 seconds - projLogic->reflectEffectId = HEFFECT_SMALL_EXP; - projLogic->hitEffectId = HEFFECT_SMALL_EXP; - projLogic->duration = s_curTick + 436; // ~3 seconds - projLogic->cameraPassSnd = s_stdProjCameraSnd; - projLogic->reflectSnd = s_stdProjReflectSnd; + setProjectileObject(projObj, PROJ_REMOTE_BOLT, externalProjectiles); + setProjectileLogic(projLogic, PROJ_REMOTE_BOLT, externalProjectiles); } break; case PROJ_EXP_BARREL: { - spirit_setData(projObj); - - projLogic->type = PROJ_EXP_BARREL; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = 0; - - projLogic->projForce = 0; - projLogic->speed = 0; - projLogic->horzBounciness = 0; - projLogic->vertBounciness = 0; - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->hitEffectId = HEFFECT_EXP_BARREL; - projLogic->duration = s_curTick; + setProjectileObject(projObj, PROJ_EXP_BARREL, externalProjectiles); + setProjectileLogic(projLogic, PROJ_EXP_BARREL, externalProjectiles); } break; case PROJ_HOMING_MISSILE: { - if (s_homingMissileProj) - { - sprite_setData(projObj, s_homingMissileProj); - } - obj_setSpriteAnim(projObj); - projObj->flags |= OBJ_FLAG_AIM; - - projLogic->flags |= PROJFLAG_EXPLODE; - projLogic->type = PROJ_HOMING_MISSILE; - projLogic->updateFunc = homingMissileProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = 0; - projLogic->dmgFalloffDelta = ONE_16; - - projLogic->projForce = ONE_16; - projLogic->speed = FIXED(58) / 2; // try slowing them down...; the value is correct according to the code, but they are slower in DOS. - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 0; - projLogic->homingAngleSpd = 455; // Starting homing rate = 10 degrees / second - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->flightSndSource = s_homingMissileFlightSnd; - projLogic->hitEffectId = HEFFECT_MISSILE_EXP; - projLogic->duration = s_curTick + 1456; - projLogic->reflectSnd = 0; + setProjectileObject(projObj, PROJ_HOMING_MISSILE, externalProjectiles); + setProjectileLogic(projLogic, PROJ_HOMING_MISSILE, externalProjectiles); + + // projLogic->speed = FIXED(58) / 2; + // speed will be set to 29 externally; the value is correct according to the code, but they are slower in DOS. + } break; case PROJ_PROBE_PROJ: { - if (s_probeProj) - { - sprite_setData(projObj, s_probeProj); - } - projObj->flags |= OBJ_FLAG_FULLBRIGHT; - projObj->worldWidth = 0; - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - - projLogic->type = PROJ_PROBE_PROJ; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = FIXED(10); - projLogic->minDmg = ONE_16; - projLogic->falloffAmt = ONE_16; // 1.0 damage - projLogic->dmgFalloffDelta = 14; - - projLogic->projForce = 2621; - projLogic->speed = FIXED(100); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 3; - projLogic->reflVariation = 9; - projLogic->nextFalloffTick = s_curTick + 14; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_PLASMA_EXP; - projLogic->duration = s_curTick + 1165; // ~8 seconds - projLogic->cameraPassSnd = s_plasmaCameraSnd; - projLogic->reflectSnd = s_plasmaReflectSnd; + setProjectileObject(projObj, PROJ_PROBE_PROJ, externalProjectiles); + setProjectileLogic(projLogic, PROJ_PROBE_PROJ, externalProjectiles); } break; case PROJ_BOBAFET_BALL: { - if (s_bobafetBall) - { - sprite_setData(projObj, s_bobafetBall); - } - // Setup the looping wax animation. - obj_setSpriteAnim(projObj); - projObj->worldWidth = 0; - - projLogic->type = PROJ_BOBAFET_BALL; - projLogic->updateFunc = stdProjectileUpdateFunc; - projLogic->dmg = 0; - projLogic->falloffAmt = 0; - - projLogic->projForce = ONE_16; - projLogic->speed = FIXED(90); - projLogic->horzBounciness = 58982; // 0.9 - projLogic->vertBounciness = 58982; - projLogic->bounceCnt = 0; - projLogic->reflectEffectId = HEFFECT_NONE; - projLogic->hitEffectId = HEFFECT_EXP_25; - projLogic->duration = s_curTick + 1456; - projLogic->cameraPassSnd = s_bobaBallCameraSnd; + setProjectileObject(projObj, PROJ_BOBAFET_BALL, externalProjectiles); + setProjectileLogic(projLogic, PROJ_BOBAFET_BALL, externalProjectiles); } break; } projLogic->col_speed = projLogic->speed; @@ -858,6 +590,31 @@ namespace TFE_DarkForces // Bug: allocator_release() is never called } + ProjectileFunc getUpdateFunc(const char* type) + { + if (strcasecmp(type, "standard") == 0) + { + return stdProjectileUpdateFunc; + } + + if (strcasecmp(type, "arcing") == 0) + { + return arcingProjectileUpdateFunc; + } + + if (strcasecmp(type, "landmine") == 0) + { + return landMineUpdateFunc; + } + + if (strcasecmp(type, "homing") == 0) + { + return homingMissileProjectileUpdateFunc; + } + + return stdProjectileUpdateFunc; + } + // The "standard" update function - projectiles travel in a straight line with a fixed velocity. ProjectileHitType stdProjectileUpdateFunc(ProjectileLogic* projLogic) { diff --git a/TheForceEngine/TFE_DarkForces/projectile.h b/TheForceEngine/TFE_DarkForces/projectile.h index 3b7e1aa33..f2a8ce429 100644 --- a/TheForceEngine/TFE_DarkForces/projectile.h +++ b/TheForceEngine/TFE_DarkForces/projectile.h @@ -62,7 +62,7 @@ namespace TFE_DarkForces enum ProjectileFlags { PROJFLAG_CAMERA_PASS_SOUND = FLAG_BIT(0), - PROJFLAG_EXPLODE = FLAG_BIT(1), + PROJFLAG_EXPLODE = FLAG_BIT(1), // Explodes when reaches end of life. }; struct ProjectileLogic; @@ -90,10 +90,10 @@ namespace TFE_DarkForces fixed16_16 vertBounciness; SecObject* prevColObj; SecObject* prevObj; - SecObject* excludeObj; + SecObject* excludeObj; // Object which is excluded from damage (used to prevent AI from being hurt by their own weapons) Tick duration; // How long the projectile continues to move before going out of range (and being destroyed). angle14_32 homingAngleSpd; // How quickly a homing projectile lines up in angle units / second. - s32 bounceCnt; + s32 bounceCnt; // Number of remaining bounces / reflections. Used for magseal (positive numbers) and thermal detonator secondary fire (set to -1) s32 reflVariation; SoundEffectId flightSndId; // Looping sound instance played while the projectile moves. SoundSourceId flightSndSource; // Source looping sound. diff --git a/TheForceEngine/TFE_ExternalData/logicTables.cpp b/TheForceEngine/TFE_ExternalData/logicTables.cpp index ee64fbdf4..317c84caf 100644 --- a/TheForceEngine/TFE_ExternalData/logicTables.cpp +++ b/TheForceEngine/TFE_ExternalData/logicTables.cpp @@ -72,6 +72,7 @@ namespace TFE_ExternalData "LIFE", // 42 "MEDKIT", // 43 "PILE", // 44 + "ITEM10", // 45 - added for s_playerInfo.itemUnused }; const char* df_effectTable[] = diff --git a/TheForceEngine/TFE_ExternalData/weaponExternal.cpp b/TheForceEngine/TFE_ExternalData/weaponExternal.cpp new file mode 100644 index 000000000..2b396cd73 --- /dev/null +++ b/TheForceEngine/TFE_ExternalData/weaponExternal.cpp @@ -0,0 +1,526 @@ +#include +#include +#include +#include +#include +#include +#include "weaponExternal.h" +#include "logicTables.h" + +namespace TFE_ExternalData +{ + static ExternalProjectile s_externalProjectiles[TFE_DarkForces::PROJ_COUNT]; + static ExternalEffect s_externalEffects[HEFFECT_COUNT]; + + static bool s_externalProjectilesFromMod = false; + static bool s_externalEffectsFromMod = false; + + ////////////////////////////// + // Forward Declarations + ////////////////////////////// + int getProjectileIndex(char* type); + bool tryAssignProjectileProperty(cJSON* data, ExternalProjectile& projectile); + int getEffectIndex(char* type); + bool tryAssignEffectProperty(cJSON* data, ExternalEffect& effect); + + + ExternalProjectile* getExternalProjectiles() + { + return s_externalProjectiles; + } + + ExternalEffect* getExternalEffects() + { + return s_externalEffects; + } + + void clearExternalProjectiles() + { + s_externalProjectilesFromMod = false; + for (int i = 0; i < TFE_DarkForces::PROJ_COUNT; i++) + { + s_externalProjectiles[i].type = nullptr; + } + } + + void clearExternalEffects() + { + s_externalEffectsFromMod = false; + for (int i = 0; i < HEFFECT_COUNT; i++) + { + s_externalEffects[i].type = nullptr; + } + } + + void loadExternalProjectiles() + { + const char* programDir = TFE_Paths::getPath(PATH_PROGRAM); + char extDataFile[TFE_MAX_PATH]; + sprintf(extDataFile, "%sExternalData\\projectiles.json", programDir); + + TFE_System::logWrite(LOG_MSG, "EXTERNAL_DATA", "Loading projectile data"); + FileStream file; + if (!file.open(extDataFile, FileStream::MODE_READ)) { return; } + + const size_t size = file.getSize(); + char* data = (char*)malloc(size + 1); + if (!data || size == 0) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Projectiles.json is %u bytes in size and cannot be read.", size); + return; + } + file.readBuffer(data, (u32)size); + data[size] = 0; + file.close(); + + parseExternalProjectiles(data, false); + free(data); + } + + void parseExternalProjectiles(char* data, bool fromMod) + { + // If projectiles have already been loaded from a mod, don't replace them + if (s_externalProjectilesFromMod) + { + return; + } + + cJSON* root = cJSON_Parse(data); + if (root) + { + cJSON* section = root->child; + if (section && cJSON_IsArray(section) && strcasecmp(section->string, "projectiles") == 0) + { + cJSON* projectile = section->child; + while (projectile) + { + cJSON* projectileType = projectile->child; + + // get the projectile type + if (projectileType && cJSON_IsString(projectileType) && strcasecmp(projectileType->string, "type") == 0) + { + // For now stick with the hardcoded DF list + int index = getProjectileIndex(projectileType->valuestring); + + if (index >= 0) + { + ExternalProjectile extProjectile = {}; + extProjectile.type = projectileType->valuestring; + + cJSON* projectileData = projectileType->next; + if (projectileData && cJSON_IsObject(projectileData)) + { + cJSON* dataItem = projectileData->child; + + // iterate through the data and assign properties + while (dataItem) + { + tryAssignProjectileProperty(dataItem, extProjectile); + dataItem = dataItem->next; + } + } + + s_externalProjectiles[index] = extProjectile; + } + } + + projectile = projectile->next; + } + } + + s_externalProjectilesFromMod = fromMod && validateExternalProjectiles(); + } + else + { + const char* error = cJSON_GetErrorPtr(); + if (error) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Failed to parse json before\n%s", error); + } + else + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Failed to parse json"); + } + } + } + + void loadExternalEffects() + { + const char* programDir = TFE_Paths::getPath(PATH_PROGRAM); + char extDataFile[TFE_MAX_PATH]; + sprintf(extDataFile, "%sExternalData\\effects.json", programDir); + + TFE_System::logWrite(LOG_MSG, "EXTERNAL_DATA", "Loading effects data"); + FileStream file; + if (!file.open(extDataFile, FileStream::MODE_READ)) { return; } + + const size_t size = file.getSize(); + char* data = (char*)malloc(size + 1); + if (!data || size == 0) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Effects.json is %u bytes in size and cannot be read.", size); + return; + } + file.readBuffer(data, (u32)size); + data[size] = 0; + file.close(); + + parseExternalEffects(data, false); + free(data); + } + + void parseExternalEffects(char* data, bool fromMod) + { + // If effects have already been loaded from a mod, don't replace them + if (s_externalEffectsFromMod) + { + return; + } + + cJSON* root = cJSON_Parse(data); + if (root) + { + cJSON* section = root->child; + if (section && cJSON_IsArray(section) && strcasecmp(section->string, "effects") == 0) + { + cJSON* effect = section->child; + while (effect) + { + cJSON* effectType = effect->child; + + // get the effect type + if (effectType && cJSON_IsString(effectType) && strcasecmp(effectType->string, "type") == 0) + { + // For now stick with the hardcoded DF list + int index = getEffectIndex(effectType->valuestring); + + if (index >= 0) + { + ExternalEffect extEffect = {}; + extEffect.type = effectType->valuestring; + + cJSON* effectData = effectType->next; + if (effectData && cJSON_IsObject(effectData)) + { + cJSON* dataItem = effectData->child; + + // iterate through the data and assign properties + while (dataItem) + { + tryAssignEffectProperty(dataItem, extEffect); + dataItem = dataItem->next; + } + } + + s_externalEffects[index] = extEffect; + } + } + + effect = effect->next; + } + } + + s_externalEffectsFromMod = fromMod && validateExternalEffects(); + } + else + { + const char* error = cJSON_GetErrorPtr(); + if (error) + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Failed to parse json before\n%s", error); + } + else + { + TFE_System::logWrite(LOG_ERROR, "EXTERNAL_DATA", "Failed to parse json"); + } + } + } + + bool validateExternalProjectiles() + { + // If the type field is null, we can assume the projectile's data hasn't loaded properly + for (int i = 0; i < TFE_DarkForces::PROJ_COUNT; i++) + { + if (!s_externalProjectiles[i].type) + { + return false; + } + } + + return true; + } + + bool validateExternalEffects() + { + // If the type field is null, we can assume the effect's data hasn't loaded properly + for (int i = 0; i < HEFFECT_COUNT; i++) + { + if (!s_externalEffects[i].type) + { + return false; + } + } + + return true; + } + + int getProjectileIndex(char* type) + { + for (int i = 0; i < TFE_DarkForces::PROJ_COUNT; i++) + { + if (strcasecmp(type, TFE_ExternalData::df_projectileTable[i]) == 0) + { + return i; + } + } + + return -1; + } + + int getEffectIndex(char* type) + { + for (int i = 0; i < HEFFECT_COUNT; i++) + { + if (strcasecmp(type, TFE_ExternalData::df_effectTable[i]) == 0) + { + return i; + } + } + + return -1; + } + + bool tryAssignProjectileProperty(cJSON* data, ExternalProjectile &projectile) + { + if (!data) + { + return false; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "assetType") == 0) + { + projectile.assetType = data->valuestring; + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "asset") == 0) + { + projectile.asset = data->valuestring; + return true; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "fullBright") == 0) + { + projectile.fullBright = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "zeroWidth") == 0) + { + projectile.zeroWidth = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "autoAim") == 0) + { + projectile.autoAim = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "movable") == 0) + { + projectile.movable = cJSON_IsTrue(data); + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "updateFunc") == 0) + { + projectile.updateFunc = data->valuestring; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "damage") == 0) + { + projectile.damage = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "falloffAmount") == 0) + { + projectile.falloffAmount = u32(data->valuedouble * ONE_16); + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "nextFalloffTick") == 0) + { + projectile.nextFalloffTick = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "damageFalloffDelta") == 0) + { + projectile.damageFalloffDelta = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "minDamage") == 0) + { + projectile.minDamage = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "force") == 0) + { + projectile.force = u32(data->valuedouble * ONE_16); + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "speed") == 0) + { + projectile.speed = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "horzBounciness") == 0) + { + projectile.horzBounciness = u32(data->valuedouble * ONE_16); + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "vertBounciness") == 0) + { + projectile.vertBounciness = u32(data->valuedouble * ONE_16); + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "bounceCount") == 0) + { + projectile.bounceCount = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "reflectVariation") == 0) + { + projectile.reflectVariation = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "duration") == 0) + { + projectile.duration = u32(data->valuedouble * 145.65); // 145.65 ticks per second + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "angularSpeed") == 0) + { + projectile.homingAngularSpeed = floatToAngle(data->valueint); + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "flightSound") == 0) + { + projectile.flightSound = data->valuestring; + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "reflectSound") == 0) + { + projectile.reflectSound = data->valuestring; + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "cameraPassSound") == 0) + { + projectile.cameraPassSound = data->valuestring; + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "reflectEffect") == 0) + { + for (int i = 0; i < HEFFECT_COUNT; i++) + { + if (strcasecmp(data->valuestring, TFE_ExternalData::df_effectTable[i]) == 0) + { + projectile.reflectEffectId = i; + return true; + } + } + + return false; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "hitEffect") == 0) + { + for (int i = 0; i < HEFFECT_COUNT; i++) + { + if (strcasecmp(data->valuestring, TFE_ExternalData::df_effectTable[i]) == 0) + { + projectile.hitEffectId = i; + return true; + } + } + + return false; + } + + if (cJSON_IsBool(data) && strcasecmp(data->string, "explodeOnTimeout") == 0) + { + projectile.explodeOnTimeout = cJSON_IsTrue(data); + return true; + } + + return false; + } + + bool tryAssignEffectProperty(cJSON* data, ExternalEffect& effect) + { + if (!data) + { + return false; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "wax") == 0) + { + effect.wax = data->valuestring; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "force") == 0) + { + effect.force = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "damage") == 0) + { + effect.damage = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "explosiveRange") == 0) + { + effect.explosiveRange = data->valueint; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "wakeupRange") == 0) + { + effect.wakeupRange = data->valueint; + return true; + } + + if (cJSON_IsString(data) && strcasecmp(data->string, "soundEffect") == 0) + { + effect.soundEffect = data->valuestring; + return true; + } + + if (cJSON_IsNumber(data) && strcasecmp(data->string, "soundPriority") == 0) + { + effect.soundPriority = data->valueint; + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/TheForceEngine/TFE_ExternalData/weaponExternal.h b/TheForceEngine/TFE_ExternalData/weaponExternal.h new file mode 100644 index 000000000..1232761fd --- /dev/null +++ b/TheForceEngine/TFE_ExternalData/weaponExternal.h @@ -0,0 +1,67 @@ +#pragma once +#include + +/////////////////////////////////////////// +// TFE Externalised Weapon data +/////////////////////////////////////////// + +namespace TFE_ExternalData +{ + struct ExternalProjectile + { + const char* type = nullptr; + + // Projectile object + const char* assetType = "spirit"; + const char* asset = ""; + bool fullBright = false; + bool zeroWidth = false; + bool autoAim = false; + bool movable = false; + + // Projectile logic + const char* updateFunc = ""; + u32 damage; + u32 falloffAmount; + u32 nextFalloffTick; + u32 damageFalloffDelta; + u32 minDamage; + u32 force; + u32 speed; + u32 horzBounciness; + u32 vertBounciness; + s32 bounceCount; + u32 reflectVariation; + u32 duration; + s32 homingAngularSpeed; + const char* flightSound = ""; + const char* reflectSound = ""; + const char* cameraPassSound = ""; + s32 reflectEffectId = -1; + s32 hitEffectId = -1; + bool explodeOnTimeout = false; + }; + + struct ExternalEffect + { + const char* type = nullptr; + const char* wax = ""; + s32 force; + s32 damage; + s32 explosiveRange; + s32 wakeupRange; + const char* soundEffect = ""; + s32 soundPriority; + }; + + ExternalProjectile* getExternalProjectiles(); + ExternalEffect* getExternalEffects(); + void clearExternalProjectiles(); + void clearExternalEffects(); + void loadExternalProjectiles(); + void parseExternalProjectiles(char* data, bool fromMod); + bool validateExternalProjectiles(); + void loadExternalEffects(); + void parseExternalEffects(char* data, bool fromMod); + bool validateExternalEffects(); +} \ No newline at end of file diff --git a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp index 855b9c3e8..56eb0c16f 100644 --- a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp +++ b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp @@ -28,6 +28,7 @@ #include #include #include +#include // Game #include #include @@ -508,6 +509,8 @@ namespace TFE_FrontEndUI s_relativeMode = false; TFE_Input::enableRelativeMode(s_relativeMode); TFE_ExternalData::getExternalLogics()->actorLogics.clear(); // clear custom logics + TFE_ExternalData::clearExternalProjectiles(); // clear projectiles + TFE_ExternalData::clearExternalEffects(); // clear effects if (TFE_Settings::getSystemSettings()->returnToModLoader && s_modLoaded) { diff --git a/TheForceEngine/TheForceEngine.vcxproj b/TheForceEngine/TheForceEngine.vcxproj index a5cecff2d..4f9a7b517 100644 --- a/TheForceEngine/TheForceEngine.vcxproj +++ b/TheForceEngine/TheForceEngine.vcxproj @@ -698,6 +698,7 @@ echo ^)"; + @@ -1054,6 +1055,7 @@ echo ^)"; + diff --git a/TheForceEngine/TheForceEngine.vcxproj.filters b/TheForceEngine/TheForceEngine.vcxproj.filters index ff56aa06d..397e4cd31 100644 --- a/TheForceEngine/TheForceEngine.vcxproj.filters +++ b/TheForceEngine/TheForceEngine.vcxproj.filters @@ -1408,6 +1408,9 @@ Source\TFE_ExternalData + + Source\TFE_ExternalData + @@ -2454,6 +2457,9 @@ Source\TFE_ExternalData + + Source\TFE_ExternalData + @@ -2667,4 +2673,4 @@ Source\TFE_ForceScript\Angelscript\angelscript\source - \ No newline at end of file +