diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index 593d36259..939563cb3 100644
--- a/autogen/convert_functions.py
+++ b/autogen/convert_functions.py
@@ -725,6 +725,86 @@ Shoots a raycast from `startX`, `startY`, and `startZ` in the direction of `dirX
+## [set_exclamation_box_contents](#set_exclamation_box_contents)
+
+Sets the contents that the exclamation box spawns. A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+* `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+* `unused`: Optional; unused by vanilla.
+* `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+* `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+* `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+
+### Lua Example
+```lua
+set_exclamation_box_contents({
+ {id = 0, unused = 0, firstByte = 0, model = E_MODEL_GOOMBA, behavior = id_bhvGoomba}, -- Uses both optional fields
+ {id = 1, unused = 0, model = E_MODEL_KOOPA_WITH_SHELL, behavior = id_bhvKoopa}, -- Only uses `unused` optional field
+ {id = 2, firsteByte = model = E_MODEL_BLACK_BOBOMB, behavior = id_bhvBobomb}, -- Only uses `firstByte` optional field
+ {id = 3, model = E_MODEL_BOO, behavior = id_bhvBoo}, -- Uses no optional fields
+})
+```
+
+### Parameters
+There exists only 1 parameter to this function which is the main table. However, each subtable has 5 different keys that could be accessed.
+| Field | Type |
+| ----- | ---- |
+| id | `integer` |
+| unused (Optional) | `integer` |
+| firstByte (Optional) | `integer` |
+| model | [ModelExtendedId](#ModelExtendedId) |
+| behavior | [BehaviorId](#BehaviorId) |
+
+### Returns
+- None
+
+### C Prototype
+N/A
+
+[:arrow_up_small:](#)
+
+
+
+## [get_exclamation_box_contents](#get_exclamation_box_contents)
+
+Gets the contents that the exclamation box spawns. A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+* `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+* `unused`: Optional; unused by vanilla.
+* `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+* `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+* `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+
+### Lua Example
+```lua
+local contents = get_exclamation_box_contents()
+for index, content in pairs(contents) do -- Enter the main table
+ djui_chat_message_create("Table index " .. index) -- Print the current table index
+ for key, value in pairs(content) do
+ djui_chat_message_create(key .. ": " .. value) -- Print a key-value pair within this subtable
+ end
+ djui_chat_message_create("---------------------------------") -- Separator
+end
+```
+
+### Parameters
+- N/A
+
+### Returns
+The function itself does not return every key/value pair. Instead it returns the main table which holds all the subtables that hold each key/value pair.
+| Field | Type |
+| ----- | ---- |
+| id | `integer` |
+| unused (Optional) | `integer` |
+| firstByte (Optional) | `integer` |
+| model | [ModelExtendedId](#ModelExtendedId) |
+| behavior | [BehaviorId](#BehaviorId) |
+
+### C Prototype
+N/A
+
+[:arrow_up_small:](#)
+
+
+
"""
############################################################################
diff --git a/autogen/lua_definitions/manual.lua b/autogen/lua_definitions/manual.lua
index 5f4478243..4c8063063 100644
--- a/autogen/lua_definitions/manual.lua
+++ b/autogen/lua_definitions/manual.lua
@@ -388,3 +388,27 @@ end
function collision_find_surface_on_ray(startX, startY, startZ, dirX, dirY, dirZ, precision)
-- ...
end
+
+--- @param contents ExclamationBoxContent[]
+--- Sets the contents that the exclamation box spawns.
+--- A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+--- * `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+--- * `unused`: Optional; unused by vanilla.
+--- * `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+--- * `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+--- * `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+function set_exclamation_box_contents(contents)
+ -- ...
+end
+
+--- @return ExclamationBoxContent[]
+--- Gets the contents that the exclamation box spawns.
+--- A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+--- * `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+--- * `unused`: Optional; unused by vanilla.
+--- * `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+--- * `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+--- * `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+function get_exclamation_box_contents()
+ -- ...
+end
diff --git a/autogen/lua_definitions/structs.lua b/autogen/lua_definitions/structs.lua
index b9f0e26a4..d3fd460fb 100644
--- a/autogen/lua_definitions/structs.lua
+++ b/autogen/lua_definitions/structs.lua
@@ -590,6 +590,13 @@
--- @field public g integer
--- @field public r integer
+--- @class ExclamationBoxContent
+--- @field public behavior BehaviorId
+--- @field public firstByte integer
+--- @field public id integer
+--- @field public model ModelExtendedId
+--- @field public unused integer
+
--- @class FirstPersonCamera
--- @field public centerL boolean
--- @field public crouch number
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index 782bf43c4..9f28fc269 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -2323,6 +2323,86 @@ Shoots a raycast from `startX`, `startY`, and `startZ` in the direction of `dirX
+## [set_exclamation_box_contents](#set_exclamation_box_contents)
+
+Sets the contents that the exclamation box spawns. A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+* `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+* `unused`: Optional; unused by vanilla.
+* `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+* `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+* `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+
+### Lua Example
+```lua
+set_exclamation_box_contents({
+ {id = 0, unused = 0, firstByte = 0, model = E_MODEL_GOOMBA, behavior = id_bhvGoomba}, -- Uses both optional fields
+ {id = 1, unused = 0, model = E_MODEL_KOOPA_WITH_SHELL, behavior = id_bhvKoopa}, -- Only uses `unused` optional field
+ {id = 2, firsteByte = model = E_MODEL_BLACK_BOBOMB, behavior = id_bhvBobomb}, -- Only uses `firstByte` optional field
+ {id = 3, model = E_MODEL_BOO, behavior = id_bhvBoo}, -- Uses no optional fields
+})
+```
+
+### Parameters
+There exists only 1 parameter to this function which is the main table. However, each subtable has 5 different keys that could be accessed.
+| Field | Type |
+| ----- | ---- |
+| id | `integer` |
+| unused (Optional) | `integer` |
+| firstByte (Optional) | `integer` |
+| model | [ModelExtendedId](#ModelExtendedId) |
+| behavior | [BehaviorId](#BehaviorId) |
+
+### Returns
+- None
+
+### C Prototype
+N/A
+
+[:arrow_up_small:](#)
+
+
+
+## [get_exclamation_box_contents](#get_exclamation_box_contents)
+
+Gets the contents that the exclamation box spawns. A single content has 5 keys: `id`, `unused`, `firstByte`, `model`, and `behavior`.
+* `id`: Required; what value the box's oBehParams2ndByte needs to be to spawn this object.
+* `unused`: Optional; unused by vanilla.
+* `firstByte`: Optional; Overrides the 1st byte given to the spawned object.
+* `model`: Required; The model that the object will spawn with. Uses `ModelExtendedId`.
+* `behavior`: Required; The behavior ID that the object will spawn with. Uses `BehaviorId`.
+
+### Lua Example
+```lua
+local contents = get_exclamation_box_contents()
+for index, content in pairs(contents) do -- Enter the main table
+ djui_chat_message_create("Table index " .. index) -- Print the current table index
+ for key, value in pairs(content) do
+ djui_chat_message_create(key .. ": " .. value) -- Print a key-value pair within this subtable
+ end
+ djui_chat_message_create("---------------------------------") -- Separator
+end
+```
+
+### Parameters
+- N/A
+
+### Returns
+The function itself does not return every key/value pair. Instead it returns the main table which holds all the subtables that hold each key/value pair.
+| Field | Type |
+| ----- | ---- |
+| id | `integer` |
+| unused (Optional) | `integer` |
+| firstByte (Optional) | `integer` |
+| model | [ModelExtendedId](#ModelExtendedId) |
+| behavior | [BehaviorId](#BehaviorId) |
+
+### C Prototype
+N/A
+
+[:arrow_up_small:](#)
+
+
+
---
# functions from area.h
diff --git a/docs/lua/structs.md b/docs/lua/structs.md
index f15b1b0ea..112aec3a0 100644
--- a/docs/lua/structs.md
+++ b/docs/lua/structs.md
@@ -24,6 +24,7 @@
- [CutsceneVariable](#CutsceneVariable)
- [DateTime](#DateTime)
- [DjuiColor](#DjuiColor)
+- [ExclamationBoxContent](#ExclamationBoxContent)
- [FirstPersonCamera](#FirstPersonCamera)
- [FloorGeometry](#FloorGeometry)
- [GlobalObjectAnimations](#GlobalObjectAnimations)
@@ -844,6 +845,20 @@
+## [ExclamationBoxContent](#ExclamationBoxContent)
+
+| Field | Type | Access |
+| ----- | ---- | ------ |
+| behavior | [enum BehaviorId](constants.md#enum-BehaviorId) | |
+| firstByte | `integer` | |
+| id | `integer` | |
+| model | [enum ModelExtendedId](constants.md#enum-ModelExtendedId) | |
+| unused | `integer` | |
+
+[:arrow_up_small:](#)
+
+
+
## [FirstPersonCamera](#FirstPersonCamera)
| Field | Type | Access |
diff --git a/src/game/behavior_actions.c b/src/game/behavior_actions.c
index f2d4f60c8..1a9d34b4b 100644
--- a/src/game/behavior_actions.c
+++ b/src/game/behavior_actions.c
@@ -45,9 +45,9 @@
#include "spawn_sound.h"
#include "game/rng_position.h"
#include "rumble_init.h"
-#include "hardcoded.h"
#include "pc/lua/utils/smlua_model_utils.h"
#include "pc/lua/smlua_hooks.h"
+#include "hardcoded.h"
#define o gCurrentObject
@@ -74,14 +74,6 @@ struct Struct8032F698 {
s16 unk4;
};
-struct Struct802C0DF0 {
- u8 unk0;
- u8 unk1;
- u8 unk2;
- u8 model;
- const BehaviorScript *behavior;
-};
-
struct Struct8032F754 {
s32 unk0;
Vec3f unk1;
diff --git a/src/game/behaviors/exclamation_box.inc.c b/src/game/behaviors/exclamation_box.inc.c
index 96d939f08..e206eeb0e 100644
--- a/src/game/behaviors/exclamation_box.inc.c
+++ b/src/game/behaviors/exclamation_box.inc.c
@@ -12,24 +12,6 @@ struct ObjectHitbox sExclamationBoxHitbox = {
/* hurtboxHeight: */ 30,
};
-// hack: if any other sync objects get added here we have to check for them (search for hack in this file)
-struct Struct802C0DF0 sExclamationBoxContents[] = { { 0, 0, 0, MODEL_MARIOS_WING_CAP, bhvWingCap },
- { 1, 0, 0, MODEL_MARIOS_METAL_CAP, bhvMetalCap },
- { 2, 0, 0, MODEL_MARIOS_CAP, bhvVanishCap },
- { 3, 0, 0, MODEL_KOOPA_SHELL, bhvKoopaShell },
- { 4, 0, 0, MODEL_YELLOW_COIN, bhvSingleCoinGetsSpawned },
- { 5, 0, 0, MODEL_NONE, bhvThreeCoinsSpawn },
- { 6, 0, 0, MODEL_NONE, bhvTenCoinsSpawn },
- { 7, 0, 0, MODEL_1UP, bhv1upWalking },
- { 8, 0, 0, MODEL_STAR, bhvSpawnedStar },
- { 9, 0, 0, MODEL_1UP, bhv1upRunningAway },
- { 10, 0, 1, MODEL_STAR, bhvSpawnedStar },
- { 11, 0, 2, MODEL_STAR, bhvSpawnedStar },
- { 12, 0, 3, MODEL_STAR, bhvSpawnedStar },
- { 13, 0, 4, MODEL_STAR, bhvSpawnedStar },
- { 14, 0, 5, MODEL_STAR, bhvSpawnedStar },
- { 99, 0, 0, 0, NULL } };
-
void bhv_rotating_exclamation_box_loop(void) {
if (!o->parentObj || o->parentObj->oAction != 1)
obj_mark_for_deletion(o);
@@ -122,7 +104,8 @@ static s32 exclamation_replace_model(struct MarioState* m, s32 model) {
}
}
-void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
+void exclamation_box_spawn_contents(struct ExclamationBoxContent *content, u8 itemId) {
+ if (content == NULL) { return; }
struct MarioState* marioState = nearest_mario_state_to_object(o);
struct Object* player = marioState ? marioState->marioObj : NULL;
struct Object *spawnedObject = NULL;
@@ -131,11 +114,11 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
return;
}
- while (a0->unk0 != 99) {
- if (a1 == a0->unk0) {
- s32 model = exclamation_replace_model(marioState, a0->model);
+ for (u8 i = 0; i < gExclamationBoxSize; i++) {
+ if (itemId == content->id) {
+ s32 model = exclamation_replace_model(marioState, smlua_model_util_load(content->model));
- spawnedObject = spawn_object(o, model, a0->behavior);
+ spawnedObject = spawn_object(o, model, get_behavior_from_id(content->behavior));
if (spawnedObject != NULL) {
spawnedObject->oVelY = 20.0f;
spawnedObject->oForwardVel = 3.0f;
@@ -144,15 +127,15 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
spawnedObject->globalPlayerIndex = player->globalPlayerIndex;
}
}
- o->oBehParams |= a0->unk2 << 24;
- if (a0->model == 122)
- o->oFlags |= 0x4000;
+ o->oBehParams |= content->firstByte << 24;
+ if (content->model == E_MODEL_STAR)
+ o->oFlags |= OBJ_FLAG_PERSISTENT_RESPAWN;
// send non-star spawn events
// stars cant be sent here to due jankiness in oBehParams
- if (a0->behavior != smlua_override_behavior(bhvSpawnedStar) && spawnedObject != NULL) {
+ if (content->behavior != get_id_from_behavior(smlua_override_behavior(bhvSpawnedStar)) && spawnedObject != NULL) {
// hack: if any other sync objects get spawned here we have to check for them
- if (a0->behavior == smlua_override_behavior(bhvKoopaShell)) {
+ if (content->behavior == get_id_from_behavior(smlua_override_behavior(bhvKoopaShell))) {
sync_object_set_id(spawnedObject);
}
struct Object* spawn_objects[] = { spawnedObject };
@@ -161,12 +144,12 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
}
break;
}
- a0++;
+ content++;
}
}
void exclamation_box_act_4(void) {
- exclamation_box_spawn_contents(sExclamationBoxContents, o->oBehParams2ndByte);
+ exclamation_box_spawn_contents(gExclamationBoxContents, o->oBehParams2ndByte);
spawn_mist_particles_variable(0, 0, 46.0f);
spawn_triangle_break_particles(20, 139, 0.3f, o->oAnimState);
create_sound_spawner(SOUND_GENERAL_BREAK_BOX);
diff --git a/src/game/hardcoded.c b/src/game/hardcoded.c
index c6e2d707c..b07c56be2 100644
--- a/src/game/hardcoded.c
+++ b/src/game/hardcoded.c
@@ -287,6 +287,32 @@ struct BehaviorValues gDefaultBehaviorValues = {
struct BehaviorValues gBehaviorValues = { 0 };
+struct ExclamationBoxContent sDefaultExclamationBoxContents[] = {
+ { 0, 0, 0, E_MODEL_MARIOS_WING_CAP, id_bhvWingCap },
+ { 1, 0, 0, E_MODEL_MARIOS_METAL_CAP, id_bhvMetalCap },
+ { 2, 0, 0, E_MODEL_MARIOS_CAP, id_bhvVanishCap },
+ { 3, 0, 0, E_MODEL_KOOPA_SHELL, id_bhvKoopaShell },
+ { 4, 0, 0, E_MODEL_YELLOW_COIN, id_bhvSingleCoinGetsSpawned },
+ { 5, 0, 0, E_MODEL_NONE, id_bhvThreeCoinsSpawn },
+ { 6, 0, 0, E_MODEL_NONE, id_bhvTenCoinsSpawn },
+ { 7, 0, 0, E_MODEL_1UP, id_bhv1upWalking },
+ { 8, 0, 0, E_MODEL_STAR, id_bhvSpawnedStar },
+ { 9, 0, 0, E_MODEL_1UP, id_bhv1upRunningAway },
+ { 10, 0, 1, E_MODEL_STAR, id_bhvSpawnedStar },
+ { 11, 0, 2, E_MODEL_STAR, id_bhvSpawnedStar },
+ { 12, 0, 3, E_MODEL_STAR, id_bhvSpawnedStar },
+ { 13, 0, 4, E_MODEL_STAR, id_bhvSpawnedStar },
+ { 14, 0, 5, E_MODEL_STAR, id_bhvSpawnedStar }
+};
+
+// Hack: Create 2 arrays: one that is constantly default and one that can be changed.
+
+struct ExclamationBoxContent sDummyContents[EXCLAMATION_BOX_MAX_SIZE];
+
+struct ExclamationBoxContent* gExclamationBoxContents = sDummyContents;
+
+u8 gExclamationBoxSize = 15;
+
//////////////
// Painting //
//////////////
@@ -336,6 +362,10 @@ AT_STARTUP void hardcoded_reset_default_values(void) {
memcpy(&sl_painting, &default_sl_painting, sizeof(struct Painting));
memcpy(&thi_huge_painting, &default_thi_huge_painting, sizeof(struct Painting));
memcpy(&ttm_slide_painting, &default_ttm_slide_painting, sizeof(struct Painting));
+ memcpy(sDummyContents, sDefaultExclamationBoxContents, sizeof(struct ExclamationBoxContent) * 15);
+
+ gExclamationBoxContents = sDummyContents;
+ gExclamationBoxSize = 15;
gPaintingValues = gDefaultPaintingValues;
}
diff --git a/src/game/hardcoded.h b/src/game/hardcoded.h
index 98aec62e2..54de71a01 100644
--- a/src/game/hardcoded.h
+++ b/src/game/hardcoded.h
@@ -5,6 +5,8 @@
#include "dialog_ids.h"
#include "seq_ids.h"
#include "paintings.h"
+#include "pc/lua/utils/smlua_model_utils.h"
+#include "include/behavior_table.h"
////////////
// Levels //
@@ -266,6 +268,19 @@ struct BehaviorValues {
extern struct BehaviorValues gBehaviorValues;
+#define EXCLAMATION_BOX_MAX_SIZE 99
+
+struct ExclamationBoxContent {
+ u8 id;
+ u8 unused;
+ u8 firstByte;
+ enum ModelExtendedId model;
+ enum BehaviorId behavior;
+};
+
+extern struct ExclamationBoxContent* gExclamationBoxContents;
+extern u8 gExclamationBoxSize;
+
//////////////
// Painting //
//////////////
diff --git a/src/pc/lua/smlua_cobject_autogen.c b/src/pc/lua/smlua_cobject_autogen.c
index b2ab63b8c..6ad328248 100644
--- a/src/pc/lua/smlua_cobject_autogen.c
+++ b/src/pc/lua/smlua_cobject_autogen.c
@@ -677,6 +677,15 @@ static struct LuaObjectField sDjuiColorFields[LUA_DJUI_COLOR_FIELD_COUNT] = {
{ "r", LVT_U8, offsetof(struct DjuiColor, r), false, LOT_NONE },
};
+#define LUA_EXCLAMATION_BOX_CONTENT_FIELD_COUNT 5
+static struct LuaObjectField sExclamationBoxContentFields[LUA_EXCLAMATION_BOX_CONTENT_FIELD_COUNT] = {
+ { "behavior", LVT_S32, offsetof(struct ExclamationBoxContent, behavior), false, LOT_NONE },
+ { "firstByte", LVT_U8, offsetof(struct ExclamationBoxContent, firstByte), false, LOT_NONE },
+ { "id", LVT_U8, offsetof(struct ExclamationBoxContent, id), false, LOT_NONE },
+ { "model", LVT_S32, offsetof(struct ExclamationBoxContent, model), false, LOT_NONE },
+ { "unused", LVT_U8, offsetof(struct ExclamationBoxContent, unused), false, LOT_NONE },
+};
+
#define LUA_FIRST_PERSON_CAMERA_FIELD_COUNT 10
static struct LuaObjectField sFirstPersonCameraFields[LUA_FIRST_PERSON_CAMERA_FIELD_COUNT] = {
{ "centerL", LVT_BOOL, offsetof(struct FirstPersonCamera, centerL), false, LOT_NONE },
@@ -2460,6 +2469,7 @@ struct LuaObjectTable sLuaObjectAutogenTable[LOT_AUTOGEN_MAX - LOT_AUTOGEN_MIN]
{ LOT_CUTSCENEVARIABLE, sCutsceneVariableFields, LUA_CUTSCENE_VARIABLE_FIELD_COUNT },
{ LOT_DATETIME, sDateTimeFields, LUA_DATE_TIME_FIELD_COUNT },
{ LOT_DJUICOLOR, sDjuiColorFields, LUA_DJUI_COLOR_FIELD_COUNT },
+ { LOT_EXCLAMATIONBOXCONTENT, sExclamationBoxContentFields, LUA_EXCLAMATION_BOX_CONTENT_FIELD_COUNT },
{ LOT_FIRSTPERSONCAMERA, sFirstPersonCameraFields, LUA_FIRST_PERSON_CAMERA_FIELD_COUNT },
{ LOT_FLOORGEOMETRY, sFloorGeometryFields, LUA_FLOOR_GEOMETRY_FIELD_COUNT },
{ LOT_GLOBALOBJECTANIMATIONS, sGlobalObjectAnimationsFields, LUA_GLOBAL_OBJECT_ANIMATIONS_FIELD_COUNT },
diff --git a/src/pc/lua/smlua_cobject_autogen.h b/src/pc/lua/smlua_cobject_autogen.h
index 0ca897f14..f08674bfa 100644
--- a/src/pc/lua/smlua_cobject_autogen.h
+++ b/src/pc/lua/smlua_cobject_autogen.h
@@ -27,6 +27,7 @@ enum LuaObjectAutogenType {
LOT_CUTSCENEVARIABLE,
LOT_DATETIME,
LOT_DJUICOLOR,
+ LOT_EXCLAMATIONBOXCONTENT,
LOT_FIRSTPERSONCAMERA,
LOT_FLOORGEOMETRY,
LOT_GLOBALOBJECTANIMATIONS,
diff --git a/src/pc/lua/smlua_functions.c b/src/pc/lua/smlua_functions.c
index b722976b1..dc3ea3b9b 100644
--- a/src/pc/lua/smlua_functions.c
+++ b/src/pc/lua/smlua_functions.c
@@ -15,6 +15,7 @@
#include "include/macro_presets.h"
#include "utils/smlua_anim_utils.h"
#include "utils/smlua_collision_utils.h"
+#include "game/hardcoded.h"
bool smlua_functions_valid_param_count(lua_State* L, int expected) {
int top = lua_gettop(L);
@@ -179,6 +180,110 @@ int smlua_func_network_send_to(lua_State* L) {
return 1;
}
+int smlua_func_set_exclamation_box_contents(lua_State* L) {
+ if (!smlua_functions_valid_param_count(L, 1)) { return 0; }
+
+ if (lua_type(L, 1) != LUA_TTABLE) {
+ LOG_LUA_LINE("Invalid type passed to set_exclamation_box(): %u", lua_type(L, -1));
+ return 0;
+ }
+
+ struct ExclamationBoxContent exclamationBoxNewContents[EXCLAMATION_BOX_MAX_SIZE];
+
+ u8 exclamationBoxIndex = 0;
+ lua_pushnil(L); // Initial pop
+ while (lua_next(L, 1)) /* Main table index */ {
+ if (lua_type(L, 3) != LUA_TTABLE) {
+ LOG_LUA_LINE("set_exclamation_box: Subtable is not a table (Subtable %u)", exclamationBoxIndex);
+ return 0;
+ }
+
+ lua_pushnil(L); // Subtable initial pop
+ bool confirm[] = { false, false, false, false, false }; /* id, unused, firstByte, model, behavior */
+ while (lua_next(L, 3)) /* Subtable index */ {
+ // key is index -2, value is index -1
+ const char* key = smlua_to_string(L, -2);
+ if (!gSmLuaConvertSuccess) {
+ LOG_LUA("set_exclamation_box: Failed to convert subtable key");
+ return 0;
+ }
+
+ s32 value = smlua_to_integer(L, -1);
+ if (!gSmLuaConvertSuccess) {
+ LOG_LUA("set_exclamation_box: Failed to convert subtable value");
+ return 0;
+ }
+
+ // Fill fields
+ if (strcmp(key, "id") == 0) { exclamationBoxNewContents[exclamationBoxIndex].id = value; confirm[0] = true; }
+ else if (strcmp(key, "unused") == 0) { exclamationBoxNewContents[exclamationBoxIndex].unused = value; confirm[1] = true; }
+ else if (strcmp(key, "firstByte") == 0) { exclamationBoxNewContents[exclamationBoxIndex].firstByte = value; confirm[2] = true; }
+ else if (strcmp(key, "model") == 0) { exclamationBoxNewContents[exclamationBoxIndex].model = value; confirm[3] = true; }
+ else if (strcmp(key, "behavior") == 0) { exclamationBoxNewContents[exclamationBoxIndex].behavior = value; confirm[4] = true; }
+ else {
+ LOG_LUA_LINE_WARNING("set_exclamation_box: Invalid key passed (Subtable %d)", exclamationBoxIndex);
+ }
+
+ lua_pop(L, 1); // Pop value
+ }
+ // Check if the fields have been filled
+ if (!(confirm[0]) || !(confirm[3]) || !(confirm[4])) {
+ LOG_LUA("set_exclamation_box: A critical component of a content (id, model, or behavior) has not been set (Subtable %d)", exclamationBoxIndex);
+ return 0;
+ }
+ if (!(confirm[1])) { exclamationBoxNewContents[exclamationBoxIndex].unused = 0; }
+ if (!(confirm[2])) { exclamationBoxNewContents[exclamationBoxIndex].firstByte = 0; }
+
+ if (++exclamationBoxIndex == EXCLAMATION_BOX_MAX_SIZE) { // There is an edge case where the 254th element will warn even though it works just fine
+ // Immediately exit if at risk for out of bounds array access.
+ lua_pop(L, 1);
+ LOG_LUA_LINE_WARNING("set_exclamation_box: Too many items have been set for the exclamation box. Some content spawns may be lost.");
+ break;
+ }
+ lua_pop(L, 1); // Pop subtable
+ }
+
+ memcpy(gExclamationBoxContents, exclamationBoxNewContents, sizeof(struct ExclamationBoxContent) * exclamationBoxIndex);
+ gExclamationBoxSize = exclamationBoxIndex;
+
+ return 1;
+}
+
+int smlua_func_get_exclamation_box_contents(lua_State* L) {
+ if (!smlua_functions_valid_param_count(L, 0)) { return 0; }
+
+ lua_newtable(L); // Index 1
+
+ for (u8 i = 0; i < gExclamationBoxSize; i++) {
+ lua_pushinteger(L, i); // Index 2
+ lua_newtable(L); // Index 3
+
+ lua_pushstring(L, "id");
+ lua_pushinteger(L, gExclamationBoxContents[i].id);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "unused");
+ lua_pushinteger(L, gExclamationBoxContents[i].unused);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "firstByte");
+ lua_pushinteger(L, gExclamationBoxContents[i].firstByte);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "model");
+ lua_pushinteger(L, gExclamationBoxContents[i].model);
+ lua_settable(L, -3);
+
+ lua_pushstring(L, "behavior");
+ lua_pushinteger(L, gExclamationBoxContents[i].behavior);
+ lua_settable(L, -3);
+
+ lua_settable(L, 1); // Insert the subtable into the main table
+ }
+
+ return 1;
+}
+
//////////////
// Textures //
//////////////
@@ -855,6 +960,8 @@ void smlua_bind_functions(void) {
smlua_bind_function(L, "reset_level", smlua_func_reset_level);
smlua_bind_function(L, "network_send", smlua_func_network_send);
smlua_bind_function(L, "network_send_to", smlua_func_network_send_to);
+ smlua_bind_function(L, "set_exclamation_box_contents", smlua_func_set_exclamation_box_contents);
+ smlua_bind_function(L, "get_exclamation_box_contents", smlua_func_get_exclamation_box_contents);
smlua_bind_function(L, "get_texture_info", smlua_func_get_texture_info);
smlua_bind_function(L, "djui_hud_render_texture", smlua_func_djui_hud_render_texture);
smlua_bind_function(L, "djui_hud_render_texture_tile", smlua_func_djui_hud_render_texture_tile);