diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8a64526..9eec9fdfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - added Italian localization to the config tool - added the ability to move the look camera while targeting an enemy in combat (#1187) - added the ability to skip fade-out in stats screens +- added support for animated room sprites in custom levels and an option to animate plant sprites in The Cistern and Tomb of Tihocan (#449) - changed stats no longer disappear during fade-out (#1211) - changed the way music timestamps are internally handled – resets music position in existing saves - changed vertex and fragment shaders into unified files that are runtime pre-processed for OpenGL versions 2.1 or 3.3 diff --git a/GAMEFLOW.md b/GAMEFLOW.md index 13c082d48..4ef0453a8 100644 --- a/GAMEFLOW.md +++ b/GAMEFLOW.md @@ -1371,6 +1371,15 @@ provided with the game achieves. braid_cut2_cut4.bin + + + cistern_plants.bin + + + This disables the animation on sprite ID 193 in The Cistern and Tomb of + Tihocan. + + khamoon_mummy.bin diff --git a/README.md b/README.md index aef833f9f..616c5b9e5 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det - added optional fade effects - added a vsync option - added contextual arrows to menu options +- added support for animated room sprites, which also restores intended behavior in, for example, The Cistern room 0 - changed the Scion in The Great Pyramid from spawning blood when hit to a richochet effect - fixed thin black lines between polygons - fixed black screen flashing when navigating the inventory diff --git a/data/ship/cfg/TR1X_gameflow.json5 b/data/ship/cfg/TR1X_gameflow.json5 index 1a51fcde3..48d9369b9 100644 --- a/data/ship/cfg/TR1X_gameflow.json5 +++ b/data/ship/cfg/TR1X_gameflow.json5 @@ -229,6 +229,7 @@ "injections": [ "data/cistern_fd.bin", "data/cistern_itemrots.bin", + "data/cistern_plants.bin", "data/cistern_textures.bin", ], "sequence": [ @@ -253,6 +254,7 @@ "type": "normal", "music": 58, "injections": [ + "data/cistern_plants.bin", "data/tihocan_fd.bin", "data/tihocan_itemrots.bin", "data/tihocan_textures.bin", diff --git a/data/ship/data/cistern_plants.bin b/data/ship/data/cistern_plants.bin new file mode 100644 index 000000000..1cb6b7d40 Binary files /dev/null and b/data/ship/data/cistern_plants.bin differ diff --git a/src/config.h b/src/config.h index 426d7a87c..1f76fc555 100644 --- a/src/config.h +++ b/src/config.h @@ -136,6 +136,7 @@ typedef struct { bool enable_target_change; TARGET_LOCK_MODE target_mode; bool enable_loading_screens; + bool fix_animated_sprites; struct { int32_t layout; diff --git a/src/config_map.c b/src/config_map.c index 3fe35288e..de342ef02 100644 --- a/src/config_map.c +++ b/src/config_map.c @@ -158,6 +158,7 @@ const CONFIG_OPTION g_ConfigOptionMap[] = { { .name = "text_scale", .type = COT_DOUBLE, .target = &g_Config.ui.text_scale, .default_value = &(double){DEFAULT_UI_SCALE}, 0}, { .name = "bar_scale", .type = COT_DOUBLE, .target = &g_Config.ui.bar_scale, .default_value = &(double){DEFAULT_UI_SCALE}, 0}, { .name = "new_game_plus_unlock", .type = COT_BOOL, .target = &g_Config.profile.new_game_plus_unlock, .default_value = &(bool){false}, 0}, + { .name = "fix_animated_sprites", .type = COT_BOOL, .target = &g_Config.fix_animated_sprites, .default_value = &(bool){true}, 0}, // clang-format on // guard diff --git a/src/game/inject.c b/src/game/inject.c index 7892d10ce..0e28571bd 100644 --- a/src/game/inject.c +++ b/src/game/inject.c @@ -37,6 +37,7 @@ typedef enum INJECTION_TYPE { INJ_LARA_JUMPS = 6, INJ_ITEM_POSITION = 7, INJ_PS1_ENEMY = 8, + INJ_DISABLE_ANIM_SPRITE = 9, } INJECTION_TYPE; typedef struct INJECTION { @@ -223,6 +224,9 @@ static void Inject_LoadFromFile(INJECTION *injection, const char *filename) case INJ_PS1_ENEMY: injection->relevant = g_Config.restore_ps1_enemies; break; + case INJ_DISABLE_ANIM_SPRITE: + injection->relevant = !g_Config.fix_animated_sprites; + break; default: LOG_WARNING("%s is of unknown type %d", filename, injection->type); break; @@ -487,24 +491,24 @@ static void Inject_TextureData( for (int i = 0; i < inj_info->sprite_count; i++) { GAME_OBJECT_ID object_num; + int16_t num_meshes; + int16_t mesh_index; File_Read(&object_num, sizeof(int32_t), 1, fp); + File_Read(&num_meshes, sizeof(int16_t), 1, fp); + File_Read(&mesh_index, sizeof(int16_t), 1, fp); + if (object_num < O_NUMBER_OF) { - File_Read(&g_Objects[object_num], sizeof(int16_t), 1, fp); - File_Read( - &g_Objects[object_num].mesh_index, sizeof(int16_t), 1, fp); - g_Objects[object_num].mesh_index += level_info->sprite_info_count; - level_info->sprite_info_count -= g_Objects[object_num].nmeshes; - g_Objects[object_num].loaded = 1; - } else { - int32_t static_num = object_num - O_NUMBER_OF; - File_Skip(fp, 2); - File_Read( - &g_StaticObjects[static_num].mesh_number, sizeof(int16_t), 1, - fp); - g_StaticObjects[static_num].mesh_number += - level_info->sprite_info_count; - level_info->sprite_info_count++; + OBJECT_INFO *object = &g_Objects[object_num]; + object->nmeshes = num_meshes; + object->mesh_index = mesh_index + level_info->sprite_info_count; + object->loaded = 1; + } else if (object_num - O_NUMBER_OF < STATIC_NUMBER_OF) { + STATIC_INFO *object = &g_StaticObjects[object_num - O_NUMBER_OF]; + object->nmeshes = num_meshes; + object->mesh_number = mesh_index + level_info->sprite_info_count; + object->loaded = true; } + level_info->sprite_info_count -= num_meshes; level_info->sprite_count++; } } diff --git a/src/game/level.c b/src/game/level.c index 74b37c8db..2aabc97b1 100644 --- a/src/game/level.c +++ b/src/game/level.c @@ -484,6 +484,7 @@ static bool Level_LoadObjects(MYFILE *fp) File_Read(&object->c.min.z, sizeof(int16_t), 1, fp); File_Read(&object->c.max.z, sizeof(int16_t), 1, fp); File_Read(&object->flags, sizeof(int16_t), 1, fp); + object->loaded = true; } File_Read(&m_LevelInfo.texture_count, sizeof(int32_t), 1, fp); @@ -528,18 +529,22 @@ static bool Level_LoadSprites(MYFILE *fp) File_Read(&m_LevelInfo.sprite_count, sizeof(int32_t), 1, fp); for (int i = 0; i < m_LevelInfo.sprite_count; i++) { GAME_OBJECT_ID object_num; + int16_t num_meshes; + int16_t mesh_index; File_Read(&object_num, sizeof(int32_t), 1, fp); + File_Read(&num_meshes, sizeof(int16_t), 1, fp); + File_Read(&mesh_index, sizeof(int16_t), 1, fp); + if (object_num < O_NUMBER_OF) { - File_Read(&g_Objects[object_num], sizeof(int16_t), 1, fp); - File_Read( - &g_Objects[object_num].mesh_index, sizeof(int16_t), 1, fp); - g_Objects[object_num].loaded = 1; - } else { - int32_t static_num = object_num - O_NUMBER_OF; - File_Skip(fp, 2); - File_Read( - &g_StaticObjects[static_num].mesh_number, sizeof(int16_t), 1, - fp); + OBJECT_INFO *object = &g_Objects[object_num]; + object->nmeshes = num_meshes; + object->mesh_index = mesh_index; + object->loaded = 1; + } else if (object_num - O_NUMBER_OF < STATIC_NUMBER_OF) { + STATIC_INFO *object = &g_StaticObjects[object_num - O_NUMBER_OF]; + object->nmeshes = num_meshes; + object->mesh_number = mesh_index; + object->loaded = true; } } return true; @@ -947,18 +952,21 @@ bool Level_Initialise(int32_t level_num) Overlay_HideGameInfo(); g_FlipStatus = 0; - for (int i = 0; i < MAX_FLIP_MAPS; i++) { + for (int32_t i = 0; i < MAX_FLIP_MAPS; i++) { g_FlipMapTable[i] = 0; } - for (int i = 0; i < MAX_CD_TRACKS; i++) { + for (int32_t i = 0; i < MAX_CD_TRACKS; i++) { g_MusicTrackFlags[i] = 0; } /* Clear Object Loaded flags */ - for (int i = 0; i < O_NUMBER_OF; i++) { + for (int32_t i = 0; i < O_NUMBER_OF; i++) { g_Objects[i].loaded = 0; } + for (int32_t i = 0; i < STATIC_NUMBER_OF; i++) { + g_StaticObjects[i].loaded = false; + } Camera_Reset(); Pierre_Reset(); diff --git a/src/game/output.c b/src/game/output.c index 734ffe54e..98598577d 100644 --- a/src/game/output.c +++ b/src/game/output.c @@ -924,16 +924,16 @@ void Output_AnimateTextures(void) { m_WibbleOffset = Clock_GetLogicalFrame() % WIBBLE_SIZE; - if (!g_AnimTextureRanges) { + if (!Clock_IsAtLogicalFrame(5)) { return; } - if (Clock_IsAtLogicalFrame(5)) { - int16_t *ptr = g_AnimTextureRanges; + if (g_AnimTextureRanges) { + const int16_t *ptr = g_AnimTextureRanges; int16_t i = *ptr++; while (i > 0) { int16_t j = *ptr++; - PHD_TEXTURE temp = g_PhdTextureInfo[*ptr]; + const PHD_TEXTURE temp = g_PhdTextureInfo[*ptr]; while (j > 0) { g_PhdTextureInfo[ptr[0]] = g_PhdTextureInfo[ptr[1]]; j--; @@ -944,6 +944,21 @@ void Output_AnimateTextures(void) ptr++; } } + + for (int32_t i = 0; i < STATIC_NUMBER_OF; i++) { + const STATIC_INFO *const static_info = &g_StaticObjects[i]; + if (!static_info->loaded || static_info->nmeshes >= -1) { + continue; + } + + const int32_t num_meshes = -static_info->nmeshes; + const PHD_SPRITE temp = g_PhdSpriteInfo[static_info->mesh_number]; + for (int32_t j = 0; j < num_meshes - 1; j++) { + g_PhdSpriteInfo[static_info->mesh_number + j] = + g_PhdSpriteInfo[static_info->mesh_number + j + 1]; + } + g_PhdSpriteInfo[static_info->mesh_number + num_meshes - 1] = temp; + } } void Output_RotateLight(int16_t pitch, int16_t yaw) diff --git a/src/global/types.h b/src/global/types.h index cb15a0ea5..a449dc4a2 100644 --- a/src/global/types.h +++ b/src/global/types.h @@ -1804,6 +1804,8 @@ typedef struct SHADOW_INFO { } SHADOW_INFO; typedef struct STATIC_INFO { + bool loaded; + int16_t nmeshes; int16_t mesh_number; int16_t flags; BOUNDS_16 p; diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/en.json b/tools/config/TR1X_ConfigTool/Resources/Lang/en.json index 15c999dc3..bf8b6ba8f 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/en.json +++ b/tools/config/TR1X_ConfigTool/Resources/Lang/en.json @@ -160,6 +160,10 @@ "Title": "Fix texture issues", "Description": "Fixes original issues with missing or incorrect textures." }, + "fix_animated_sprites": { + "Title": "Fix sprite animations", + "Description": "Fixes original issues in The Cistern and Tomb of Tihocan where plant sprites in water areas do not animate." + }, "fix_item_rots": { "Title": "Fix item rotation issues", "Description": "Fixes original issues with some incorrectly rotated pickups when using the 3D pickups option." diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/es.json b/tools/config/TR1X_ConfigTool/Resources/Lang/es.json index 41ad25bbf..da963478b 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/es.json +++ b/tools/config/TR1X_ConfigTool/Resources/Lang/es.json @@ -328,6 +328,10 @@ "Title": "Arreglar problemas de textura", "Description": "Corrige los problemas originales con texturas faltantes o incorrectas." }, + "fix_animated_sprites": { + "Title": "Arreglar animaciones de sprites", + "Description": "Corrige los problemas originales en La Cisterna y La Tumba de Tihocan donde los duendes de las plantas en las áreas de agua no se animan." + }, "fix_item_rots": { "Title": "Arreglar problemas de rotación de objetos", "Description": "Corrige problemas originales con algunas recogidas giradas incorrectamente al usar la opción de recogidas en 3D." diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json b/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json index 4bdc8441f..540571f93 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json +++ b/tools/config/TR1X_ConfigTool/Resources/Lang/fr.json @@ -160,6 +160,10 @@ "Title": "Corrige les problèmes de texture", "Description": "Corrige les bugs originaux connus de textures manquantes ou incorrectes." }, + "fix_animated_sprites": { + "Title": "Correction des animations des sprites", + "Description": "Corrige les problèmes originaux dans La Citerne et Le Tombe de Tihocan où les sprites végétaux dans les zones aquatiques ne s'animent pas." + }, "fix_item_rots": { "Title": "Correction orientation des collectibles", "Description": "Corrige des problèmes avec certains collectibles mal orientés, quand l'option des collectibles en 3D est utilisée." diff --git a/tools/config/TR1X_ConfigTool/Resources/Lang/it.json b/tools/config/TR1X_ConfigTool/Resources/Lang/it.json index 2553a3299..4f5398b91 100644 --- a/tools/config/TR1X_ConfigTool/Resources/Lang/it.json +++ b/tools/config/TR1X_ConfigTool/Resources/Lang/it.json @@ -160,6 +160,10 @@ "Title": "Correggi i problemi delle texture", "Description": "Risolve i problemi riguardanti texture mancanti o errate." }, + "fix_animated_sprites": { + "Title": "Correggi le animazioni degli sprite", + "Description": "Risolve i problemi originali nella Cisterna e nella Tomba di Tihocan per cui gli sprite delle piante nelle aree acquatiche non si animavano." + }, "fix_item_rots": { "Title": "Correggi l'orientamento degli oggetti", "Description": "Risolve i problemi relativi all'orientamento errato di alcuni oggetti quando viene utilizzata l'opzione Oggetti 3D." diff --git a/tools/config/TR1X_ConfigTool/Resources/specification.json b/tools/config/TR1X_ConfigTool/Resources/specification.json index a253d7cb5..e5ecce2a7 100644 --- a/tools/config/TR1X_ConfigTool/Resources/specification.json +++ b/tools/config/TR1X_ConfigTool/Resources/specification.json @@ -151,6 +151,11 @@ "DataType": "Bool", "DefaultValue": true }, + { + "Field": "fix_animated_sprites", + "DataType": "Bool", + "DefaultValue": true + }, { "Field": "fix_qwop_glitch", "DataType": "Bool",