From af2a80007a10067dbe31cc0f7a736e65770eeee2 Mon Sep 17 00:00:00 2001
From: Sunk <69110309+Sunketchupm@users.noreply.github.com>
Date: Tue, 18 Jul 2023 18:18:02 -0400
Subject: [PATCH] Add set_exclamation_box_contents() (#445)
* Properly set Mario's y vel to 0 on popping
* Change 0 to 0.0f, just in case
* Re-introduce a few vanilla bugs under gBehaviorValues
The Shell Mario glitch was patched as a side effect to patching a different bug, but several romhacks use it so I need it back.
The ability to collect multiple normal caps at once is needed for hat-in-hand using the hat factory glitch.
* Fix Shell Mario fix
Found the actual reason why the glitch doesn't work and figured that this change shouldn't affect anything else, so I removed its entry from gBehaviorValues.
* Add InfiniteRenderDistance to gBehaviorValues
I'm well aware that disabling the infinite render distance will be very desync prone, however a few glitches, most notably cloning and chuckya double jump, need objects load and unload from render distance.
* Allow mods to disable the camera centering from romhack camera
* Allow mods to disable romhack camera centering
Done again
* Update on network shutdown
* Remove a line which I have no idea why it returned
* Add set_exclamation_box_contents()
No way this is memory safe or even well made but I did what I could
* Added (most of) peachy's suggestions
Still need to figure out how to stop the game from reading further than the size of the array without using a hardcoded number
* Added more of peachy's suggestions
I figured a good way to cap how far the exclamation box reads is to pass in the length of the array as well
---
autogen/convert_constants.py | 2 +-
autogen/convert_functions.py | 4 +-
autogen/convert_structs.py | 5 +-
autogen/lua_definitions/constants.lua | 4 +-
autogen/lua_definitions/functions.lua | 9 ++-
autogen/lua_definitions/manual.lua | 12 +++
autogen/lua_definitions/structs.lua | 12 ++-
docs/lua/functions-5.md | 18 +++++
docs/lua/functions.md | 1 +
docs/lua/structs.md | 15 ++++
src/game/behavior_actions.c | 6 ++
src/game/behaviors/exclamation_box.inc.c | 11 ++-
src/pc/lua/smlua.c | 3 +
src/pc/lua/smlua_functions.c | 94 ++++++++++++++++++++++++
src/pc/lua/smlua_functions_autogen.c | 16 ++++
src/pc/lua/utils/smlua_obj_utils.c | 21 ++++++
src/pc/lua/utils/smlua_obj_utils.h | 13 ++++
17 files changed, 234 insertions(+), 12 deletions(-)
diff --git a/autogen/convert_constants.py b/autogen/convert_constants.py
index ebe19add..020e0312 100644
--- a/autogen/convert_constants.py
+++ b/autogen/convert_constants.py
@@ -339,7 +339,7 @@ def def_constant(processed_constant):
return s
def build_to_def(processed_files):
- s = '-- AUTOGENERATED FOR CODE EDITORS --\n\n'
+ s = '-- AUTOGENERATED FOR CODE EDITORS -- \n--- @meta\n--- @diagnostic disable\n\n'
with open(get_path(in_filename), 'r', newline='\n') as f:
s += f.read()
s += '\n'
diff --git a/autogen/convert_functions.py b/autogen/convert_functions.py
index d8e381ba..73fc9370 100644
--- a/autogen/convert_functions.py
+++ b/autogen/convert_functions.py
@@ -102,7 +102,7 @@ override_disallowed_functions = {
"src/game/obj_behaviors.c": [ "debug_" ],
"src/game/obj_behaviors_2.c": [ "wiggler_jumped_on_attack_handler", "huge_goomba_weakly_attacked" ],
"src/game/spawn_sound.c": [ "spawner" ],
- "src/pc/lua/utils/smlua_obj_utils.h": [ "spawn_object_remember_field" ],
+ "src/pc/lua/utils/smlua_obj_utils.h": [ "spawn_object_remember_field", "set_exclamation_box_new_contents", "get_exclamation_box_new_contents_pointer", "get_exclamation_box_new_contents_size" ],
"src/game/camera.h": [ "update_camera", "init_camera", "stub_camera", "^reset_camera", "move_point_along_spline" ],
"src/game/behavior_actions.h": [ "bhv_dust_smoke_loop", "bhv_init_room" ],
"src/pc/lua/utils/smlua_audio_utils.h": [ "smlua_audio_utils_override", "audio_custom_shutdown"],
@@ -1067,7 +1067,7 @@ def def_function(function):
def def_files(processed_files):
- s = '-- AUTOGENERATED FOR CODE EDITORS --\n\n'
+ s = '-- AUTOGENERATED FOR CODE EDITORS -- \n--- @meta\n--- @diagnostic disable\n\n'
for processed_file in processed_files:
for function in processed_file['functions']:
s += def_function(function)
diff --git a/autogen/convert_structs.py b/autogen/convert_structs.py
index a633aa09..65343d78 100644
--- a/autogen/convert_structs.py
+++ b/autogen/convert_structs.py
@@ -123,7 +123,8 @@ sLuaManuallyDefinedStructs = [{
'structs': [
'struct Vec3f { float x; float y; float z; }',
'struct Vec3s { s16 x; s16 y; s16 z; }',
- 'struct Color { u8 r; u8 g; u8 b; }'
+ 'struct Color { u8 r; u8 g; u8 b; }',
+ 'struct ExclamationBoxContents { u8 index; u8 unused; u8 firstByte; enum ModelExtendedId emodel; enum BehaviorId behaviorId; }'
]
}]
@@ -580,7 +581,7 @@ def def_struct(struct):
return s
def def_structs(structs):
- s = '-- AUTOGENERATED FOR CODE EDITORS --\n'
+ s = '-- AUTOGENERATED FOR CODE EDITORS -- \n--- @meta\n--- @diagnostic disable\n\n'
for struct in structs:
if struct['identifier'] in exclude_structs:
diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua
index 2faab968..a20b1634 100644
--- a/autogen/lua_definitions/constants.lua
+++ b/autogen/lua_definitions/constants.lua
@@ -1,4 +1,6 @@
--- AUTOGENERATED FOR CODE EDITORS --
+-- AUTOGENERATED FOR CODE EDITORS --
+--- @meta
+--- @diagnostic disable
math.randomseed(get_time())
diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua
index 36007e76..1fa36fd9 100644
--- a/autogen/lua_definitions/functions.lua
+++ b/autogen/lua_definitions/functions.lua
@@ -1,4 +1,6 @@
--- AUTOGENERATED FOR CODE EDITORS --
+-- AUTOGENERATED FOR CODE EDITORS --
+--- @meta
+--- @diagnostic disable
--- @param id integer
--- @return ObjectWarpNode
@@ -9023,6 +9025,11 @@ function obj_set_vel(o, vx, vy, vz)
-- ...
end
+--- @return nil
+function restore_exclamation_box_original_contents()
+ -- ...
+end
+
--- @param x number
--- @param y number
--- @param z number
diff --git a/autogen/lua_definitions/manual.lua b/autogen/lua_definitions/manual.lua
index c24e8c4d..3f77c6da 100644
--- a/autogen/lua_definitions/manual.lua
+++ b/autogen/lua_definitions/manual.lua
@@ -278,3 +278,15 @@ end
function level_script_parse(levelNum, func)
-- ...
end
+
+--- @param contents ExclamationBoxContents[]
+--- @return nil
+--- The parameter should be a table containing several subtables with the following keys
+--- - index: The index of the content (used by oBehParam2ndByte)
+--- - unused: Unused
+--- - firstByte: The spawned object's oBehParam's 1st byte
+--- - emodel: The spawned object's model
+--- - behaviorId: The spawned object's behavior ID
+function set_exclamation_box_new_contents(contents)
+ -- ...
+end
\ No newline at end of file
diff --git a/autogen/lua_definitions/structs.lua b/autogen/lua_definitions/structs.lua
index 38f4ef87..bd5c1b1f 100644
--- a/autogen/lua_definitions/structs.lua
+++ b/autogen/lua_definitions/structs.lua
@@ -1,4 +1,7 @@
--- AUTOGENERATED FOR CODE EDITORS --
+-- AUTOGENERATED FOR CODE EDITORS --
+--- @meta
+--- @diagnostic disable
+
--- @class AnimInfo
--- @field public animAccel integer
@@ -1931,6 +1934,13 @@
--- @field public g integer
--- @field public r integer
+--- @class ExclamationBoxContents
+--- @field public behaviorId BehaviorId
+--- @field public emodel ModelExtendedId
+--- @field public firstByte integer
+--- @field public index integer
+--- @field public unused integer
+
--- @class Pointer_integer
--- @class Pointer_Trajectory
--- @class Pointer_LevelScript
diff --git a/docs/lua/functions-5.md b/docs/lua/functions-5.md
index 3b7dec61..7761d85b 100644
--- a/docs/lua/functions-5.md
+++ b/docs/lua/functions-5.md
@@ -2209,6 +2209,24 @@
+## [restore_exclamation_box_original_contents](#restore_exclamation_box_original_contents)
+
+### Lua Example
+`restore_exclamation_box_original_contents()`
+
+### Parameters
+- None
+
+### Returns
+- None
+
+### C Prototype
+`void restore_exclamation_box_original_contents(void);`
+
+[:arrow_up_small:](#)
+
+
+
## [set_whirlpools](#set_whirlpools)
### Lua Example
diff --git a/docs/lua/functions.md b/docs/lua/functions.md
index c1fc7d08..186d33d5 100644
--- a/docs/lua/functions.md
+++ b/docs/lua/functions.md
@@ -1676,6 +1676,7 @@
- [obj_move_xyz](functions-5.md#obj_move_xyz)
- [obj_set_model_extended](functions-5.md#obj_set_model_extended)
- [obj_set_vel](functions-5.md#obj_set_vel)
+ - [restore_exclamation_box_original_contents](functions-5.md#restore_exclamation_box_original_contents)
- [set_whirlpools](functions-5.md#set_whirlpools)
- [spawn_non_sync_object](functions-5.md#spawn_non_sync_object)
- [spawn_sync_object](functions-5.md#spawn_sync_object)
diff --git a/docs/lua/structs.md b/docs/lua/structs.md
index f4ea39ee..5fe94990 100644
--- a/docs/lua/structs.md
+++ b/docs/lua/structs.md
@@ -22,6 +22,7 @@
- [Cutscene](#Cutscene)
- [CutsceneSplinePoint](#CutsceneSplinePoint)
- [CutsceneVariable](#CutsceneVariable)
+- [ExclamationBoxContents](#ExclamationBoxContents)
- [FloorGeometry](#FloorGeometry)
- [GlobalObjectAnimations](#GlobalObjectAnimations)
- [GlobalObjectCollisionData](#GlobalObjectCollisionData)
@@ -599,6 +600,20 @@
+## [ExclamationBoxContents](#ExclamationBoxContents)
+
+| Field | Type | Access |
+| ----- | ---- | ------ |
+| behaviorId | [enum BehaviorId](constants.md#enum-BehaviorId) | |
+| emodel | [enum ModelExtendedId](constants.md#enum-ModelExtendedId) | |
+| firstByte | `integer` | |
+| index | `integer` | |
+| unused | `integer` | |
+
+[:arrow_up_small:](#)
+
+
+
## [FloorGeometry](#FloorGeometry)
| Field | Type | Access |
diff --git a/src/game/behavior_actions.c b/src/game/behavior_actions.c
index 4aac7922..03df7bd1 100644
--- a/src/game/behavior_actions.c
+++ b/src/game/behavior_actions.c
@@ -49,6 +49,10 @@
#include "pc/lua/utils/smlua_model_utils.h"
#include "pc/lua/smlua_hooks.h"
+#include "pc/lua/smlua.h"
+#include "pc/lua/smlua_utils.h"
+#include "pc/lua/utils/smlua_obj_utils.h"
+
#define o gCurrentObject
struct WFRotatingPlatformData {
@@ -74,6 +78,7 @@ struct Struct8032F698 {
s16 unk4;
};
+/* Moved to smlua_obj_utils.h
struct Struct802C0DF0 {
u8 unk0;
u8 unk1;
@@ -81,6 +86,7 @@ struct Struct802C0DF0 {
u8 model;
const BehaviorScript *behavior;
};
+*/
struct Struct8032F754 {
s32 unk0;
diff --git a/src/game/behaviors/exclamation_box.inc.c b/src/game/behaviors/exclamation_box.inc.c
index 8a10a6af..109808f4 100644
--- a/src/game/behaviors/exclamation_box.inc.c
+++ b/src/game/behaviors/exclamation_box.inc.c
@@ -28,7 +28,7 @@ struct Struct802C0DF0 sExclamationBoxContents[] = { { 0, 0, 0, MODEL_MARIOS_WING
{ 12, 0, 3, MODEL_STAR, bhvSpawnedStar },
{ 13, 0, 4, MODEL_STAR, bhvSpawnedStar },
{ 14, 0, 5, MODEL_STAR, bhvSpawnedStar },
- { 99, 0, 0, 0, NULL } };
+ { 255, 0, 0, 0, NULL } };
void bhv_rotating_exclamation_box_loop(void) {
if (!o->parentObj || o->parentObj->oAction != 1)
@@ -122,7 +122,7 @@ 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 Struct802C0DF0 *a0, u8 a1, u8 size) {
struct MarioState* marioState = nearest_mario_state_to_object(o);
struct Object* player = marioState ? marioState->marioObj : NULL;
struct Object *sp1C = NULL;
@@ -131,7 +131,7 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
return;
}
- while (a0->unk0 != 99) {
+ for (u8 i = 0; i < size; i++) {
if (a1 == a0->unk0) {
s32 model = exclamation_replace_model(marioState, a0->model);
@@ -152,6 +152,7 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
// stars cant be sent here to due jankiness in oBehParams
if (a0->behavior != smlua_override_behavior(bhvSpawnedStar) && sp1C != NULL) {
// hack: if any other sync objects get spawned here we have to check for them
+ // problem: going to need to sync every object
if (a0->behavior == smlua_override_behavior(bhvKoopaShell)) {
sync_object_set_id(sp1C);
}
@@ -166,7 +167,9 @@ void exclamation_box_spawn_contents(struct Struct802C0DF0 *a0, u8 a1) {
}
void exclamation_box_act_4(void) {
- exclamation_box_spawn_contents(sExclamationBoxContents, o->oBehParams2ndByte);
+ struct Struct802C0DF0 *newContents = get_exclamation_box_new_contents_pointer();
+ u8 newContentsSize = get_exclamation_box_new_contents_size();
+ exclamation_box_spawn_contents(newContents ? newContents : sExclamationBoxContents, o->oBehParams2ndByte, newContentsSize);
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/pc/lua/smlua.c b/src/pc/lua/smlua.c
index 81b36729..9181bd48 100644
--- a/src/pc/lua/smlua.c
+++ b/src/pc/lua/smlua.c
@@ -8,6 +8,7 @@
#include "pc/lua/utils/smlua_model_utils.h"
#include "pc/lua/utils/smlua_level_utils.h"
#include "pc/lua/utils/smlua_anim_utils.h"
+#include "pc/lua/utils/smlua_obj_utils.h"
#include "pc/djui/djui.h"
lua_State* gLuaState = NULL;
@@ -321,6 +322,8 @@ void smlua_shutdown(void) {
smlua_model_util_clear();
smlua_level_util_reset();
smlua_anim_util_reset();
+ restore_exclamation_box_original_contents();
+
lua_State* L = gLuaState;
if (L != NULL) {
lua_close(L);
diff --git a/src/pc/lua/smlua_functions.c b/src/pc/lua/smlua_functions.c
index 18809025..d401c974 100644
--- a/src/pc/lua/smlua_functions.c
+++ b/src/pc/lua/smlua_functions.c
@@ -14,6 +14,7 @@
#include "include/level_misc_macros.h"
#include "include/macro_presets.h"
#include "utils/smlua_anim_utils.h"
+#include "utils/smlua_obj_utils.h"
bool smlua_functions_valid_param_count(lua_State* L, int expected) {
int top = lua_gettop(L);
@@ -210,6 +211,98 @@ int smlua_func_network_send_to(lua_State* L) {
return 1;
}
+int smlua_func_set_exclamation_box_new_contents(lua_State* L) {
+ if (L == NULL) { return 0; }
+ if (!smlua_functions_valid_param_count(L, 1)) { return 0; }
+
+ static struct Struct802C0DF0 sExclamationBoxContentsArray[255];
+
+ u8 subtable_counter = 0;
+ if (lua_type(L, 1) != LUA_TTABLE) {
+ LOG_LUA("Failed to convert parameter 1 to table");
+ return 0;
+ }
+
+ lua_pushnil(L); // Set so lua_next() pops this off upon starting
+ // Enter main table
+ while (lua_next(L, 1) != 0) {
+ // key is index -2, value is index -1
+ if (lua_type(L, -1) != LUA_TTABLE) {
+ LOG_LUA("Failed to find subtable\n");
+ return 0;
+ }
+
+ // Create an empty struct to put values in
+ struct Struct802C0DF0 sContents = { 0 };
+
+ int initialTop = lua_gettop(L); // Hopefully 3
+ lua_pushnil(L); // Set so lua_next() pops this off upon starting
+ u8 elements_counter = 0;
+ // Special care is taken here, so any invalid subtables will error
+ while (lua_next(L, initialTop) != 0) {
+ int keyIndex = lua_gettop(L) - 1;
+ int valueIndex = lua_gettop(L) - 0;
+
+ const char* key = smlua_to_string(L, keyIndex);
+ if (!gSmLuaConvertSuccess) {
+ LOG_LUA("Failed to convert key to string in subtable %u", subtable_counter);
+ return 0;
+ }
+
+ const int value = smlua_to_integer(L, valueIndex);
+ if (!gSmLuaConvertSuccess) {
+ LOG_LUA("The key \"%s\" within subtable %u has a value that is not a number (Type: %u)", key, subtable_counter, lua_type(L, valueIndex));
+ return 0;
+ }
+
+ // Bad
+ if (!strcmp(key, "index")) {
+ sContents.unk0 = value;
+ elements_counter++;
+ lua_pop(L, 1); // pop value
+ } else if (!strcmp(key, "unused")) {
+ sContents.unk1 = value;
+ elements_counter++;
+ lua_pop(L, 1); // pop value
+ } else if (!strcmp(key, "firstByte")) {
+ sContents.unk2 = value;
+ elements_counter++;
+ lua_pop(L, 1); // pop value
+ } else if (!strcmp(key, "emodel")) {
+ u16 loadedModelId = smlua_model_util_load(value);
+ sContents.model = loadedModelId;
+ elements_counter++;
+ lua_pop(L, 1); // pop value
+ } else if (!strcmp(key, "behaviorId")) {
+ sContents.behavior = get_behavior_from_id(value);
+ elements_counter++;
+ lua_pop(L, 1); // pop value
+ } else {
+ LOG_LUA("The key \"%s\" in subtable %u is invalid", key, subtable_counter);
+ return 0;
+ }
+ }
+
+ if (elements_counter != 5) {
+ LOG_LUA("Invalid elements count in subtable %u (There should be 5)", subtable_counter);
+ return 0;
+ }
+
+ // Element 254 will not be set
+ if (subtable_counter < 254) {
+ sExclamationBoxContentsArray[subtable_counter] = sContents;
+ subtable_counter++;
+ }
+
+ lua_pop(L, 1); // pop key
+ }
+ lua_pop(L, 1); // pop key
+
+ set_exclamation_box_new_contents(sExclamationBoxContentsArray, subtable_counter);
+
+ return 1;
+}
+
//////////////
// Textures //
//////////////
@@ -747,4 +840,5 @@ void smlua_bind_functions(void) {
smlua_bind_function(L, "djui_hud_render_texture_tile_interpolated", smlua_func_djui_hud_render_texture_tile_interpolated);
smlua_bind_function(L, "level_script_parse", smlua_func_level_script_parse);
smlua_bind_function(L, "smlua_anim_util_register_animation", smlua_func_smlua_anim_util_register_animation);
+ smlua_bind_function(L, "set_exclamation_box_new_contents", smlua_func_set_exclamation_box_new_contents);
}
diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c
index f31d657b..e75b7ab3 100644
--- a/src/pc/lua/smlua_functions_autogen.c
+++ b/src/pc/lua/smlua_functions_autogen.c
@@ -29647,6 +29647,21 @@ int smlua_func_obj_set_vel(lua_State* L) {
return 1;
}
+int smlua_func_restore_exclamation_box_original_contents(UNUSED lua_State* L) {
+ if (L == NULL) { return 0; }
+
+ int top = lua_gettop(L);
+ if (top != 0) {
+ LOG_LUA_LINE("Improper param count for '%s': Expected %u, Received %u", "restore_exclamation_box_original_contents", 0, top);
+ return 0;
+ }
+
+
+ restore_exclamation_box_original_contents();
+
+ return 1;
+}
+
int smlua_func_set_whirlpools(lua_State* L) {
if (L == NULL) { return 0; }
@@ -32167,6 +32182,7 @@ void smlua_bind_functions_autogen(void) {
smlua_bind_function(L, "obj_move_xyz", smlua_func_obj_move_xyz);
smlua_bind_function(L, "obj_set_model_extended", smlua_func_obj_set_model_extended);
smlua_bind_function(L, "obj_set_vel", smlua_func_obj_set_vel);
+ smlua_bind_function(L, "restore_exclamation_box_original_contents", smlua_func_restore_exclamation_box_original_contents);
smlua_bind_function(L, "set_whirlpools", smlua_func_set_whirlpools);
smlua_bind_function(L, "spawn_non_sync_object", smlua_func_spawn_non_sync_object);
smlua_bind_function(L, "spawn_sync_object", smlua_func_spawn_sync_object);
diff --git a/src/pc/lua/utils/smlua_obj_utils.c b/src/pc/lua/utils/smlua_obj_utils.c
index f87752a4..1bda952d 100644
--- a/src/pc/lua/utils/smlua_obj_utils.c
+++ b/src/pc/lua/utils/smlua_obj_utils.c
@@ -407,6 +407,27 @@ void set_whirlpools(f32 x, f32 y, f32 z, s16 strength, s16 area, s32 index) {
gAreas[area].whirlpools[index]->strength = strength;
}
+struct Struct802C0DF0 *newContents = NULL;
+u8 newContentsSize = 16;
+
+struct Struct802C0DF0* get_exclamation_box_new_contents_pointer(void) {
+ return newContents;
+}
+
+u8 get_exclamation_box_new_contents_size(void) {
+ return newContentsSize;
+}
+
+void set_exclamation_box_new_contents(struct Struct802C0DF0 contents[], u8 size) {
+ newContents = contents;
+ newContentsSize = size;
+}
+
+void restore_exclamation_box_original_contents(void) {
+ newContents = NULL;
+ newContentsSize = 16;
+}
+
#ifdef DEVELOPMENT
void obj_randomize(struct Object* o) {
if (!o) { return; }
diff --git a/src/pc/lua/utils/smlua_obj_utils.h b/src/pc/lua/utils/smlua_obj_utils.h
index 48b0963f..5fae98bc 100644
--- a/src/pc/lua/utils/smlua_obj_utils.h
+++ b/src/pc/lua/utils/smlua_obj_utils.h
@@ -5,6 +5,14 @@
#include "smlua_model_utils.h"
#include "game/object_list_processor.h"
+struct Struct802C0DF0 {
+ u8 unk0; // Index
+ u8 unk1; // Unused
+ u8 unk2; // oBehParams1stByte
+ u16 model;
+ const BehaviorScript *behavior;
+};
+
struct Object* spawn_sync_object(enum BehaviorId behaviorId, enum ModelExtendedId modelId, f32 x, f32 y, f32 z, LuaFunction objSetupFunction);
struct Object* spawn_non_sync_object(enum BehaviorId behaviorId, enum ModelExtendedId modelId, f32 x, f32 y, f32 z, LuaFunction objSetupFunction);
@@ -57,4 +65,9 @@ void obj_move_xyz(struct Object *o, f32 dx, f32 dy, f32 dz);
void set_whirlpools(f32 x, f32 y, f32 z, s16 strength, s16 area, s32 index);
+struct Struct802C0DF0* get_exclamation_box_new_contents_pointer(void);
+u8 get_exclamation_box_new_contents_size(void);
+void set_exclamation_box_new_contents(struct Struct802C0DF0 contents[], u8 size);
+void restore_exclamation_box_original_contents(void);
+
#endif