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
+ 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",