mirror of
https://github.com/LostArtefacts/TRX.git
synced 2025-04-28 20:58:07 +03:00
ai: add configurable item drop behaviour (#1012)
This replaces all hardcoded enemy item drops with a flexible system using the gameflow. Specific enemies can carry pickup items, which spawn on death and are remembered between saves. Dropped guns can also optionally be converted to ammo if Lara has the gun already. Support is also included for Pierre, Cowboy, Skaterboy and Kold to handle their items accordingly when using legacy save files. Resolves #967.
This commit is contained in:
parent
cd64a006df
commit
c1bcc82693
22 changed files with 423 additions and 70 deletions
|
@ -2,9 +2,10 @@
|
||||||
- renamed the project from Tomb1Main to TR1X in an effort to establish our own unique identity, while respectfully disassociating from TR2Main.
|
- renamed the project from Tomb1Main to TR1X in an effort to establish our own unique identity, while respectfully disassociating from TR2Main.
|
||||||
- added Linux builds and toolchain
|
- added Linux builds and toolchain
|
||||||
- added an option to allow Lara to roll while underwater, similar to TR2+ (#993)
|
- added an option to allow Lara to roll while underwater, similar to TR2+ (#993)
|
||||||
- fixed baddies dropping duplicate guns (only affects mods) (#1000)
|
|
||||||
- added the bonus level type for custom levels that unlocks if all main game secrets are found (#645)
|
- added the bonus level type for custom levels that unlocks if all main game secrets are found (#645)
|
||||||
- added detection for animation commands to play SFX on land, water or both (#999)
|
- added detection for animation commands to play SFX on land, water or both (#999)
|
||||||
|
- added support for customizable enemy item drops via the gameflow (#967)
|
||||||
|
- fixed baddies dropping duplicate guns (only affects mods) (#1000)
|
||||||
- improved frame scheduling to use less CPU (#985)
|
- improved frame scheduling to use less CPU (#985)
|
||||||
|
|
||||||
## [2.16](https://github.com/LostArtefacts/TR1X/compare/2.15.3...2.16) - 2023-09-20
|
## [2.16](https://github.com/LostArtefacts/TR1X/compare/2.15.3...2.16) - 2023-09-20
|
||||||
|
|
|
@ -410,6 +410,7 @@ Not all options are turned on by default. Refer to `TR1X_ConfigTool.exe` for det
|
||||||
- you can change the main menu backdrop
|
- you can change the main menu backdrop
|
||||||
- you can specify the anchor room for Bacon Lara
|
- you can specify the anchor room for Bacon Lara
|
||||||
- you can add bonus levels that unlock if all main game secrets are found
|
- you can add bonus levels that unlock if all main game secrets are found
|
||||||
|
- you can specify which enemies drop items, and the number and types of those items
|
||||||
- added automatic calculation of secret counts (no longer having to fiddle with the .exe to get correct secret stats)
|
- added automatic calculation of secret counts (no longer having to fiddle with the .exe to get correct secret stats)
|
||||||
- added save game crystals game mode (enabled via gameflow)
|
- added save game crystals game mode (enabled via gameflow)
|
||||||
- added per-level customizable water color (with customizable blue component)
|
- added per-level customizable water color (with customizable blue component)
|
||||||
|
|
|
@ -53,6 +53,11 @@
|
||||||
"data/uzi_sfx.bin",
|
"data/uzi_sfx.bin",
|
||||||
],
|
],
|
||||||
|
|
||||||
|
// When enabled and an enemy is configured to drop a gun, if Lara already has
|
||||||
|
// that gun, then the equivalent ammo will instead be dropped. Otherwise, the
|
||||||
|
// gun will always be dropped.
|
||||||
|
"convert_dropped_guns": false,
|
||||||
|
|
||||||
// List of levels
|
// List of levels
|
||||||
"levels": [
|
"levels": [
|
||||||
// Level 0
|
// Level 0
|
||||||
|
@ -305,6 +310,9 @@
|
||||||
"data/tihocan_itemrots.bin",
|
"data/tihocan_itemrots.bin",
|
||||||
"data/tihocan_textures.bin",
|
"data/tihocan_textures.bin",
|
||||||
],
|
],
|
||||||
|
"item_drops": [
|
||||||
|
{"enemy_num": 82, "object_ids": [86, 144, 129]},
|
||||||
|
],
|
||||||
"sequence": [
|
"sequence": [
|
||||||
{"type": "start_game"},
|
{"type": "start_game"},
|
||||||
{"type": "loop_game"},
|
{"type": "loop_game"},
|
||||||
|
@ -407,6 +415,11 @@
|
||||||
"data/mines_textures.bin",
|
"data/mines_textures.bin",
|
||||||
"data/skateboardkid_textures.bin"
|
"data/skateboardkid_textures.bin"
|
||||||
],
|
],
|
||||||
|
"item_drops": [
|
||||||
|
{"enemy_num": 17, "object_ids": [86]},
|
||||||
|
{"enemy_num": 50, "object_ids": [87]},
|
||||||
|
{"enemy_num": 75, "object_ids": [85]},
|
||||||
|
],
|
||||||
"sequence": [
|
"sequence": [
|
||||||
{"type": "play_fmv", "fmv_path": "fmv/canyon.avi"},
|
{"type": "play_fmv", "fmv_path": "fmv/canyon.avi"},
|
||||||
{"type": "remove_guns"},
|
{"type": "remove_guns"},
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"data/lara_jumping.bin",
|
"data/lara_jumping.bin",
|
||||||
"data/uzi_sfx.bin",
|
"data/uzi_sfx.bin",
|
||||||
],
|
],
|
||||||
|
"convert_dropped_guns": false,
|
||||||
|
|
||||||
"levels": [
|
"levels": [
|
||||||
// Level 0
|
// Level 0
|
||||||
|
|
|
@ -86,6 +86,7 @@ sources = [
|
||||||
'src/filesystem.c',
|
'src/filesystem.c',
|
||||||
'src/game/box.c',
|
'src/game/box.c',
|
||||||
'src/game/camera.c',
|
'src/game/camera.c',
|
||||||
|
'src/game/carrier.c',
|
||||||
'src/game/clock.c',
|
'src/game/clock.c',
|
||||||
'src/game/collide.c',
|
'src/game/collide.c',
|
||||||
'src/game/creature.c',
|
'src/game/creature.c',
|
||||||
|
|
241
src/game/carrier.c
Normal file
241
src/game/carrier.c
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
#include "game/carrier.h"
|
||||||
|
|
||||||
|
#include "game/gamebuf.h"
|
||||||
|
#include "game/gameflow.h"
|
||||||
|
#include "game/inventory.h"
|
||||||
|
#include "game/items.h"
|
||||||
|
#include "global/const.h"
|
||||||
|
#include "global/types.h"
|
||||||
|
#include "global/vars.h"
|
||||||
|
#include "log.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#define NO_OBJECT O_NUMBER_OF
|
||||||
|
|
||||||
|
typedef struct GAME_OBJECT_PAIR {
|
||||||
|
const GAME_OBJECT_ID key_id;
|
||||||
|
const GAME_OBJECT_ID value_id;
|
||||||
|
} GAME_OBJECT_PAIR;
|
||||||
|
|
||||||
|
static ITEM_INFO *Carrier_GetCarrier(int16_t item_num);
|
||||||
|
static bool Carrier_IsObjectType(
|
||||||
|
GAME_OBJECT_ID object_id, const GAME_OBJECT_ID *test_arr);
|
||||||
|
static GAME_OBJECT_ID Carrier_GetCognate(
|
||||||
|
GAME_OBJECT_ID key_id, const GAME_OBJECT_PAIR *test_map);
|
||||||
|
|
||||||
|
static const GAME_OBJECT_ID m_CarrierObjects[] = {
|
||||||
|
O_WOLF, O_BEAR, O_BAT, O_LION, O_LIONESS, O_PUMA, O_APE,
|
||||||
|
O_TREX, O_RAPTOR, O_CENTAUR, O_MUMMY, O_LARSON, O_PIERRE, O_SKATEKID,
|
||||||
|
O_COWBOY, O_BALDY, O_NATLA, O_TORSO, NO_OBJECT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const GAME_OBJECT_ID m_PlaceholderObjects[] = { O_STATUE, O_PODS,
|
||||||
|
O_BIG_POD, NO_OBJECT };
|
||||||
|
|
||||||
|
static const GAME_OBJECT_ID m_DropObjects[] = {
|
||||||
|
O_GUN_ITEM, O_SHOTGUN_ITEM, O_MAGNUM_ITEM, O_UZI_ITEM,
|
||||||
|
O_SG_AMMO_ITEM, O_MAG_AMMO_ITEM, O_UZI_AMMO_ITEM, O_MEDI_ITEM,
|
||||||
|
O_BIGMEDI_ITEM, O_PUZZLE_ITEM1, O_PUZZLE_ITEM2, O_PUZZLE_ITEM3,
|
||||||
|
O_PUZZLE_ITEM4, O_KEY_ITEM1, O_KEY_ITEM2, O_KEY_ITEM3,
|
||||||
|
O_KEY_ITEM4, O_PICKUP_ITEM1, O_PICKUP_ITEM2, O_LEADBAR_ITEM,
|
||||||
|
O_SCION_ITEM2, NO_OBJECT
|
||||||
|
};
|
||||||
|
|
||||||
|
static const GAME_OBJECT_ID m_GunObjects[] = { O_SHOTGUN_ITEM, O_MAGNUM_ITEM,
|
||||||
|
O_UZI_ITEM, NO_OBJECT };
|
||||||
|
|
||||||
|
static const GAME_OBJECT_PAIR m_GunAmmoMap[] = {
|
||||||
|
{ O_SHOTGUN_ITEM, O_SG_AMMO_ITEM },
|
||||||
|
{ O_MAGNUM_ITEM, O_MAG_AMMO_ITEM },
|
||||||
|
{ O_UZI_ITEM, O_UZI_AMMO_ITEM },
|
||||||
|
{ NO_OBJECT, NO_OBJECT },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const GAME_OBJECT_PAIR m_LegacyMap[] = {
|
||||||
|
{ O_PIERRE, O_SCION_ITEM2 }, { O_COWBOY, O_MAGNUM_ITEM },
|
||||||
|
{ O_SKATEKID, O_UZI_ITEM }, { O_BALDY, O_SHOTGUN_ITEM },
|
||||||
|
{ NO_OBJECT, NO_OBJECT },
|
||||||
|
};
|
||||||
|
|
||||||
|
void Carrier_InitialiseLevel(int32_t level_num)
|
||||||
|
{
|
||||||
|
int32_t total_item_count = g_LevelItemCount;
|
||||||
|
GAMEFLOW_LEVEL level = g_GameFlow.levels[level_num];
|
||||||
|
for (int i = 0; i < level.item_drops.count; i++) {
|
||||||
|
GAMEFLOW_DROP_ITEM_DATA *data = &level.item_drops.data[i];
|
||||||
|
|
||||||
|
ITEM_INFO *item = Carrier_GetCarrier(data->enemy_num);
|
||||||
|
if (!item) {
|
||||||
|
LOG_WARNING("%d does not refer to a loaded item", data->enemy_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (total_item_count + data->count > MAX_ITEMS) {
|
||||||
|
LOG_WARNING("Too many items being loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->carried_item) {
|
||||||
|
LOG_WARNING("Item %d is already carrying", data->enemy_num);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Carrier_IsObjectType(item->object_number, m_CarrierObjects)) {
|
||||||
|
LOG_WARNING(
|
||||||
|
"Item %d of type %d cannot carry items", data->enemy_num,
|
||||||
|
item->object_number);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
item->carried_item =
|
||||||
|
GameBuf_Alloc(sizeof(CARRIED_ITEM) * data->count, GBUF_ITEMS);
|
||||||
|
CARRIED_ITEM *drop = item->carried_item;
|
||||||
|
for (int i = 0; i < data->count; i++) {
|
||||||
|
drop->object_id = data->object_ids[i];
|
||||||
|
drop->spawn_number = NO_ITEM;
|
||||||
|
|
||||||
|
if (Carrier_IsObjectType(drop->object_id, m_DropObjects)) {
|
||||||
|
drop->status = IS_NOT_ACTIVE;
|
||||||
|
total_item_count++;
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(
|
||||||
|
"Items of type %d cannot be carried", drop->object_id);
|
||||||
|
drop->object_id = NO_OBJECT;
|
||||||
|
drop->status = IS_INVISIBLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < data->count - 1) {
|
||||||
|
drop->next_item = drop + 1;
|
||||||
|
drop++;
|
||||||
|
} else {
|
||||||
|
drop->next_item = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ITEM_INFO *Carrier_GetCarrier(int16_t item_num)
|
||||||
|
{
|
||||||
|
if (item_num < 0 || item_num >= g_LevelItemCount) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow carried items to be allocated to holder objects (pods/statues),
|
||||||
|
// but then have those items dropped by the actual creatures within.
|
||||||
|
ITEM_INFO *item = &g_Items[item_num];
|
||||||
|
if (Carrier_IsObjectType(item->object_number, m_PlaceholderObjects)) {
|
||||||
|
int16_t child_item_num = *(int16_t *)item->data;
|
||||||
|
item = &g_Items[child_item_num];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!g_Objects[item->object_number].loaded) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Carrier_IsObjectType(
|
||||||
|
GAME_OBJECT_ID object_id, const GAME_OBJECT_ID *test_arr)
|
||||||
|
{
|
||||||
|
for (int i = 0; test_arr[i] != NO_OBJECT; i++) {
|
||||||
|
if (test_arr[i] == object_id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GAME_OBJECT_ID Carrier_GetCognate(
|
||||||
|
GAME_OBJECT_ID key_id, const GAME_OBJECT_PAIR *test_map)
|
||||||
|
{
|
||||||
|
const GAME_OBJECT_PAIR *pair = &test_map[0];
|
||||||
|
while (pair->key_id != NO_OBJECT) {
|
||||||
|
if (pair->key_id == key_id) {
|
||||||
|
return pair->value_id;
|
||||||
|
}
|
||||||
|
pair++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NO_OBJECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t Carrier_GetItemCount(int16_t item_num)
|
||||||
|
{
|
||||||
|
ITEM_INFO *carrier = Carrier_GetCarrier(item_num);
|
||||||
|
if (!carrier) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CARRIED_ITEM *item = carrier->carried_item;
|
||||||
|
int32_t count = 0;
|
||||||
|
while (item) {
|
||||||
|
if (item->object_id != NO_OBJECT) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
item = item->next_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Carrier_TestItemDrops(int16_t item_num)
|
||||||
|
{
|
||||||
|
ITEM_INFO *carrier = &g_Items[item_num];
|
||||||
|
CARRIED_ITEM *item = carrier->carried_item;
|
||||||
|
if (carrier->status != IS_DEACTIVATED || !item
|
||||||
|
|| (carrier->object_number == O_PIERRE
|
||||||
|
&& !(carrier->flags & IF_ONESHOT))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The enemy is killed (plus is not runaway) and is carrying at
|
||||||
|
// least one item. Ensure that each item has not already spawned,
|
||||||
|
// convert guns to ammo if applicable, and spawn the items.
|
||||||
|
do {
|
||||||
|
if (item->status == IS_INVISIBLE) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GAME_OBJECT_ID object_id = item->object_id;
|
||||||
|
if (g_GameFlow.convert_dropped_guns
|
||||||
|
&& Carrier_IsObjectType(object_id, m_GunObjects)
|
||||||
|
&& Inv_RequestItem(object_id)) {
|
||||||
|
object_id = Carrier_GetCognate(object_id, m_GunAmmoMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
item->spawn_number = Item_Spawn(carrier, object_id);
|
||||||
|
item->status = IS_ACTIVE;
|
||||||
|
} while ((item = item->next_item));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Carrier_TestLegacyDrops(int16_t item_num)
|
||||||
|
{
|
||||||
|
ITEM_INFO *carrier = &g_Items[item_num];
|
||||||
|
if (carrier->status != IS_DEACTIVATED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cases where legacy saves have been loaded. Ensure that
|
||||||
|
// the OG enemy will still spawn items if Lara hasn't yet collected
|
||||||
|
// them by using a test cognate in each case. Ensure also that
|
||||||
|
// collected items do not re-spawn now or in future saves.
|
||||||
|
GAME_OBJECT_ID test_id =
|
||||||
|
Carrier_GetCognate(carrier->object_number, m_LegacyMap);
|
||||||
|
if (test_id == NO_OBJECT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Inv_RequestItem(test_id)) {
|
||||||
|
Carrier_TestItemDrops(item_num);
|
||||||
|
} else {
|
||||||
|
CARRIED_ITEM *item = carrier->carried_item;
|
||||||
|
while (item) {
|
||||||
|
// Simulate Lara having picked up the item.
|
||||||
|
item->status = IS_INVISIBLE;
|
||||||
|
item = item->next_item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/game/carrier.h
Normal file
8
src/game/carrier.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
void Carrier_InitialiseLevel(int32_t level_num);
|
||||||
|
int32_t Carrier_GetItemCount(int16_t item_num);
|
||||||
|
void Carrier_TestItemDrops(int16_t item_num);
|
||||||
|
void Carrier_TestLegacyDrops(int16_t item_num);
|
|
@ -1,6 +1,7 @@
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
|
|
||||||
#include "game/box.h"
|
#include "game/box.h"
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/collide.h"
|
#include "game/collide.h"
|
||||||
#include "game/effects.h"
|
#include "game/effects.h"
|
||||||
#include "game/effects/gunshot.h"
|
#include "game/effects/gunshot.h"
|
||||||
|
@ -418,6 +419,7 @@ bool Creature_Animate(int16_t item_num, int16_t angle, int16_t tilt)
|
||||||
item->hit_points = DONT_TARGET;
|
item->hit_points = DONT_TARGET;
|
||||||
LOT_DisableBaddieAI(item_num);
|
LOT_DisableBaddieAI(item_num);
|
||||||
Item_RemoveActive(item_num);
|
Item_RemoveActive(item_num);
|
||||||
|
Carrier_TestItemDrops(item_num);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -292,6 +292,9 @@ static bool GameFlow_LoadScriptMeta(struct json_object_s *obj)
|
||||||
g_GameFlow.injections.length = 0;
|
g_GameFlow.injections.length = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_GameFlow.convert_dropped_guns =
|
||||||
|
json_object_get_bool(obj, "convert_dropped_guns", false);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -934,6 +937,53 @@ static bool GameFlow_LoadScriptLevels(struct json_object_s *obj)
|
||||||
}
|
}
|
||||||
cur->lara_type = (GAME_OBJECT_ID)tmp_i;
|
cur->lara_type = (GAME_OBJECT_ID)tmp_i;
|
||||||
|
|
||||||
|
tmp_arr = json_object_get_array(jlvl_obj, "item_drops");
|
||||||
|
if (tmp_arr) {
|
||||||
|
cur->item_drops.count = (signed)tmp_arr->length;
|
||||||
|
cur->item_drops.data = Memory_Alloc(
|
||||||
|
sizeof(GAMEFLOW_DROP_ITEM_DATA) * (signed)tmp_arr->length);
|
||||||
|
|
||||||
|
for (int i = 0; i < cur->item_drops.count; i++) {
|
||||||
|
GAMEFLOW_DROP_ITEM_DATA *data = &cur->item_drops.data[i];
|
||||||
|
struct json_object_s *jlvl_data =
|
||||||
|
json_array_get_object(tmp_arr, i);
|
||||||
|
|
||||||
|
data->enemy_num = json_object_get_int(
|
||||||
|
jlvl_data, "enemy_num", JSON_INVALID_NUMBER);
|
||||||
|
if (data->enemy_num == JSON_INVALID_NUMBER) {
|
||||||
|
LOG_ERROR(
|
||||||
|
"level %d, item drop %d: 'enemy_num' must be a number",
|
||||||
|
level_num, i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_array_s *object_arr =
|
||||||
|
json_object_get_array(jlvl_data, "object_ids");
|
||||||
|
if (!object_arr) {
|
||||||
|
LOG_ERROR(
|
||||||
|
"level %d, item drop %d: 'object_ids' must be an array",
|
||||||
|
level_num, i);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
data->count = (signed)object_arr->length;
|
||||||
|
data->object_ids = Memory_Alloc(sizeof(int16_t) * data->count);
|
||||||
|
for (int j = 0; j < data->count; j++) {
|
||||||
|
int id = json_array_get_int(object_arr, j, -1);
|
||||||
|
if (id < 0 || id >= O_NUMBER_OF) {
|
||||||
|
LOG_ERROR(
|
||||||
|
"level %d, item drop %d, index %d: 'object_id' "
|
||||||
|
"must be a valid object id",
|
||||||
|
level_num, i, j);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
data->object_ids[j] = (int16_t)id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cur->item_drops.count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!GameFlow_LoadLevelSequence(jlvl_obj, level_num)) {
|
if (!GameFlow_LoadLevelSequence(jlvl_obj, level_num)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1030,6 +1080,15 @@ void GameFlow_Shutdown(void)
|
||||||
&g_GameFlow.levels[i].injections.data_paths[j]);
|
&g_GameFlow.levels[i].injections.data_paths[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (g_GameFlow.levels[i].item_drops.count) {
|
||||||
|
for (int j = 0; j < g_GameFlow.levels[i].item_drops.count;
|
||||||
|
j++) {
|
||||||
|
Memory_FreePointer(
|
||||||
|
&g_GameFlow.levels[i].item_drops.data[j].object_ids);
|
||||||
|
}
|
||||||
|
Memory_FreePointer(&g_GameFlow.levels[i].item_drops.data);
|
||||||
|
}
|
||||||
|
|
||||||
GAMEFLOW_SEQUENCE *seq = g_GameFlow.levels[i].sequence;
|
GAMEFLOW_SEQUENCE *seq = g_GameFlow.levels[i].sequence;
|
||||||
if (seq) {
|
if (seq) {
|
||||||
while (seq->type != GFS_END) {
|
while (seq->type != GFS_END) {
|
||||||
|
|
|
@ -15,6 +15,12 @@ typedef struct GAMEFLOW_SEQUENCE {
|
||||||
void *data;
|
void *data;
|
||||||
} GAMEFLOW_SEQUENCE;
|
} GAMEFLOW_SEQUENCE;
|
||||||
|
|
||||||
|
typedef struct GAMEFLOW_DROP_ITEM_DATA {
|
||||||
|
int32_t enemy_num;
|
||||||
|
int32_t count;
|
||||||
|
int16_t *object_ids;
|
||||||
|
} GAMEFLOW_DROP_ITEM_DATA;
|
||||||
|
|
||||||
typedef struct GAMEFLOW_LEVEL {
|
typedef struct GAMEFLOW_LEVEL {
|
||||||
GAMEFLOW_LEVEL_TYPE level_type;
|
GAMEFLOW_LEVEL_TYPE level_type;
|
||||||
int16_t music;
|
int16_t music;
|
||||||
|
@ -52,6 +58,10 @@ typedef struct GAMEFLOW_LEVEL {
|
||||||
int length;
|
int length;
|
||||||
char **data_paths;
|
char **data_paths;
|
||||||
} injections;
|
} injections;
|
||||||
|
struct {
|
||||||
|
int count;
|
||||||
|
GAMEFLOW_DROP_ITEM_DATA *data;
|
||||||
|
} item_drops;
|
||||||
GAME_OBJECT_ID lara_type;
|
GAME_OBJECT_ID lara_type;
|
||||||
} GAMEFLOW_LEVEL;
|
} GAMEFLOW_LEVEL;
|
||||||
|
|
||||||
|
@ -77,6 +87,7 @@ typedef struct GAMEFLOW {
|
||||||
int length;
|
int length;
|
||||||
char **data_paths;
|
char **data_paths;
|
||||||
} injections;
|
} injections;
|
||||||
|
bool convert_dropped_guns;
|
||||||
} GAMEFLOW;
|
} GAMEFLOW;
|
||||||
|
|
||||||
extern GAMEFLOW g_GameFlow;
|
extern GAMEFLOW g_GameFlow;
|
||||||
|
|
|
@ -132,6 +132,7 @@ void Item_Initialise(int16_t item_num)
|
||||||
item->touch_bits = 0;
|
item->touch_bits = 0;
|
||||||
item->data = NULL;
|
item->data = NULL;
|
||||||
item->priv = NULL;
|
item->priv = NULL;
|
||||||
|
item->carried_item = NULL;
|
||||||
|
|
||||||
if (item->flags & IF_NOT_VISIBLE) {
|
if (item->flags & IF_NOT_VISIBLE) {
|
||||||
item->status = IS_INVISIBLE;
|
item->status = IS_INVISIBLE;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "filesystem.h"
|
#include "filesystem.h"
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/effects.h"
|
#include "game/effects.h"
|
||||||
#include "game/gamebuf.h"
|
#include "game/gamebuf.h"
|
||||||
#include "game/gameflow.h"
|
#include "game/gameflow.h"
|
||||||
|
@ -48,7 +49,7 @@ static bool Level_LoadSamples(MYFILE *fp);
|
||||||
static bool Level_LoadTexturePages(MYFILE *fp);
|
static bool Level_LoadTexturePages(MYFILE *fp);
|
||||||
|
|
||||||
static bool Level_LoadFromFile(const char *filename, int32_t level_num);
|
static bool Level_LoadFromFile(const char *filename, int32_t level_num);
|
||||||
static void Level_CompleteSetup(void);
|
static void Level_CompleteSetup(int32_t level_num);
|
||||||
|
|
||||||
static bool Level_LoadFromFile(const char *filename, int32_t level_num)
|
static bool Level_LoadFromFile(const char *filename, int32_t level_num)
|
||||||
{
|
{
|
||||||
|
@ -659,7 +660,7 @@ static bool Level_LoadTexturePages(MYFILE *fp)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Level_CompleteSetup(void)
|
static void Level_CompleteSetup(int32_t level_num)
|
||||||
{
|
{
|
||||||
Inject_AllInjections(&m_LevelInfo);
|
Inject_AllInjections(&m_LevelInfo);
|
||||||
|
|
||||||
|
@ -675,6 +676,9 @@ static void Level_CompleteSetup(void)
|
||||||
Item_Initialise(i);
|
Item_Initialise(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Configure enemies who carry and drop items
|
||||||
|
Carrier_InitialiseLevel(level_num);
|
||||||
|
|
||||||
// Move the prepared texture pages into g_TexturePagePtrs.
|
// Move the prepared texture pages into g_TexturePagePtrs.
|
||||||
uint8_t *base = GameBuf_Alloc(
|
uint8_t *base = GameBuf_Alloc(
|
||||||
m_LevelInfo.texture_page_count * PAGE_SIZE, GBUF_TEXTURE_PAGES);
|
m_LevelInfo.texture_page_count * PAGE_SIZE, GBUF_TEXTURE_PAGES);
|
||||||
|
@ -723,7 +727,7 @@ bool Level_Load(int level_num)
|
||||||
Level_LoadFromFile(g_GameFlow.levels[level_num].level_file, level_num);
|
Level_LoadFromFile(g_GameFlow.levels[level_num].level_file, level_num);
|
||||||
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
Level_CompleteSetup();
|
Level_CompleteSetup(level_num);
|
||||||
}
|
}
|
||||||
|
|
||||||
Inject_Cleanup();
|
Inject_Cleanup();
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "game/objects/creatures/baldy.h"
|
#include "game/objects/creatures/baldy.h"
|
||||||
|
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
#include "game/inventory.h"
|
|
||||||
#include "game/items.h"
|
#include "game/items.h"
|
||||||
#include "game/lot.h"
|
#include "game/lot.h"
|
||||||
#include "global/const.h"
|
#include "global/const.h"
|
||||||
|
@ -77,11 +76,6 @@ void Baldy_Control(int16_t item_num)
|
||||||
if (item->current_anim_state != BALDY_DEATH) {
|
if (item->current_anim_state != BALDY_DEATH) {
|
||||||
item->current_anim_state = BALDY_DEATH;
|
item->current_anim_state = BALDY_DEATH;
|
||||||
Item_SwitchToAnim(item, BALDY_DIE_ANIM, 0);
|
Item_SwitchToAnim(item, BALDY_DIE_ANIM, 0);
|
||||||
if (Inv_RequestItem(O_SHOTGUN_ITEM)) {
|
|
||||||
Item_Spawn(item, O_SG_AMMO_ITEM);
|
|
||||||
} else {
|
|
||||||
Item_Spawn(item, O_SHOTGUN_ITEM);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AI_INFO info;
|
AI_INFO info;
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
#include "game/effects.h"
|
#include "game/effects.h"
|
||||||
#include "game/effects/gunshot.h"
|
#include "game/effects/gunshot.h"
|
||||||
#include "game/inventory.h"
|
|
||||||
#include "game/items.h"
|
#include "game/items.h"
|
||||||
#include "game/lot.h"
|
#include "game/lot.h"
|
||||||
#include "global/const.h"
|
#include "global/const.h"
|
||||||
|
@ -74,11 +73,6 @@ void Cowboy_Control(int16_t item_num)
|
||||||
if (item->current_anim_state != COWBOY_DEATH) {
|
if (item->current_anim_state != COWBOY_DEATH) {
|
||||||
item->current_anim_state = COWBOY_DEATH;
|
item->current_anim_state = COWBOY_DEATH;
|
||||||
Item_SwitchToAnim(item, COWBOY_DIE_ANIM, 0);
|
Item_SwitchToAnim(item, COWBOY_DIE_ANIM, 0);
|
||||||
if (Inv_RequestItem(O_MAGNUM_ITEM)) {
|
|
||||||
Item_Spawn(item, O_MAG_AMMO_ITEM);
|
|
||||||
} else {
|
|
||||||
Item_Spawn(item, O_MAGNUM_ITEM);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AI_INFO info;
|
AI_INFO info;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "game/objects/creatures/mummy.h"
|
#include "game/objects/creatures/mummy.h"
|
||||||
|
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
#include "game/gamebuf.h"
|
#include "game/gamebuf.h"
|
||||||
#include "game/items.h"
|
#include "game/items.h"
|
||||||
|
@ -67,6 +68,9 @@ void Mummy_Control(int16_t item_num)
|
||||||
g_GameInfo.current[g_CurrentLevel].stats.kill_count++;
|
g_GameInfo.current[g_CurrentLevel].stats.kill_count++;
|
||||||
}
|
}
|
||||||
Item_RemoveActive(item_num);
|
Item_RemoveActive(item_num);
|
||||||
|
if (item->hit_points != DONT_TARGET) {
|
||||||
|
Carrier_TestItemDrops(item_num);
|
||||||
|
}
|
||||||
item->hit_points = DONT_TARGET;
|
item->hit_points = DONT_TARGET;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
#include "game/inventory.h"
|
|
||||||
#include "game/items.h"
|
#include "game/items.h"
|
||||||
#include "game/los.h"
|
#include "game/los.h"
|
||||||
#include "game/lot.h"
|
#include "game/lot.h"
|
||||||
|
@ -114,13 +113,6 @@ void Pierre_Control(int16_t item_num)
|
||||||
if (item->current_anim_state != PIERRE_DEATH) {
|
if (item->current_anim_state != PIERRE_DEATH) {
|
||||||
item->current_anim_state = PIERRE_DEATH;
|
item->current_anim_state = PIERRE_DEATH;
|
||||||
Item_SwitchToAnim(item, PIERRE_DIE_ANIM, 0);
|
Item_SwitchToAnim(item, PIERRE_DIE_ANIM, 0);
|
||||||
if (Inv_RequestItem(O_MAGNUM_ITEM)) {
|
|
||||||
Item_Spawn(item, O_MAG_AMMO_ITEM);
|
|
||||||
} else {
|
|
||||||
Item_Spawn(item, O_MAGNUM_ITEM);
|
|
||||||
}
|
|
||||||
Item_Spawn(item, O_SCION_ITEM2);
|
|
||||||
Item_Spawn(item, O_KEY_ITEM1);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AI_INFO info;
|
AI_INFO info;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "game/objects/creatures/skate_kid.h"
|
#include "game/objects/creatures/skate_kid.h"
|
||||||
|
|
||||||
#include "game/creature.h"
|
#include "game/creature.h"
|
||||||
#include "game/inventory.h"
|
|
||||||
#include "game/items.h"
|
#include "game/items.h"
|
||||||
#include "game/lot.h"
|
#include "game/lot.h"
|
||||||
#include "game/music.h"
|
#include "game/music.h"
|
||||||
|
@ -86,11 +85,6 @@ void SkateKid_Control(int16_t item_num)
|
||||||
if (item->current_anim_state != SKATE_KID_DEATH) {
|
if (item->current_anim_state != SKATE_KID_DEATH) {
|
||||||
item->current_anim_state = SKATE_KID_DEATH;
|
item->current_anim_state = SKATE_KID_DEATH;
|
||||||
Item_SwitchToAnim(item, SKATE_KID_DIE_ANIM, 0);
|
Item_SwitchToAnim(item, SKATE_KID_DIE_ANIM, 0);
|
||||||
if (Inv_RequestItem(O_UZI_ITEM)) {
|
|
||||||
Item_Spawn(item, O_UZI_AMMO_ITEM);
|
|
||||||
} else {
|
|
||||||
Item_Spawn(item, O_UZI_ITEM);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
AI_INFO info;
|
AI_INFO info;
|
||||||
|
|
|
@ -126,31 +126,14 @@ static void Savegame_LoadPostprocess(void)
|
||||||
|
|
||||||
if (item->object_number == O_PIERRE && item->hit_points <= 0
|
if (item->object_number == O_PIERRE && item->hit_points <= 0
|
||||||
&& (item->flags & IF_ONESHOT)) {
|
&& (item->flags & IF_ONESHOT)) {
|
||||||
if (Inv_RequestItem(O_SCION_ITEM) == 1) {
|
|
||||||
Item_Spawn(item, O_MAGNUM_ITEM);
|
|
||||||
Item_Spawn(item, O_SCION_ITEM2);
|
|
||||||
Item_Spawn(item, O_KEY_ITEM1);
|
|
||||||
}
|
|
||||||
g_MusicTrackFlags[MX_PIERRE_SPEECH] |= IF_ONESHOT;
|
g_MusicTrackFlags[MX_PIERRE_SPEECH] |= IF_ONESHOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item->object_number == O_SKATEKID && item->hit_points <= 0) {
|
|
||||||
if (!Inv_RequestItem(O_UZI_ITEM)) {
|
|
||||||
Item_Spawn(item, O_UZI_ITEM);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item->object_number == O_COWBOY && item->hit_points <= 0) {
|
if (item->object_number == O_COWBOY && item->hit_points <= 0) {
|
||||||
if (!Inv_RequestItem(O_MAGNUM_ITEM)) {
|
|
||||||
Item_Spawn(item, O_MAGNUM_ITEM);
|
|
||||||
}
|
|
||||||
g_MusicTrackFlags[MX_COWBOY_SPEECH] |= IF_ONESHOT;
|
g_MusicTrackFlags[MX_COWBOY_SPEECH] |= IF_ONESHOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item->object_number == O_BALDY && item->hit_points <= 0) {
|
if (item->object_number == O_BALDY && item->hit_points <= 0) {
|
||||||
if (!Inv_RequestItem(O_SHOTGUN_ITEM)) {
|
|
||||||
Item_Spawn(item, O_SHOTGUN_ITEM);
|
|
||||||
}
|
|
||||||
g_MusicTrackFlags[MX_BALDY_SPEECH] |= IF_ONESHOT;
|
g_MusicTrackFlags[MX_BALDY_SPEECH] |= IF_ONESHOT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "game/savegame/savegame_bson.h"
|
#include "game/savegame/savegame_bson.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/effects.h"
|
#include "game/effects.h"
|
||||||
#include "game/gameflow.h"
|
#include "game/gameflow.h"
|
||||||
#include "game/inventory.h"
|
#include "game/inventory.h"
|
||||||
|
@ -586,6 +587,32 @@ static bool Savegame_BSON_LoadItems(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct json_array_s *carried_items =
|
||||||
|
json_object_get_array(item_obj, "carried_items");
|
||||||
|
if (carried_items) {
|
||||||
|
CARRIED_ITEM *carried_item = item->carried_item;
|
||||||
|
for (int j = 0; j < (signed)carried_items->length; j++) {
|
||||||
|
if (!carried_item) {
|
||||||
|
LOG_ERROR("Malformed save: carried item mismatch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct json_object_s *carried_item_obj =
|
||||||
|
json_array_get_object(carried_items, j);
|
||||||
|
|
||||||
|
carried_item->object_id = json_object_get_int(
|
||||||
|
carried_item_obj, "object_id", carried_item->object_id);
|
||||||
|
carried_item->status = json_object_get_int(
|
||||||
|
carried_item_obj, "status", carried_item->status);
|
||||||
|
|
||||||
|
carried_item = carried_item->next_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
Carrier_TestItemDrops(i);
|
||||||
|
} else if (header_version < VERSION_4) {
|
||||||
|
Carrier_TestLegacyDrops(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1050,6 +1077,28 @@ static struct json_array_s *Savegame_BSON_DumpItems(void)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct json_array_s *carried_items_arr = json_array_new();
|
||||||
|
|
||||||
|
CARRIED_ITEM *drop_item = item->carried_item;
|
||||||
|
while (drop_item) {
|
||||||
|
struct json_object_s *drop_obj = json_object_new();
|
||||||
|
json_object_append_int(drop_obj, "object_id", drop_item->object_id);
|
||||||
|
|
||||||
|
ITEM_STATUS status = drop_item->status;
|
||||||
|
if (status == IS_ACTIVE) {
|
||||||
|
ITEM_INFO *drop = &g_Items[drop_item->spawn_number];
|
||||||
|
if (drop->status == IS_INVISIBLE) {
|
||||||
|
status = IS_INVISIBLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
json_object_append_int(drop_obj, "status", status);
|
||||||
|
|
||||||
|
json_array_append_object(carried_items_arr, drop_obj);
|
||||||
|
drop_item = drop_item->next_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
json_object_append_array(item_obj, "carried_items", carried_items_arr);
|
||||||
|
|
||||||
json_array_append_object(items_arr, item_obj);
|
json_array_append_object(items_arr, item_obj);
|
||||||
}
|
}
|
||||||
return items_arr;
|
return items_arr;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "game/savegame/savegame_legacy.h"
|
#include "game/savegame/savegame_legacy.h"
|
||||||
|
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/effects.h"
|
#include "game/effects.h"
|
||||||
#include "game/gameflow.h"
|
#include "game/gameflow.h"
|
||||||
#include "game/inventory.h"
|
#include "game/inventory.h"
|
||||||
|
@ -600,6 +601,8 @@ bool Savegame_Legacy_LoadFromFile(MYFILE *fp, GAME_INFO *game_info)
|
||||||
item->data = NULL;
|
item->data = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Carrier_TestLegacyDrops(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
Savegame_Legacy_ReadLara(&g_Lara);
|
Savegame_Legacy_ReadLara(&g_Lara);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "game/stats.h"
|
#include "game/stats.h"
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
#include "game/carrier.h"
|
||||||
#include "game/clock.h"
|
#include "game/clock.h"
|
||||||
#include "game/game.h"
|
#include "game/game.h"
|
||||||
#include "game/gamebuf.h"
|
#include "game/gamebuf.h"
|
||||||
|
@ -19,11 +20,6 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#define PIERRE_ITEMS 3
|
|
||||||
#define SKATEKID_ITEMS 1
|
|
||||||
#define COWBOY_ITEMS 1
|
|
||||||
#define BALDY_ITEMS 1
|
|
||||||
|
|
||||||
typedef struct TOTAL_STATS {
|
typedef struct TOTAL_STATS {
|
||||||
uint32_t timer;
|
uint32_t timer;
|
||||||
uint32_t death_count;
|
uint32_t death_count;
|
||||||
|
@ -70,6 +66,7 @@ static void Stats_TraverseFloor(void);
|
||||||
static void Stats_CheckTriggers(
|
static void Stats_CheckTriggers(
|
||||||
ROOM_INFO *r, int room_num, int x_floor, int y_floor);
|
ROOM_INFO *r, int room_num, int x_floor, int y_floor);
|
||||||
static bool Stats_IsObjectKillable(int32_t obj_num);
|
static bool Stats_IsObjectKillable(int32_t obj_num);
|
||||||
|
static void Stats_IncludeKillableItem(int16_t item_num);
|
||||||
|
|
||||||
static void Stats_TraverseFloor(void)
|
static void Stats_TraverseFloor(void)
|
||||||
{
|
{
|
||||||
|
@ -148,9 +145,7 @@ static void Stats_CheckTriggers(
|
||||||
// Add Pierre pickup and kills if oneshot
|
// Add Pierre pickup and kills if oneshot
|
||||||
if (item->object_number == O_PIERRE
|
if (item->object_number == O_PIERRE
|
||||||
&& trig_flags & IF_ONESHOT) {
|
&& trig_flags & IF_ONESHOT) {
|
||||||
m_KillableItems[idx] = true;
|
Stats_IncludeKillableItem(idx);
|
||||||
m_LevelPickups += PIERRE_ITEMS;
|
|
||||||
m_LevelKillables += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for only valid pods
|
// Check for only valid pods
|
||||||
|
@ -160,26 +155,13 @@ static void Stats_CheckTriggers(
|
||||||
int16_t bug_item_num = *(int16_t *)item->data;
|
int16_t bug_item_num = *(int16_t *)item->data;
|
||||||
const ITEM_INFO *bug_item = &g_Items[bug_item_num];
|
const ITEM_INFO *bug_item = &g_Items[bug_item_num];
|
||||||
if (g_Objects[bug_item->object_number].loaded) {
|
if (g_Objects[bug_item->object_number].loaded) {
|
||||||
m_KillableItems[idx] = true;
|
Stats_IncludeKillableItem(idx);
|
||||||
m_LevelKillables += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add killable if object triggered
|
// Add killable if object triggered
|
||||||
if (Stats_IsObjectKillable(item->object_number)) {
|
if (Stats_IsObjectKillable(item->object_number)) {
|
||||||
m_KillableItems[idx] = true;
|
Stats_IncludeKillableItem(idx);
|
||||||
m_LevelKillables += 1;
|
|
||||||
|
|
||||||
// Add mercenary pickups
|
|
||||||
if (item->object_number == O_SKATEKID) {
|
|
||||||
m_LevelPickups += SKATEKID_ITEMS;
|
|
||||||
}
|
|
||||||
if (item->object_number == O_COWBOY) {
|
|
||||||
m_LevelPickups += COWBOY_ITEMS;
|
|
||||||
}
|
|
||||||
if (item->object_number == O_BALDY) {
|
|
||||||
m_LevelPickups += BALDY_ITEMS;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!(trigger & END_BIT));
|
} while (!(trigger & END_BIT));
|
||||||
|
@ -198,6 +180,13 @@ static bool Stats_IsObjectKillable(int32_t obj_num)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void Stats_IncludeKillableItem(int16_t item_num)
|
||||||
|
{
|
||||||
|
m_KillableItems[item_num] = true;
|
||||||
|
m_LevelKillables += 1;
|
||||||
|
m_LevelPickups += Carrier_GetItemCount(item_num);
|
||||||
|
}
|
||||||
|
|
||||||
static void Stats_ComputeTotal(GAMEFLOW_LEVEL_TYPE level_type)
|
static void Stats_ComputeTotal(GAMEFLOW_LEVEL_TYPE level_type)
|
||||||
{
|
{
|
||||||
memset(&m_TotalStats, 0, sizeof(TOTAL_STATS));
|
memset(&m_TotalStats, 0, sizeof(TOTAL_STATS));
|
||||||
|
|
|
@ -1369,6 +1369,13 @@ typedef struct ROOM_INFO {
|
||||||
uint16_t flags;
|
uint16_t flags;
|
||||||
} ROOM_INFO;
|
} ROOM_INFO;
|
||||||
|
|
||||||
|
typedef struct CARRIED_ITEM {
|
||||||
|
GAME_OBJECT_ID object_id;
|
||||||
|
int16_t spawn_number;
|
||||||
|
enum ITEM_STATUS status;
|
||||||
|
struct CARRIED_ITEM *next_item;
|
||||||
|
} CARRIED_ITEM;
|
||||||
|
|
||||||
typedef struct ITEM_INFO {
|
typedef struct ITEM_INFO {
|
||||||
int32_t floor;
|
int32_t floor;
|
||||||
uint32_t touch_bits;
|
uint32_t touch_bits;
|
||||||
|
@ -1391,6 +1398,7 @@ typedef struct ITEM_INFO {
|
||||||
int16_t shade;
|
int16_t shade;
|
||||||
void *data;
|
void *data;
|
||||||
void *priv;
|
void *priv;
|
||||||
|
CARRIED_ITEM *carried_item;
|
||||||
PHD_3DPOS pos;
|
PHD_3DPOS pos;
|
||||||
uint16_t active : 1;
|
uint16_t active : 1;
|
||||||
uint16_t status : 2;
|
uint16_t status : 2;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue