Custom exclamation boxes (again) (#360)

* Unhardcode exclamation boxes

Part 1 of creating custom exclamation boxes

* Add `set_exclamation_box` and `get_exclamation_box`

Part 2 of creating custom exclamation boxes

* Exclamation box docs

Part 3 of creating exclamation boxes, although this was entirely optional

* Safety check

* Add a warning

* Remake component check

* Update warnings

* Check for subtables not being tables

* Reduce exclamation box size

This matches how many contents could exist in vanilla due to its hardcoded id 99 check. This check allows me to reasonably assume that no romhacks have more than 99 custom contents. Other mods that may use this function really shouldn't need more than 99 contents either.
This commit is contained in:
Sunk 2024-11-25 04:26:37 -05:00 committed by GitHub
parent d51ee7ffe4
commit 5965b55eb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 383 additions and 39 deletions

View file

@ -725,6 +725,86 @@ Shoots a raycast from `startX`, `startY`, and `startZ` in the direction of `dirX
<br />
## [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:](#)
<br />
## [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:](#)
<br />
"""
############################################################################

View file

@ -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

View file

@ -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

View file

@ -2323,6 +2323,86 @@ Shoots a raycast from `startX`, `startY`, and `startZ` in the direction of `dirX
<br />
## [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:](#)
<br />
## [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:](#)
<br />
---
# functions from area.h

View file

@ -24,6 +24,7 @@
- [CutsceneVariable](#CutsceneVariable)
- [DateTime](#DateTime)
- [DjuiColor](#DjuiColor)
- [ExclamationBoxContent](#ExclamationBoxContent)
- [FirstPersonCamera](#FirstPersonCamera)
- [FloorGeometry](#FloorGeometry)
- [GlobalObjectAnimations](#GlobalObjectAnimations)
@ -844,6 +845,20 @@
<br />
## [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:](#)
<br />
## [FirstPersonCamera](#FirstPersonCamera)
| Field | Type | Access |

View file

@ -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;

View file

@ -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);

View file

@ -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;
}

View file

@ -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 //
//////////////

View file

@ -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 },

View file

@ -27,6 +27,7 @@ enum LuaObjectAutogenType {
LOT_CUTSCENEVARIABLE,
LOT_DATETIME,
LOT_DJUICOLOR,
LOT_EXCLAMATIONBOXCONTENT,
LOT_FIRSTPERSONCAMERA,
LOT_FLOORGEOMETRY,
LOT_GLOBALOBJECTANIMATIONS,

View file

@ -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);