diff --git a/CHANGELOG.md b/CHANGELOG.md index 299db2df8..5aafb9e20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - fixed a potential softlock when killing the Torso boss in Great Pyramid (#1236) - fixed Bacon Lara re-spawning after saving and loading (#1500, regression from 0.7) - fixed config JSON not sanitizing some numeric values (#1515) +- fixed potential crashes in custom levels if hybrid creature objects are not present in the level (#1444) - changed `/tp` console command to look for the closest place to teleport to when targeting items (#1484) - changed the door cheat to also target drawbridges - improved appearance of textures around edges when bilinear filter is off (#1483) diff --git a/src/game/creature.c b/src/game/creature.c index 848742afc..2309da10e 100644 --- a/src/game/creature.c +++ b/src/game/creature.c @@ -15,14 +15,18 @@ #include "global/vars.h" #include "math/math.h" +#include + #include #define MAX_CREATURE_DISTANCE (WALL_L * 30) static bool Creature_SwitchToWater( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info); + int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); static bool Creature_SwitchToLand( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info); + int16_t item_num, const int32_t *wh, const HYBRID_INFO *info); +static bool Creature_TestSwitchOrKill( + int16_t item_num, GAME_OBJECT_ID target_id); void Creature_Initialise(int16_t item_num) { @@ -709,11 +713,11 @@ bool Creature_ShootAtLara( } bool Creature_EnsureHabitat( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info) + const int16_t item_num, int32_t *const wh, const HYBRID_INFO *const info) { // Test the environment for a hybrid creature. Record the water height and // return whether or not a type conversion has taken place. - ITEM_INFO *item = &g_Items[item_num]; + const ITEM_INFO *const item = &g_Items[item_num]; *wh = Room_GetWaterHeight( item->pos.x, item->pos.y, item->pos.z, item->room_num); @@ -729,13 +733,14 @@ bool Creature_IsBoss(const int16_t item_num) } static bool Creature_SwitchToWater( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info) + const int16_t item_num, const int32_t *const wh, + const HYBRID_INFO *const info) { if (*wh == NO_HEIGHT) { return false; } - ITEM_INFO *item = &g_Items[item_num]; + ITEM_INFO *const item = &g_Items[item_num]; if (item->hit_points <= 0) { // Dead land creatures should remain in their pose permanently. @@ -744,6 +749,10 @@ static bool Creature_SwitchToWater( // The land creature is alive and the room has been flooded. Switch to the // water creature. + if (!Creature_TestSwitchOrKill(item_num, info->water.id)) { + return false; + } + item->object_id = info->water.id; Item_SwitchToAnim(item, info->water.active_anim, 0); item->current_anim_state = g_Anims[item->anim_num].current_anim_state; @@ -754,13 +763,18 @@ static bool Creature_SwitchToWater( } static bool Creature_SwitchToLand( - int16_t item_num, int32_t *wh, const HYBRID_INFO *info) + const int16_t item_num, const int32_t *const wh, + const HYBRID_INFO *const info) { if (*wh != NO_HEIGHT) { return false; } - ITEM_INFO *item = &g_Items[item_num]; + if (!Creature_TestSwitchOrKill(item_num, info->land.id)) { + return false; + } + + ITEM_INFO *const item = &g_Items[item_num]; // Switch to the land creature regardless of death state. item->object_id = info->land.id; @@ -790,3 +804,17 @@ static bool Creature_SwitchToLand( return true; } + +static bool Creature_TestSwitchOrKill( + const int16_t item_num, const GAME_OBJECT_ID target_id) +{ + if (g_Objects[target_id].loaded) { + return true; + } + + LOG_WARNING( + "Object %d is not loaded; item %d cannot be converted.", target_id, + item_num); + Item_Kill(item_num); + return false; +} diff --git a/src/game/objects/creatures/skate_kid.c b/src/game/objects/creatures/skate_kid.c index f3ee524d2..4ffd950be 100644 --- a/src/game/objects/creatures/skate_kid.c +++ b/src/game/objects/creatures/skate_kid.c @@ -9,6 +9,7 @@ #include "global/const.h" #include "global/vars.h" +#include #include #define SKATE_KID_STOP_SHOT_DAMAGE 50 @@ -57,6 +58,12 @@ void SkateKid_Setup(OBJECT_INFO *obj) obj->save_anim = 1; obj->save_flags = 1; g_AnimBones[obj->bone_index] |= BEB_ROT_Y; + + if (!g_Objects[O_SKATEBOARD].loaded) { + LOG_WARNING( + "Skateboard object (%d) is not loaded and so will not be drawn.", + O_SKATEBOARD); + } } void SkateKid_Initialise(int16_t item_num) @@ -168,6 +175,9 @@ void SkateKid_Control(int16_t item_num) void SkateKid_Draw(ITEM_INFO *item) { Object_DrawAnimatingItem(item); + if (!g_Objects[O_SKATEBOARD].loaded) { + return; + } int16_t relative_anim = item->anim_num - g_Objects[item->object_id].anim_index;