From 697f1c3f826cc7482ae8f383ee00bd2a18779b53 Mon Sep 17 00:00:00 2001 From: Cooliokid956 <68075390+Cooliokid956@users.noreply.github.com> Date: Sun, 15 Mar 2026 08:02:17 -0500 Subject: [PATCH 01/19] Add Scroll Binds (#835) * Add Scroll Binds and fix a five year oob array index/write * number * fix that part I just saw this * That should be that I have tested it, scroll works on the Up input, and on a regular input, so does everything else --- src/pc/controller/controller_bind_mapping.c | 16 +++++++++++----- src/pc/controller/controller_mouse.c | 5 +++++ src/pc/controller/controller_sdl.h | 2 ++ src/pc/controller/controller_sdl2.c | 8 ++++---- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/pc/controller/controller_bind_mapping.c b/src/pc/controller/controller_bind_mapping.c index 8c56299f2..24f81481b 100644 --- a/src/pc/controller/controller_bind_mapping.c +++ b/src/pc/controller/controller_bind_mapping.c @@ -106,11 +106,17 @@ const char* translate_bind_to_name(int bind) { // mouse if (bind >= VK_BASE_SDL_MOUSE) { int mouse_button = (bind - VK_BASE_SDL_MOUSE); - if (mouse_button == 1) { return "L Mouse"; } - if (mouse_button == 2) { return "M Mouse"; } - if (mouse_button == 3) { return "R Mouse"; } - snprintf(name, 8, "Mouse %d", bind - VK_BASE_SDL_MOUSE); - return name; + switch (mouse_button) { + case 1: return "L Mouse"; + case 2: return "M Mouse"; + case 3: return "R Mouse"; + case 6: return "Scroll Up"; + case 7: return "Scroll Down"; + default: { + snprintf(name, 11, "Mouse %d", mouse_button); + return name; + } + } } // gamepad diff --git a/src/pc/controller/controller_mouse.c b/src/pc/controller/controller_mouse.c index 84c10a635..bb047ead5 100644 --- a/src/pc/controller/controller_mouse.c +++ b/src/pc/controller/controller_mouse.c @@ -116,6 +116,11 @@ void controller_mouse_read_relative(void) { #elif defined(CAPI_SDL1) || defined(CAPI_SDL2) mouse_buttons = SDL_GetRelativeMouseState(&mouse_x, &mouse_y); #endif + if (mouse_scroll_y > 0) { + mouse_buttons |= MWHEELUP; + } else if (mouse_scroll_y < 0) { + mouse_buttons |= MWHEELDOWN; + } } void controller_mouse_enter_relative(void) { diff --git a/src/pc/controller/controller_sdl.h b/src/pc/controller/controller_sdl.h index bab54eddc..b6235365d 100644 --- a/src/pc/controller/controller_sdl.h +++ b/src/pc/controller/controller_sdl.h @@ -8,6 +8,8 @@ // mouse buttons are also in the controller namespace, just offset 0x100 #define VK_OFS_SDL_MOUSE 0x0100 #define VK_BASE_SDL_MOUSE (VK_BASE_SDL_GAMEPAD + VK_OFS_SDL_MOUSE) +#define MWHEELUP 0x20 +#define MWHEELDOWN 0x40 extern struct ControllerAPI controller_sdl; diff --git a/src/pc/controller/controller_sdl2.c b/src/pc/controller/controller_sdl2.c index 373065415..95dafbed1 100644 --- a/src/pc/controller/controller_sdl2.c +++ b/src/pc/controller/controller_sdl2.c @@ -60,11 +60,11 @@ static s16 invert_s16(s16 val) { static inline void controller_add_binds(const u32 mask, const u32 *btns) { for (u32 i = 0; i < MAX_BINDS; ++i) { if (btns[i] >= VK_BASE_SDL_GAMEPAD && btns[i] <= VK_BASE_SDL_GAMEPAD + VK_SIZE) { - if (btns[i] >= VK_BASE_SDL_MOUSE && num_joy_binds < MAX_JOYBINDS) { + if (btns[i] >= VK_BASE_SDL_MOUSE && num_mouse_binds < MAX_JOYBINDS) { mouse_binds[num_mouse_binds][0] = btns[i] - VK_BASE_SDL_MOUSE; mouse_binds[num_mouse_binds][1] = mask; ++num_mouse_binds; - } else if (num_mouse_binds < MAX_JOYBINDS) { + } else if (num_joy_binds < MAX_JOYBINDS) { joy_binds[num_joy_binds][0] = btns[i] - VK_BASE_SDL_GAMEPAD; joy_binds[num_joy_binds][1] = mask; ++num_joy_binds; @@ -190,10 +190,11 @@ static void controller_sdl_read(OSContPad *pad) { controller_mouse_read_relative(); u32 mouse = mouse_buttons; + u32 buttons_down = 0; if (!gInteractableOverridePad) { for (u32 i = 0; i < num_mouse_binds; ++i) if (mouse & SDL_BUTTON(mouse_binds[i][0])) - pad->button |= mouse_binds[i][1]; + buttons_down |= mouse_binds[i][1]; } // remember buttons that changed from 0 to 1 last_mouse = (mouse_prev ^ mouse) & mouse; @@ -282,7 +283,6 @@ static void controller_sdl_read(OSContPad *pad) { update_button(VK_LTRIGGER - VK_BASE_SDL_GAMEPAD, ltrig > AXIS_THRESHOLD); update_button(VK_RTRIGGER - VK_BASE_SDL_GAMEPAD, rtrig > AXIS_THRESHOLD); - u32 buttons_down = 0; for (u32 i = 0; i < num_joy_binds; ++i) if (joy_buttons[joy_binds[i][0]]) buttons_down |= joy_binds[i][1]; From d9debe61d5e96a383e9194f5fbbe32f777fdbefb Mon Sep 17 00:00:00 2001 From: iZePlayz <69536095+iZePlayzYT@users.noreply.github.com> Date: Sun, 15 Mar 2026 14:02:30 +0100 Subject: [PATCH 02/19] fixed german translation for /confirm (#1067) --- lang/German.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lang/German.ini b/lang/German.ini index 46d0bf440..e44e5c718 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -46,10 +46,10 @@ NAMETAGS_MISSING_PARAMETERS = "Fehlende Parameter: [OPTION]" SELF_KICK = "Du kannst dich nicht selbst kicken." SELF_BAN = "Du kannst dich nicht selbst bannen." SELF_MOD = "Du kannst dich nicht selbst zum Moderator machen." -KICK_CONFIRM = "Bist du sicher, dass du '@' vom Server kicken möchtest?\nGib '\\#a0ffa0\\/bestätigen\\#fff982\\' ein, um fortzufahren." -BAN_CONFIRM = "Bist du sicher, dass du '@' vom Server bannen möchtest?\nGib '\\#a0ffa0\\/bestätigen\\#fff982\\' ein, um fortzufahren." -PERM_BAN_CONFIRM = "Bist du sicher, dass du '@' dauerhaft vom Server bannen möchtest?\nGib '\\#a0ffa0\\/bestätigen\\#fff982\\' ein, um fortzufahren." -MOD_CONFIRM = "Bist du sicher, dass du '@' zum Moderator ernennen möchtest?\nGib '\\#a0ffa0\\/bestätigen\\#fff982\\' ein." +KICK_CONFIRM = "Bist du sicher, dass du '@' vom Server kicken möchtest?\nGib '\\#a0ffa0\\/confirm\\#fff982\\' ein, um fortzufahren." +BAN_CONFIRM = "Bist du sicher, dass du '@' vom Server bannen möchtest?\nGib '\\#a0ffa0\\/confirm\\#fff982\\' ein, um fortzufahren." +PERM_BAN_CONFIRM = "Bist du sicher, dass du '@' dauerhaft vom Server bannen möchtest?\nGib '\\#a0ffa0\\/confirm\\#fff982\\' ein, um fortzufahren." +MOD_CONFIRM = "Bist du sicher, dass du '@' zum Moderator ernennen möchtest?\nGib '\\#a0ffa0\\/confirm\\#fff982\\' ein." PLAYERS_DESC = "/players - Zeige alle Spieler und ihre IDs." KICK_DESC = "/kick [NAME|ID] - Kicke einen Spieler vom Server." BAN_DESC = "/ban [NAME|ID] - Banne einen Spieler vom Server." From 8fe56ab9990be338ef3a347ee22941ef09731af8 Mon Sep 17 00:00:00 2001 From: frog8412 <160930327+frog8412@users.noreply.github.com> Date: Sun, 15 Mar 2026 16:02:49 +0300 Subject: [PATCH 03/19] Update Japanese.ini (changes by 3UPPER) (#1114) * Update Japanese.ini (original by 3UPPER) * Fixed Japanese.ini --- lang/Japanese.ini | 206 +++++++++++++++++++++++----------------------- 1 file changed, 102 insertions(+), 104 deletions(-) diff --git a/lang/Japanese.ini b/lang/Japanese.ini index 322c449e8..a15f74b3a 100644 --- a/lang/Japanese.ini +++ b/lang/Japanese.ini @@ -1,61 +1,61 @@ [NOTIF] -CONNECTED = "@が接続しました" -DISCONNECTED = "@が切断しました。" -LEFT_THIS_LEVEL = "@がこのコースから出ました。" -ENTERED_THIS_LEVEL = "@がこのコースに入りました。" -ENTERED = "@が\n#に入りました。" -SERVER_CLOSED = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ 部屋が閉じられました。" -DISCORD_ERROR = "Discordエラーが発生しました。\n解決するには、\n1. ゲームを終了し、\n2. Discordを再起動してから、\n3. もう一度ゲームを開いてください。" +CONNECTED = "@ が参加しました" +DISCONNECTED = "@ が退出しました" +LEFT_THIS_LEVEL = "@ がこのコースから出ました" +ENTERED_THIS_LEVEL = "@ がこのコースに入りました" +ENTERED = "@ が \n# に入りました" +SERVER_CLOSED = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ ルームが閉じられました。" +DISCORD_ERROR = "Discordエラーが発生しました。\n解決するには、\n1. ゲームを終了する\n2. Discordを再起動する\n3. もう一度ゲームを開く\nの手順で進めてください。" DISCORD_DETECT = "\\#ffa0a0\\エラー:\\#dcdcdc\\ Discordを検出できませんでした。\n\\#a0a0a0\\ゲームを終了し、Discordを再起動してから、もう一度お試しください。" DISCONNECT_FULL = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ ルームが満員です。" DISCONNECT_KICK = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ キックされました。" DISCONNECT_BAN = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ BANされました。" DISCONNECT_REJOIN = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ 再参加中です…" DISCONNECT_CLOSED = "\\#ffa0a0\\切断されました:\\#dcdcdc\\ ホストが切断しました。" -DISCONNECT_BIG_MOD = "MODの量が多すぎます!\n切断しました。" -DIED = "@がやられた!" -DEBUG_FLY = "@がデバッグ飛行モードに入りました!" -IMPORT_MOD_SUCCESS = "'@'\n\\#a0ffa0\\MODを読み込みました\\#dcdcdc\\" -IMPORT_DYNOS_SUCCESS = "'@'\n\\#a0ffa0\\DynOSのパックを読み込みました\\#dcdcdc\\" -IMPORT_PALETTE_SUCCESS = "'@'\n\\#a0ffa0\\パレットのプリセットを読み込みました\\#dcdcdc\\" -IMPORT_FAIL = "'@'\n\\#ffa0a0\\読み込みに失敗しました。\\#dcdcdc\\" -IMPORT_FAIL_INGAME = "\\#ffa0a0\\ゲーム中はMODを読み込めません" +DISCONNECT_BIG_MOD = "このルームはMODの量が多すぎます。\n切断しました。" +DIED = "@がやられた" +DEBUG_FLY = "@がデバッグ飛行モードに入った" +IMPORT_MOD_SUCCESS = "\\#a0ffa0\\MODをインポートしました:\n\\#dcdcdc\\@" +IMPORT_DYNOS_SUCCESS = "\\#a0ffa0\\DynOSパックをインポートしました:\n\\#dcdcdc\\@" +IMPORT_PALETTE_SUCCESS = "\\#a0ffa0\\パレットプリセットをインポートしました:\n\\#dcdcdc\\@" +IMPORT_FAIL = "\\#ffa0a0\\インポートに失敗しました:\n\\#dcdcdc\\@" +IMPORT_FAIL_INGAME = "\\#ffa0a0\\ゲーム中はMODをインポートできません" COOPNET_CONNECTION_FAILED = "\\#ffa0a0\\CoopNetに接続できませんでした!" -COOPNET_DISCONNECTED = "\\#ffa0a0\\CoopNetとの接続が途絶えました!" +COOPNET_DISCONNECTED = "\\#ffa0a0\\CoopNetとの接続が失われました!" LOBBY_NOT_FOUND = "\\#ffa0a0\\エラー:\\#dcdcdc\\ ルームがすでに閉じられています!" -LOBBY_JOIN_FAILED = "\\#ffa0a0\\ルームに参加できませんでした。" -LOBBY_PASSWORD_INCORRECT = "\\#ffa0a0\\パスワードが間違っています。" -COOPNET_VERSION = "\\#ffa0a0\\あなたのバージョンはCoopNetに対応していません。アップデートしましょう!" -PEER_FAILED = "\\#ffa0a0\\'@'への接続に失敗しました。" +LOBBY_JOIN_FAILED = "\\#ffa0a0\\ルームへの参加に失敗しました!" +LOBBY_PASSWORD_INCORRECT = "\\#ffa0a0\\パスワードが間違っています!" +COOPNET_VERSION = "\\#ffa0a0\\あなたのゲームバージョンはCoopNetに対応していません。アップデートしましょう!" +PEER_FAILED = "\\#ffa0a0\\プレイヤー @ への接続に失敗しました。" UNKNOWN = "未知" -LOBBY_HOST = "部屋主" +LOBBY_HOST = "ルームのホスト" UPDATE_AVAILABLE = "アップデートが利用可能です!" -LATEST_VERSION = "最新バージョン" +LATEST_VERSION = "最新のバージョン" YOUR_VERSION = "あなたのバージョン" [CHAT] -KICKING = "'@'をキックしました!" -BANNING = "'@'をBANしました!" +KICKING = "@ をキックしました!" +BANNING = "@ をBANしました!" SERVER_ONLY = "このコマンドはホストのみが実行できます。" -PERM_BANNING = "'@'を永久BANしました!" -ADD_MODERATOR = "'@'をモデレーターにしました!" -PLAYERS = "プレイヤー" +PERM_BANNING = "@ を永久BANしました!" +ADD_MODERATOR = "@ をモデレーターにしました!" +PLAYERS = "ルーム内のプレイヤー" NO_PERMS = "このコマンドを実行する権限がありません。" PLAYER_NOT_FOUND = "プレイヤーが見つかりませんでした。" -NAMETAGS_MISSING_PARAMETERS = "引数が不足しています: [OPTION]が必要です。" +NAMETAGS_MISSING_PARAMETERS = "引数が不足しています: [OPTION] が必要です。" SELF_KICK = "自分自身はキックできません。" SELF_BAN = "自分自身はBANできません。" SELF_MOD = "自分自身をモデレーターにすることはできません。" -KICK_CONFIRM = "本当に'@'を強制退出させますか?\n実行するには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" -BAN_CONFIRM = "本当に'@'をBANしますか?\nBANするには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" -PERM_BAN_CONFIRM = "本当に'@'を永久BANしますか?\nBANするには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" -MOD_CONFIRM = "本当に'@'をモデレーターにしますか?\n'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" -PLAYERS_DESC = "/players - プレイヤー名とID一覧を表示します。" -KICK_DESC = "/kick [NAME|ID] - プレイヤーを現在のルームからキックします。" -BAN_DESC = "/ban [NAME|ID] - プレイヤーを現在のルームからBANします。" -PERM_BAN_DESC = "/permban [NAME|ID] - プレイヤーをあなたが今後ホストするすべてのルームからBANします。" -MOD_DESC = "/moderator [NAME|ID] - プレイヤーに/kick、/ban、/permbanのようなコマンドの使用を許可します。" -NAMETAGS_DESC = "/nametags [show-tag|show-health] - あなたの体力やネームタグの表示を変更します。" +KICK_CONFIRM = "本当に @ をキックしますか?\n実行するには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" +BAN_CONFIRM = "本当に @ をBANしますか?\nBANするには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" +PERM_BAN_CONFIRM = "本当に @ を永久BANしますか?\nBANするには'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" +MOD_CONFIRM = "本当に @ をモデレーターにしますか?\n'\\#a0ffa0\\/confirm\\#fff982\\' と入力して確定します。" +PLAYERS_DESC = "/players - ルーム内のプレイヤー名とIDの一覧を表示します。" +KICK_DESC = "/kick [プレイヤー名|ID] - 指定したプレイヤーを現在のルームからキックします。" +BAN_DESC = "/ban [プレイヤー名|ID] - 指定したプレイヤーを現在のルームからBANします。" +PERM_BAN_DESC = "/permban [プレイヤー名|ID] - 指定したプレイヤーをあなたが今後ホストするすべてのルームからBANします。" +MOD_DESC = "/moderator [プレイヤー名|ID] - 指定したプレイヤーに/kick、/ban、/permbanのようなコマンドの使用を許可します。" +NAMETAGS_DESC = "/nametags [show-tag|show-health] - あなたのネームタグ/体力の表示を変更します。" UNRECOGNIZED = "未知のコマンドです。" MOD_GRANTED = "\\#fff982\\あなたはモデレーターになりました。" @@ -70,8 +70,8 @@ CAMERA = "CAMERA" FREE_CAMERA = "フリーカメラ" ANALOG_CAMERA = "アナログカメラ" FREE_CAMERA_TITLE = "FREE CAMERA" -FREE_CAMERA_L_CENTERING = "Lセンタリング" -FREE_CAMERA_USE_DPAD = "DPad の動作" +FREE_CAMERA_L_CENTERING = "Lボタンで前を向く" +FREE_CAMERA_USE_DPAD = "十字キー操作" FREE_CAMERA_COLLISION = "カメラの衝突" ROMHACK_CAMERA_TITLE = "ROMHACK\nCAMERA" ROMHACK_CAMERA = "ロムハックカメラ" @@ -79,26 +79,25 @@ ROMHACK_CAMERA_AUTOMATIC = "自動" ROMHACK_CAMERA_ON = "オン" ROMHACK_CAMERA_OFF = "オフ" ROMHACK_CAMERA_IN_BOWSER = "クッパ戦で使用" -ROMHACK_CAMERA_COLLISION = "カメラの衝突" -ROMHACK_CAMERA_L_CENTERING = "Lセンタリング" -ROMHACK_CAMERA_USE_DPAD = "DPad の動作" -ROMHACK_CAMERA_SLOW_FALL = "スローフォール" -CAMERA_TOXIC_GAS = "有毒ガスの調整" +ROMHACK_CAMERA_COLLISION = "カメラの当たり判定" +ROMHACK_CAMERA_L_CENTERING = "Lボタンで前を向く" +ROMHACK_CAMERA_USE_DPAD = "十字キー操作" +ROMHACK_CAMERA_SLOW_FALL = "低速落下" +CAMERA_TOXIC_GAS = "有毒ガス内での調整" MOUSE_LOOK = "マウスでの操作" INVERT_X = "X方向のカメラ反転" INVERT_Y = "Y方向のカメラ反転" X_SENSITIVITY = "X方向の感度" Y_SENSITIVITY = "Y方向の感度" -AGGRESSION = "かたさ" -PAN_LEVEL = "カメラのずれ" -DECELERATION = "カメラ減速" -ROMHACK_CAMERA_OFF = "オフ" +AGGRESSION = "カメラの追従性" +PAN_LEVEL = "カメラの水平速度" +DECELERATION = "カメラ減速の強さ" [CONTROLS] CONTROLS = "CONTROLS" -N64_BINDS = "ニンテンドウ64の入力" -EXTRA_BINDS = "追加の入力" +N64_BINDS = "ニンテンドウ64のボタン割り当て" +EXTRA_BINDS = "追加のボタン割り当て" BACKGROUND_GAMEPAD = "バックグラウンドでのコントローラー認識" DISABLE_GAMEPADS = "コントローラーを無効化" GAMEPAD = "コントローラー" @@ -106,28 +105,28 @@ DEADZONE = "デッドゾーン" RUMBLE_STRENGTH = "振動の強さ" CHAT = "チャット" -PLAYERS = "プレイヤーリストの表示" +PLAYERS = "プレイヤーリスト" D_UP = "十字キー 上" D_DOWN = "十字キー 下" D_LEFT = "十字キー 左" D_RIGHT = "十字キー 右" -X = "X" -Y = "Y" +X = "Xボタン" +Y = "Yボタン" CONSOLE = "コンソール" PREV = "前のページ" NEXT = "次のページ" -DISCONNECT = "切断" +DISCONNECT = "ゲームから切断" -UP = "上" -DOWN = "下" -LEFT = "左" -RIGHT = "右" -A = "A" -B = "B" -START = "スタート" -L = "L" -R = "R" -Z = "Z" +UP = "3Dスティック 上" +DOWN = "3Dスティック 下" +LEFT = "3Dスティック 左" +RIGHT = "3Dスティック 右" +A = "Aボタン" +B = "Bボタン" +START = "STARTボタン" +L = "Lトリガー" +R = "Rトリガー" +Z = "Zトリガー" C_UP = "Cボタン 上" C_DOWN = "Cボタン 下" C_LEFT = "Cボタン 左" @@ -135,7 +134,7 @@ C_RIGHT = "Cボタン 右" ANALOG_STICK_OPTIONS = "アナログスティックのオプション" -ROTATE_LEFT = "左スティックを90度回転させる" +ROTATE_LEFT = "左スティックを90度回転" INVERT_LEFT_X = "左スティックX軸の反転" INVERT_LEFT_Y = "左スティックY軸の反転" ROTATE_RIGHT = "右スティックを90度回転" @@ -153,7 +152,7 @@ AUTO = "自動" MANUAL = "手動" UNCAPPED = "無制限" FRAME_LIMIT = "FPSの制限" -FAST = "速い" +FAST = "高速" ACCURATE = "正確" INTERPOLATION = "補間" NEAREST = "ニアレスト" @@ -196,24 +195,24 @@ LOCAL_PLAYER_MODEL_ONLY = "ローカルのキャラモデルに限定" [HOST_MESSAGE] INFO_TITLE = "INFO" -WARN_DISCORD = "招待したいフレンドを右クリックしてn'\\#d0d0ff\\ゲームに招待\\#dcdcdc\\'.\n\nを押すと招待できます。サーバー内のチャンネルにも、チャット横の\\#d0d0ff\\プラス\\#dcdcdc\\マークから招待メッセージを送信できます。\n\nゲーム アクティビティを\\#ffa0a0\\必ず\\#dcdcdc\\有効にしてください。\n\n\nオフラインに設定していると、招待の送信を\\#ffa0a0\\妨げる\\#dcdcdc\\可能性があります。" -WARN_DISCORD2 = "\\#ffa0a0\\エラー:\\#dcdcdc\\Discordを検出できませんでした。\n\\#a0a0a0\\ゲームを終了し、Discordを再起動してから、もう一度お試しください。" -WARN_SOCKET = "ファイアウォール設定が正しく設定されている事をご確認ください。\n直接接続には、ルータのポート転送でIPv4インバウンド接続を受信するように設定する\\#ffa0a0\\必要\\#dcdcdc\\があります。\n\nUDPポート'%d'番を開放してください。IPv6にも対応しています。" +WARN_DISCORD = "招待したいフレンドを右クリックしてn'\\#d0d0ff\\ゲームに招待\\#dcdcdc\\'.\n\nを押すと招待できます。サーバー内のチャンネルにも、チャット入力欄の横にある\\#d0d0ff\\+\\#dcdcdc\\マークから招待メッセージを送信できます。\n\nDiscordのユーザー設定からゲーム アクティビティを\\#ffa0a0\\必ず\\#dcdcdc\\有効にしてください。\n\n\nステータスをオフラインに設定していると、招待の送信が\\#ffa0a0\\妨げられる\\#dcdcdc\\可能性があります。" +WARN_DISCORD2 = "\\#ffa0a0\\エラー:\\#dcdcdc\\Discordを検出できませんでした。\n\\#a0a0a0\\ゲームを終了してDiscordを再起動してから、もう一度お試しください。" +WARN_SOCKET = "ファイアウォールの設定が正しく完了していることを確認してください。\nダイレクト接続には\\#ffa0a0\\あなた自身が\\#dcdcdc\\ルーターでIPv4の接続を受け入れるようにポートフォワーディング設定を行う必要があります。\n\nUDPポート'\\#d0d0ff\\%d\\#dcdcdc\\'を解放してください。IPv6も使用可能です。" HOST = "ルームを作る" [HOST_MODS] MODS = "MODS" CATEGORIES = "カテゴリ一覧" -WARNING = "\\#ffffa0\\<注意>\\#dcdcdc\\ MOD数が10個以上になっています。ラグや不安定を防ぐため、いくつか無効にしてください" -NO_MODS_FOUND = "MODが見つかりませんでした。" +WARNING = "\\#ffffa0\\<注意>\\#dcdcdc\\ MODの数が10個以上になっています。ラグや不安定を防ぐため、いくつか無効にしてください。" +NO_MODS_FOUND = "MODは見つかりませんでした。" [HOST_MOD_CATEGORIES] ALL = "すべて" MISC = "その他" -ROMHACKS = "ハックロム" -GAMEMODES = "ゲームモード" -MOVESETS = "ムーブセット" -CHARACTER_SELECT = "キャラクター選択" +ROMHACKS = "ロムハック系" +GAMEMODES = "ゲームモード系" +MOVESETS = "ムーブセット系" +CHARACTER_SELECT = "追加キャラクター系" [HOST_SAVE] SAVE_TITLE = "SAVE" @@ -222,7 +221,7 @@ CONFIRM = "本当に消しますか?" ERASE = "消す" EDIT = "編集" EDIT_TITLE = "EDIT" -EDIT_NAME = "ファイル名を変更:" +EDIT_NAME = "マリオ @のおなまえ変更:" [HOST_SETTINGS] SETTINGS = "SETTINGS" @@ -235,7 +234,7 @@ NORMAL = "普通" TOO_MUCH = "最強" KNOCKBACK_STRENGTH = "ノックバックの強さ" CLASSIC_PVP = "クラシック" -REVAMPED_PVP = "改良" +REVAMPED_PVP = "改良型" PVP_MODE = "PvPモード" LEAVE_LEVEL = "コースを出る" STAY_IN_LEVEL = "コースに留まる" @@ -243,14 +242,14 @@ NONSTOP = "ノンストップ" ON_STAR_COLLECTION = "スター取得時の動作" SKIP_INTRO_CUTSCENE = "イントロをスキップ" ENABLE_CHEATS = "チートを有効にする" -BUBBLE_ON_DEATH = "やられた時にシャボンで復活" +BUBBLE_ON_DEATH = "ミス時にシャボンで復活" NAMETAGS = "ネームタグを有効にする" MOD_DEV_MODE = "MOD開発モード" -BOUNCY_BOUNDS_ON_CAP = "オン(制限付き)" +BOUNCY_BOUNDS_ON_CAP = "オン(速度制限)" BOUNCY_BOUNDS_ON = "オン" BOUNCY_BOUNDS_OFF = "オフ" BOUNCY_LEVEL_BOUNDS = "コース境界での跳ね返り" -AMOUNT_OF_PLAYERS = "最大人数" +AMOUNT_OF_PLAYERS = "最大ルーム人数" PAUSE_ANYWHERE = "どこでもポーズ" [HOST] @@ -275,7 +274,7 @@ JOINING = "JOINING" [JOIN] JOIN_TITLE = "JOIN" JOIN_DISCORD = "\\#d0d0ff\\Discord\\#dcdcdc\\ロビーへの参加:\n\nゲームを開いたまま、招待メッセージの参加ボタンを押してください。\n\n「ゲームは終了しました」と表示されている場合は、招待を送信した人の名前をクリックして更新してください。" -JOIN_SOCKET = "\\#d0d0ff\\ダイレクト接続\\#dcdcdc\\のIPとポートを入力してください:" +JOIN_SOCKET = "\\#d0d0ff\\ダイレクト接続先\\#dcdcdc\\のIPアドレスとポート番号を入力してください:" JOIN = "参加する" PUBLIC_LOBBIES = "公開ルーム" PRIVATE_LOBBIES = "非公開ルーム" @@ -283,14 +282,14 @@ DIRECT = "ダイレクト接続" [RULES] RULES_TITLE = "RULES" -RULE_1 = "1. 13歳以上であること。" -RULE_2 = "2. 不快な言葉、中傷、攻撃的な言葉を使わないこと。" -RULE_3 = "3. 非公式ビルドを使わないこと。" -RULE_4 = "4. ゲームをエクスプロイトする外部ツールを使用しないこと。" -RULE_5 = "5. 作者の許可なく、非公開MODを公開しないこと。" -RULE_6 = "6. 全てのNSFWコンテンツは禁止です。" -SUBJECT_TO_CHANGE = "ルールはアップデートで変更される可能性があります。" -NOTICE = "公開ルームではルールをお守りください。" +RULE_1 = "1. CoopNetの利用は13歳以上に限ります。" +RULE_2 = "2. ハラスメント(嫌がらせ)、ヘイトスピーチ、差別用語、その他攻撃的な言動は禁止です。" +RULE_3 = "3. CoopNetでは改造(改ざん)されていない正規のsm64coopdxのみが使用可能です。" +RULE_4 = "4. ゲームの脆弱性を悪用するための外部ツールの使用は禁止です。" +RULE_5 = "5. 作者の許可なく、未公開MODでルームをホストしないでください。" +RULE_6 = "6. ポルノやフェティッシュなコンテンツは一切禁止されています。これにはMOD、キャラクター、成人向けロールプレイなどが含まれますが、これらに限定されません。" +SUBJECT_TO_CHANGE = "これらのルールは今後のアップデートで変更される可能性があります。" +NOTICE = "公開ルームでプレイするためにCoopNetへ接続した時点で、あなたはこれらのルールを遵守することに同意したものとみなされます。" RULES = "ルールを見る" [MAIN] @@ -307,27 +306,27 @@ LEVEL = "コース" STAFF_ROLL = "スタッフロール" MUSIC = "BGM" RANDOM_STAGE = "ランダムなステージ" -PLAY_VANILLA_DEMOS = "バニラゲームのデモを再生" +PLAY_VANILLA_DEMOS = "オリジナルゲームのデモを再生" [MISC] DEBUG_TITLE = "DEBUG" FIXED_COLLISIONS = "修正された当たり判定" LUA_PROFILER = "Luaのプロファイラー" CTX_PROFILER = "Ctxのプロファイラー" -DEBUG_PRINT = "デバッグ情報の表示" -DEBUG_INFO = "デバッグの情報" -DEBUG_ERRORS = "デバッグのエラー" +DEBUG_PRINT = "デバッグログの表示" +DEBUG_INFO = "デバッグ情報の表示" +DEBUG_ERRORS = "デバッグエラーの表示" MISC_TITLE = "MISC" -PAUSE_IN_SINGLEPLAYER = "ソロプレイでの一時停止" +PAUSE_IN_SINGLEPLAYER = "1人プレイ中にポーズで一時停止を有効化" DISABLE_POPUPS = "ポップアップを無効にする" -USE_STANDARD_KEY_BINDINGS_CHAT = "初期のチャット操作" +USE_STANDARD_KEY_BINDINGS_CHAT = "旧式チャット操作" MENU_OPTIONS = "メニューの設定" INFORMATION = "情報" DEBUG = "デバッグ" LANGUAGE = "言語" COOP_COMPATIBILITY = "sm64ex-coopとの互換性を有効にする" R_BUTTON = "Rボタン - 設定" -L_BUTTON = "Lボタン - アクティブなMODを再読み込み" +L_BUTTON = "Lボタン - 有効化されたMODを再読み込み" [INFORMATION] INFORMATION_TITLE = "INFO" @@ -371,7 +370,7 @@ PLAYER_TITLE = "PLAYER" OVERALLS = "オーバーオール" SHIRT = "シャツ" GLOVES = "手袋" -SHOES = "くつ" +SHOES = "クツ" HAIR = "髪" SKIN = "肌" CAP = "帽子" @@ -408,8 +407,8 @@ MASTER_VOLUME = "主音量" MUSIC_VOLUME = "BGM音量" SFX_VOLUME = "SE音量" ENV_VOLUME = "環境音量" -FADEOUT = "遠い音のフェードアウト" -MUTE_FOCUS_LOSS = "非フォーカス時にミュート" +FADEOUT = "音の距離減衰" +MUTE_FOCUS_LOSS = "非フォーカス時に音をミュート" [LANGUAGE] LANGUAGE = "LANGUAGE" @@ -428,12 +427,11 @@ Spanish = "スペイン語 (Español)" [LOBBIES] PUBLIC_LOBBIES = "PUBLIC ROOMS" PRIVATE_LOBBIES = "PRIVATE ROOMS" -REFRESH = "更新" +REFRESH = "更新する" REFRESHING = "更新中…" -ENTER_PASSWORD = "部屋のパスワードを入力してください:" +ENTER_PASSWORD = "ルームのパスワードを入力してください:" SEARCH = "検索" -NONE_FOUND = "部屋が見つかりませんでした" -NO_LOBBIES_FOUND = "ロビーは見つからなかった。" +NO_LOBBIES_FOUND = "ルームが見つかりませんでした。" [CHANGELOG] CHANGELOG_TITLE = "CHANGELOG" From 2c367b556dcf113c87f59d1c3e616c38021d4ace Mon Sep 17 00:00:00 2001 From: djoslin0 Date: Sun, 15 Mar 2026 06:04:36 -0700 Subject: [PATCH 04/19] Add surface collision Lua hooks (#1139) * Add surface collision Lua hooks - Add 6 new hooks: HOOK_ON_FIND_WALL_COLLISION, HOOK_ON_FIND_CEIL, HOOK_ON_FIND_FLOOR, HOOK_ON_FIND_WATER_LEVEL, HOOK_ON_FIND_POISON_GAS_LEVEL, HOOK_ON_FIND_SURFACE_ON_RAY - Hooks expose current result values and allow overriding collision outputs from Lua * Remove unnecessary branches --------- Co-authored-by: MysterD --- autogen/lua_definitions/constants.lua | 14 +- autogen/lua_definitions/manual.lua | 2 +- docs/lua/constants.md | 8 +- docs/lua/guides/hooks.md | 6 + src/engine/surface_collision.c | 17 ++ src/pc/lua/smlua_constants_autogen.c | 8 +- src/pc/lua/smlua_hook_events.inl | 6 + src/pc/lua/smlua_hooks.c | 305 +++++++++++++++++++++++++- src/pc/lua/smlua_hooks.h | 8 + 9 files changed, 368 insertions(+), 6 deletions(-) diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 8e160c5d4..5bbb7065a 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -8207,7 +8207,13 @@ HOOK_MARIO_OVERRIDE_FLOOR_CLASS = 56 --- @type LuaHookedEventType HOOK_ON_ADD_SURFACE = 57 --- @type LuaHookedEventType HOOK_ON_CLEAR_AREAS = 58 --- @type LuaHookedEventType HOOK_ON_PACKET_BYTESTRING_RECEIVE = 59 --- @type LuaHookedEventType -HOOK_MAX = 60 --- @type LuaHookedEventType +HOOK_ON_FIND_WALL_COLLISION = 60 --- @type LuaHookedEventType +HOOK_ON_FIND_CEIL = 61 --- @type LuaHookedEventType +HOOK_ON_FIND_FLOOR = 62 --- @type LuaHookedEventType +HOOK_ON_FIND_WATER_LEVEL = 63 --- @type LuaHookedEventType +HOOK_ON_FIND_POISON_GAS_LEVEL = 64 --- @type LuaHookedEventType +HOOK_ON_FIND_SURFACE_ON_RAY = 65 --- @type LuaHookedEventType +HOOK_MAX = 66 --- @type LuaHookedEventType --- @alias LuaHookedEventType --- | `HOOK_UPDATE` @@ -8270,6 +8276,12 @@ HOOK_MAX = 60 --- @type LuaHookedEventType --- | `HOOK_ON_ADD_SURFACE` --- | `HOOK_ON_CLEAR_AREAS` --- | `HOOK_ON_PACKET_BYTESTRING_RECEIVE` +--- | `HOOK_ON_FIND_WALL_COLLISION` +--- | `HOOK_ON_FIND_CEIL` +--- | `HOOK_ON_FIND_FLOOR` +--- | `HOOK_ON_FIND_WATER_LEVEL` +--- | `HOOK_ON_FIND_POISON_GAS_LEVEL` +--- | `HOOK_ON_FIND_SURFACE_ON_RAY` --- | `HOOK_MAX` --- @type integer diff --git a/autogen/lua_definitions/manual.lua b/autogen/lua_definitions/manual.lua index 2ba4a4366..2ce161f8a 100644 --- a/autogen/lua_definitions/manual.lua +++ b/autogen/lua_definitions/manual.lua @@ -127,7 +127,7 @@ function update_chat_command_description(command, description) end --- @param hookEventType LuaHookedEventType When a function should run ---- @param func fun(...: any): any The function to run +--- @param func fun(...: any): any?, any? The function to run --- Different hooks can pass in different parameters and have different return values. Be sure to read the hooks guide for more information. function hook_event(hookEventType, func) -- ... diff --git a/docs/lua/constants.md b/docs/lua/constants.md index 0c92edac9..cf00279f7 100644 --- a/docs/lua/constants.md +++ b/docs/lua/constants.md @@ -3537,7 +3537,13 @@ | HOOK_ON_ADD_SURFACE | 57 | | HOOK_ON_CLEAR_AREAS | 58 | | HOOK_ON_PACKET_BYTESTRING_RECEIVE | 59 | -| HOOK_MAX | 60 | +| HOOK_ON_FIND_WALL_COLLISION | 60 | +| HOOK_ON_FIND_CEIL | 61 | +| HOOK_ON_FIND_FLOOR | 62 | +| HOOK_ON_FIND_WATER_LEVEL | 63 | +| HOOK_ON_FIND_POISON_GAS_LEVEL | 64 | +| HOOK_ON_FIND_SURFACE_ON_RAY | 65 | +| HOOK_MAX | 66 | - MAX_HOOKED_BEHAVIORS [:arrow_up_small:](#) diff --git a/docs/lua/guides/hooks.md b/docs/lua/guides/hooks.md index af1226bae..07d522650 100644 --- a/docs/lua/guides/hooks.md +++ b/docs/lua/guides/hooks.md @@ -151,6 +151,12 @@ The lua functions sent to `hook_event()` will be automatically called by SM64 wh | HOOK_MARIO_OVERRIDE_FLOOR_CLASS | Called when Mario's floor class logic updates, return a `SURFACE_CLASS_*` constant to override the type. | [MarioState](../structs.md#MarioState) mario, `integer` surfaceClass | | HOOK_ON_ADD_SURFACE | Called when collision surfaces are added. | [Surface](../structs.md#Surface) surface, `boolean` dynamic | | HOOK_ON_CLEAR_AREAS | Called when a level's areas are unloaded. | None | +| HOOK_ON_FIND_WALL_COLLISION | Called after wall collision detection completes. You can modify the `colData` fields directly. Return a number to override `numCollisions` | `number` posX, `number` posY, `number` posZ, [WallCollisionData](../structs.md#WallCollisionData) colData | +| HOOK_ON_FIND_CEIL | Called after ceiling detection completes. Return `height` to override height, or `height, surface` to override both | `number` posX, `number` posY, `number` posZ, [Surface](../structs.md#Surface) ceil, `number` height | +| HOOK_ON_FIND_FLOOR | Called after floor detection completes. Return `height` to override height, or `height, surface` to override both | `number` posX, `number` posY, `number` posZ, [Surface](../structs.md#Surface) floor, `number` height | +| HOOK_ON_FIND_WATER_LEVEL | Called after water level detection completes. Return a number to override the water level | `number` x, `number` z, `number` waterLevel | +| HOOK_ON_FIND_POISON_GAS_LEVEL | Called after poison gas level detection completes. Return a number to override the gas level | `number` x, `number` z, `number` gasLevel | +| HOOK_ON_FIND_SURFACE_ON_RAY | Called after ray-surface intersection completes. Return `surface` to override the hit surface, or `surface, hitPos` to override both | `Vec3f` orig, `Vec3f` dir, [Surface](../structs.md#Surface) hitSurface, `Vec3f` hitPos | ### Parameters diff --git a/src/engine/surface_collision.c b/src/engine/surface_collision.c index 15f6f9a9f..97414235e 100644 --- a/src/engine/surface_collision.c +++ b/src/engine/surface_collision.c @@ -12,6 +12,7 @@ #include "game/hardcoded.h" #include "pc/utils/misc.h" #include "pc/network/network.h" +#include "pc/lua/smlua_hooks.h" Vec3f gFindWallDirection = { 0 }; u8 gFindWallDirectionActive = false; @@ -343,6 +344,9 @@ s32 find_wall_collisions(struct WallCollisionData *colData) { s32 numCollisions = 0; s16 x = colData->x; s16 z = colData->z; + f32 posX = colData->x; + f32 posY = colData->y; + f32 posZ = colData->z; colData->numWalls = 0; @@ -371,6 +375,8 @@ s32 find_wall_collisions(struct WallCollisionData *colData) { // Increment the debug tracker. gNumCalls.wall += 1; + smlua_call_event_hooks(HOOK_ON_FIND_WALL_COLLISION, posX, posY, posZ, colData, &numCollisions); + return numCollisions; } @@ -544,6 +550,8 @@ f32 find_ceil(f32 posX, f32 posY, f32 posZ, RET struct Surface **pceil) { // Increment the debug tracker. gNumCalls.ceil += 1; + smlua_call_event_hooks(HOOK_ON_FIND_CEIL, posX, posY, posZ, pceil, &height); + return height; } @@ -882,6 +890,8 @@ f32 find_floor(f32 xPos, f32 yPos, f32 zPos, RET struct Surface **pfloor) { // Increment the debug tracker. gNumCalls.floor += 1; + smlua_call_event_hooks(HOOK_ON_FIND_FLOOR, xPos, yPos, zPos, pfloor, &height); + return height; } @@ -922,6 +932,8 @@ f32 find_water_level(f32 x, f32 z) { } } + smlua_call_event_hooks(HOOK_ON_FIND_WATER_LEVEL, x, z, &waterLevel); + return waterLevel; } @@ -963,6 +975,8 @@ f32 find_poison_gas_level(f32 x, f32 z) { } } + smlua_call_event_hooks(HOOK_ON_FIND_POISON_GAS_LEVEL, x, z, &gasLevel); + return gasLevel; } @@ -1227,6 +1241,7 @@ void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Ve if (normalized_dir[1] >= 1.0f || normalized_dir[1] <= -1.0f) { find_surface_on_ray_cell(cellX, cellZ, orig, normalized_dir, dir_length, hit_surface, hit_pos, &max_length); + smlua_call_event_hooks(HOOK_ON_FIND_SURFACE_ON_RAY, orig, dir, hit_surface, hit_pos); return; } @@ -1249,4 +1264,6 @@ void find_surface_on_ray(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Ve cellX = (s16)fCellX; cellZ = (s16)fCellZ; } + + smlua_call_event_hooks(HOOK_ON_FIND_SURFACE_ON_RAY, orig, dir, hit_surface, hit_pos); } diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index d7ca5a882..60737b707 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -3547,7 +3547,13 @@ char gSmluaConstants[] = "" "HOOK_ON_ADD_SURFACE=57\n" "HOOK_ON_CLEAR_AREAS=58\n" "HOOK_ON_PACKET_BYTESTRING_RECEIVE=59\n" -"HOOK_MAX=60\n" +"HOOK_ON_FIND_WALL_COLLISION=60\n" +"HOOK_ON_FIND_CEIL=61\n" +"HOOK_ON_FIND_FLOOR=62\n" +"HOOK_ON_FIND_WATER_LEVEL=63\n" +"HOOK_ON_FIND_POISON_GAS_LEVEL=64\n" +"HOOK_ON_FIND_SURFACE_ON_RAY=65\n" +"HOOK_MAX=66\n" "MAX_HOOKED_BEHAVIORS=1024\n" "HUD_DISPLAY_LIVES=0\n" "HUD_DISPLAY_COINS=1\n" diff --git a/src/pc/lua/smlua_hook_events.inl b/src/pc/lua/smlua_hook_events.inl index 16bbbaee2..71f4a915c 100644 --- a/src/pc/lua/smlua_hook_events.inl +++ b/src/pc/lua/smlua_hook_events.inl @@ -58,3 +58,9 @@ SMLUA_EVENT_HOOK(HOOK_MARIO_OVERRIDE_FLOOR_CLASS, HOOK_RETURN_ON_OUTPUT_SET, str SMLUA_EVENT_HOOK(HOOK_ON_ADD_SURFACE, HOOK_RETURN_NEVER, struct Surface *surface, bool dynamic) SMLUA_EVENT_HOOK(HOOK_ON_CLEAR_AREAS, HOOK_RETURN_NEVER) SMLUA_EVENT_HOOK(HOOK_ON_PACKET_BYTESTRING_RECEIVE, HOOK_RETURN_NEVER, s32 modIndex, s32 valueIndex) +SMLUA_EVENT_HOOK(HOOK_ON_FIND_WALL_COLLISION, _, f32 posX, f32 posY, f32 posZ, struct WallCollisionData *colData, s32 *numCollisions) // Manually defined hook +SMLUA_EVENT_HOOK(HOOK_ON_FIND_CEIL, _, f32 posX, f32 posY, f32 posZ, struct Surface **pceil, f32 *height) // Manually defined hook +SMLUA_EVENT_HOOK(HOOK_ON_FIND_FLOOR, _, f32 posX, f32 posY, f32 posZ, struct Surface **pfloor, f32 *height) // Manually defined hook +SMLUA_EVENT_HOOK(HOOK_ON_FIND_WATER_LEVEL, _, f32 x, f32 z, f32 *waterLevel) // Manually defined hook +SMLUA_EVENT_HOOK(HOOK_ON_FIND_POISON_GAS_LEVEL, _, f32 x, f32 z, f32 *gasLevel) // Manually defined hook +SMLUA_EVENT_HOOK(HOOK_ON_FIND_SURFACE_ON_RAY, _, Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos) // Manually defined hook diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index fc1dafcae..0984c8223 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -26,6 +26,9 @@ #include "game/print.h" #include "gfx_dimensions.h" +extern void smlua_new_vec3f(Vec3f src); +extern void smlua_get_vec3f(Vec3f dest, int index); + #define MAX_HOOKED_REFERENCES 64 #define LUA_BEHAVIOR_FLAG (1 << 15) @@ -172,7 +175,6 @@ bool smlua_call_event_hooks_HOOK_ON_NAMETAGS_RENDER(s32 playerIndex, Vec3f pos, lua_pushinteger(L, playerIndex); // push pos - extern void smlua_new_vec3f(Vec3f src); smlua_new_vec3f(pos); // call the callback @@ -203,7 +205,6 @@ bool smlua_call_event_hooks_HOOK_ON_NAMETAGS_RENDER(s32 playerIndex, Vec3f pos, // pos lua_getfield(L, -1, "pos"); if (lua_type(L, -1) == LUA_TTABLE) { - extern void smlua_get_vec3f(Vec3f dest, int index); smlua_get_vec3f(pos, -1); override = true; } @@ -220,6 +221,306 @@ bool smlua_call_event_hooks_HOOK_ON_NAMETAGS_RENDER(s32 playerIndex, Vec3f pos, return false; } +bool smlua_call_event_hooks_HOOK_ON_FIND_WALL_COLLISION(f32 posX, f32 posY, f32 posZ, struct WallCollisionData *colData, s32 *numCollisions) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_WALL_COLLISION]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push posX, posY, posZ + lua_pushnumber(L, posX); + lua_pushnumber(L, posY); + lua_pushnumber(L, posZ); + + // push colData + smlua_push_object(L, LOT_WALLCOLLISIONDATA, colData, NULL); + + // call the callback (4 args, 1 result) + if (0 != smlua_call_hook(L, 4, 1, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_WALL_COLLISION]); + lua_settop(L, prevTop); + continue; + } + + // return number overrides numCollisions + if (lua_type(L, -1) == LUA_TNUMBER) { + *numCollisions = smlua_to_integer(L, -1); + lua_settop(L, prevTop); + sInHook = false; + return true; + } + + lua_settop(L, prevTop); + } + sInHook = false; + return false; +} + +bool smlua_call_event_hooks_HOOK_ON_FIND_CEIL(f32 posX, f32 posY, f32 posZ, struct Surface **pceil, f32 *height) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_CEIL]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push posX, posY, posZ + lua_pushnumber(L, posX); + lua_pushnumber(L, posY); + lua_pushnumber(L, posZ); + + // push current ceil surface (or nil) + if (pceil && *pceil) { + smlua_push_object(L, LOT_SURFACE, *pceil, NULL); + } else { + lua_pushnil(L); + } + + // push current height + lua_pushnumber(L, *height); + + // call the callback (5 args, 2 results) + if (0 != smlua_call_hook(L, 5, 2, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_CEIL]); + lua_settop(L, prevTop); + continue; + } + + bool override = false; + + // first return value: height (number) + if (lua_type(L, -2) == LUA_TNUMBER) { + *height = smlua_to_number(L, -2); + override = true; + } + + // second return value: surface (userdata) + if (lua_type(L, -1) == LUA_TUSERDATA) { + struct Surface *surface = (struct Surface *)smlua_to_cobject(L, -1, LOT_SURFACE); + if (surface && pceil) { + *pceil = surface; + override = true; + } + } + + lua_settop(L, prevTop); + if (override) { sInHook = false; return true; } + } + sInHook = false; + return false; +} + +bool smlua_call_event_hooks_HOOK_ON_FIND_FLOOR(f32 posX, f32 posY, f32 posZ, struct Surface **pfloor, f32 *height) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_FLOOR]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push posX, posY, posZ + lua_pushnumber(L, posX); + lua_pushnumber(L, posY); + lua_pushnumber(L, posZ); + + // push current floor surface (or nil) + if (pfloor && *pfloor) { + smlua_push_object(L, LOT_SURFACE, *pfloor, NULL); + } else { + lua_pushnil(L); + } + + // push current height + lua_pushnumber(L, *height); + + // call the callback (5 args, 2 results) + if (0 != smlua_call_hook(L, 5, 2, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_FLOOR]); + lua_settop(L, prevTop); + continue; + } + + bool override = false; + + // first return value: height (number) + if (lua_type(L, -2) == LUA_TNUMBER) { + *height = smlua_to_number(L, -2); + override = true; + } + + // second return value: surface (userdata) + if (lua_type(L, -1) == LUA_TUSERDATA) { + struct Surface *surface = (struct Surface *)smlua_to_cobject(L, -1, LOT_SURFACE); + if (surface && pfloor) { + *pfloor = surface; + override = true; + } + } + + lua_settop(L, prevTop); + if (override) { sInHook = false; return true; } + } + sInHook = false; + return false; +} + +bool smlua_call_event_hooks_HOOK_ON_FIND_SURFACE_ON_RAY(Vec3f orig, Vec3f dir, struct Surface **hit_surface, Vec3f hit_pos) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_SURFACE_ON_RAY]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push orig, dir + smlua_new_vec3f(orig); + smlua_new_vec3f(dir); + + // push hit_surface (or nil) + if (hit_surface && *hit_surface) { + smlua_push_object(L, LOT_SURFACE, *hit_surface, NULL); + } else { + lua_pushnil(L); + } + + // push hit_pos + smlua_new_vec3f(hit_pos); + + // call the callback (4 args, 2 results) + if (0 != smlua_call_hook(L, 4, 2, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_SURFACE_ON_RAY]); + lua_settop(L, prevTop); + continue; + } + + bool override = false; + + // first return value: surface (userdata) + if (lua_type(L, -2) == LUA_TUSERDATA) { + struct Surface *surface = (struct Surface *)smlua_to_cobject(L, -2, LOT_SURFACE); + if (surface && hit_surface) { + *hit_surface = surface; + override = true; + } + } + + // second return value: hitPos (table {x, y, z}) + if (lua_type(L, -1) == LUA_TTABLE) { + smlua_get_vec3f(hit_pos, -1); + override = true; + } + + lua_settop(L, prevTop); + if (override) { sInHook = false; return true; } + } + sInHook = false; + return false; +} + +bool smlua_call_event_hooks_HOOK_ON_FIND_WATER_LEVEL(f32 x, f32 z, f32 *waterLevel) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_WATER_LEVEL]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push x, z + lua_pushnumber(L, x); + lua_pushnumber(L, z); + + // push current water level + lua_pushnumber(L, *waterLevel); + + // call the callback (3 args, 1 result) + if (0 != smlua_call_hook(L, 3, 1, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_WATER_LEVEL]); + lua_settop(L, prevTop); + continue; + } + + // return number overrides waterLevel + if (lua_type(L, -1) == LUA_TNUMBER) { + *waterLevel = smlua_to_number(L, -1); + lua_settop(L, prevTop); + sInHook = false; + return true; + } + + lua_settop(L, prevTop); + } + sInHook = false; + return false; +} + +bool smlua_call_event_hooks_HOOK_ON_FIND_POISON_GAS_LEVEL(f32 x, f32 z, f32 *gasLevel) { + static bool sInHook = false; + lua_State *L = gLuaState; + if (L == NULL || sInHook) { return false; } + sInHook = true; + + struct LuaHookedEvent *hook = &sHookedEvents[HOOK_ON_FIND_POISON_GAS_LEVEL]; + for (int i = 0; i < hook->count; i++) { + s32 prevTop = lua_gettop(L); + + // push the callback onto the stack + lua_rawgeti(L, LUA_REGISTRYINDEX, hook->reference[i]); + + // push x, z + lua_pushnumber(L, x); + lua_pushnumber(L, z); + + // push current gas level + lua_pushnumber(L, *gasLevel); + + // call the callback (3 args, 1 result) + if (0 != smlua_call_hook(L, 3, 1, 0, hook->mod[i], hook->modFile[i])) { + LOG_LUA("Failed to call the callback for hook %s", sLuaHookedEventTypeName[HOOK_ON_FIND_POISON_GAS_LEVEL]); + lua_settop(L, prevTop); + continue; + } + + // return number overrides gasLevel + if (lua_type(L, -1) == LUA_TNUMBER) { + *gasLevel = smlua_to_number(L, -1); + lua_settop(L, prevTop); + sInHook = false; + return true; + } + + lua_settop(L, prevTop); + } + sInHook = false; + return false; +} + //////////////////// // hooked actions // //////////////////// diff --git a/src/pc/lua/smlua_hooks.h b/src/pc/lua/smlua_hooks.h index a857bbad3..c814c73e9 100644 --- a/src/pc/lua/smlua_hooks.h +++ b/src/pc/lua/smlua_hooks.h @@ -11,6 +11,8 @@ // forward declare struct Camera; struct WarpDest; +struct WallCollisionData; +struct Surface; // ! Hooks must be added at the end enum LuaHookedEventType { @@ -74,6 +76,12 @@ enum LuaHookedEventType { HOOK_ON_ADD_SURFACE, HOOK_ON_CLEAR_AREAS, HOOK_ON_PACKET_BYTESTRING_RECEIVE, + HOOK_ON_FIND_WALL_COLLISION, + HOOK_ON_FIND_CEIL, + HOOK_ON_FIND_FLOOR, + HOOK_ON_FIND_WATER_LEVEL, + HOOK_ON_FIND_POISON_GAS_LEVEL, + HOOK_ON_FIND_SURFACE_ON_RAY, HOOK_MAX, }; From 8735cf96adf5c876f953cb911793403afdeaa4d7 Mon Sep 17 00:00:00 2001 From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com> Date: Sun, 15 Mar 2026 15:49:01 -0500 Subject: [PATCH 05/19] Add `get_mod_files` (#1091) * Add `get_mod_files` * Dont allocate memory, use the stack * Implement peachy's suggestion * Actually allow optional to wrok --- autogen/lua_definitions/functions.lua | 8 +++++ docs/lua/functions-7.md | 24 ++++++++++++++ docs/lua/functions.md | 1 + src/pc/lua/smlua_functions_autogen.c | 23 +++++++++++++ src/pc/lua/utils/smlua_misc_utils.c | 48 +++++++++++++++++++++++++++ src/pc/lua/utils/smlua_misc_utils.h | 4 ++- 6 files changed, 107 insertions(+), 1 deletion(-) diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index 746a8f4c4..c716033b3 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -11647,6 +11647,14 @@ function get_active_mod() -- ... end +--- @param mod Mod +--- @param subDirectory? string +--- @return table +--- Gets all files a mod contains +function get_mod_files(mod, subDirectory) + -- ... +end + --- @param title string --- Sets the window title to a custom title function set_window_title(title) diff --git a/docs/lua/functions-7.md b/docs/lua/functions-7.md index 207edbb6f..1dd27c2d7 100644 --- a/docs/lua/functions-7.md +++ b/docs/lua/functions-7.md @@ -2107,6 +2107,30 @@ Gets the mod currently being processed
+## [get_mod_files](#get_mod_files) + +### Description +Gets all files a mod contains + +### Lua Example +`local tableValue = get_mod_files(mod, subDirectory)` + +### Parameters +| Field | Type | +| ----- | ---- | +| mod | [Mod](structs.md#Mod) | +| subDirectory | `string` | + +### Returns +- `table` + +### C Prototype +`LuaTable get_mod_files(struct Mod* mod, OPTIONAL const char* subDirectory);` + +[:arrow_up_small:](#) + +
+ ## [set_window_title](#set_window_title) ### Description diff --git a/docs/lua/functions.md b/docs/lua/functions.md index 4076375df..ecce742d3 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -2071,6 +2071,7 @@ - [set_environment_region](functions-7.md#set_environment_region) - [mod_file_exists](functions-7.md#mod_file_exists) - [get_active_mod](functions-7.md#get_active_mod) + - [get_mod_files](functions-7.md#get_mod_files) - [set_window_title](functions-7.md#set_window_title) - [reset_window_title](functions-7.md#reset_window_title) - [get_os_name](functions-7.md#get_os_name) diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index e227a54aa..70d040309 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -34301,6 +34301,28 @@ int smlua_func_get_active_mod(UNUSED lua_State* L) { return 1; } +int smlua_func_get_mod_files(lua_State* L) { + if (L == NULL) { return 0; } + + int top = lua_gettop(L); + if (top < 1 || top > 2) { + LOG_LUA_LINE("Improper param count for '%s': Expected between %u and %u, Received %u", "get_mod_files", 1, 2, top); + return 0; + } + + struct Mod* mod = (struct Mod*)smlua_to_cobject(L, 1, LOT_MOD); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 1, "get_mod_files"); return 0; } + const char* subDirectory = (const char*) NULL; + if (top >= 2) { + subDirectory = smlua_to_string(L, 2); + if (!gSmLuaConvertSuccess) { LOG_LUA("Failed to convert parameter %u for function '%s'", 2, "get_mod_files"); return 0; } + } + + smlua_push_lua_table(L, get_mod_files(mod, subDirectory)); + + return 1; +} + int smlua_func_set_window_title(lua_State* L) { if (L == NULL) { return 0; } @@ -38685,6 +38707,7 @@ void smlua_bind_functions_autogen(void) { smlua_bind_function(L, "set_environment_region", smlua_func_set_environment_region); smlua_bind_function(L, "mod_file_exists", smlua_func_mod_file_exists); smlua_bind_function(L, "get_active_mod", smlua_func_get_active_mod); + smlua_bind_function(L, "get_mod_files", smlua_func_get_mod_files); smlua_bind_function(L, "set_window_title", smlua_func_set_window_title); smlua_bind_function(L, "reset_window_title", smlua_func_reset_window_title); smlua_bind_function(L, "get_os_name", smlua_func_get_os_name); diff --git a/src/pc/lua/utils/smlua_misc_utils.c b/src/pc/lua/utils/smlua_misc_utils.c index de447b3d1..9e90a45fb 100644 --- a/src/pc/lua/utils/smlua_misc_utils.c +++ b/src/pc/lua/utils/smlua_misc_utils.c @@ -607,6 +607,54 @@ struct Mod* get_active_mod(void) { return gLuaActiveMod; } +LuaTable get_mod_files(struct Mod* mod, OPTIONAL const char* subDirectory) { + if (!mod) { + struct lua_State *L = gLuaState; + if (L) { + lua_newtable(L); + return smlua_to_lua_table(L, -1); + } + return 0; + } + + char normalizedSubDir[SYS_MAX_PATH] = { 0 }; + snprintf(normalizedSubDir, SYS_MAX_PATH, "%s", subDirectory ? subDirectory : ""); + normalize_path(normalizedSubDir); + + size_t subDirLen = strlen(normalizedSubDir); + if (subDirLen > 0 && subDirLen + 1 < SYS_MAX_PATH && normalizedSubDir[subDirLen - 1] != '/') { + strcat(normalizedSubDir, "/"); + subDirLen = strlen(normalizedSubDir); + } + + struct lua_State *L = gLuaState; + if (!L) { return 0; } + + LUA_STACK_CHECK_BEGIN_NUM(L, 1); + + lua_newtable(L); + + int luaTableIndex = 1; + for (int i = 0; i < mod->fileCount; i++) { + struct ModFile* file = &mod->files[i]; + char normalizedPath[SYS_MAX_PATH] = { 0 }; + if (snprintf(normalizedPath, SYS_MAX_PATH, "%s", file->relativePath) < 0) { + LOG_ERROR("Failed to copy relativePath for normalization: %s", file->relativePath); + continue; + } + normalize_path(normalizedPath); + + if (strncmp(normalizedPath, normalizedSubDir, subDirLen) == 0) { + lua_pushstring(L, file->relativePath); + lua_rawseti(L, -2, luaTableIndex++); + } + } + + LUA_STACK_CHECK_END(L); + + return smlua_to_lua_table(L, -1); +} + /// void set_window_title(const char* title) { diff --git a/src/pc/lua/utils/smlua_misc_utils.h b/src/pc/lua/utils/smlua_misc_utils.h index e4bb3c1eb..1b7d929cb 100644 --- a/src/pc/lua/utils/smlua_misc_utils.h +++ b/src/pc/lua/utils/smlua_misc_utils.h @@ -37,7 +37,7 @@ enum ActSelectHudPart { ACT_SELECT_HUD_ACT_NAME = 1 << 3, ACT_SELECT_HUD_STAR_NUM = 1 << 4, ACT_SELECT_HUD_PLAYERS_IN_LEVEL = 1 << 5, - + ACT_SELECT_HUD_NONE = 0, ACT_SELECT_HUD_ALL = ACT_SELECT_HUD_SCORE | ACT_SELECT_HUD_LEVEL_NAME | ACT_SELECT_HUD_COURSE_NUM | ACT_SELECT_HUD_ACT_NAME |ACT_SELECT_HUD_STAR_NUM | ACT_SELECT_HUD_PLAYERS_IN_LEVEL }; @@ -246,6 +246,8 @@ void set_environment_region(u8 index, s16 value); bool mod_file_exists(const char* filename); /* |description|Gets the mod currently being processed|descriptionEnd| */ struct Mod* get_active_mod(void); +/* |description|Gets all files a mod contains|descriptionEnd| */ +LuaTable get_mod_files(struct Mod* mod, OPTIONAL const char* subDirectory); /* |description|Sets the window title to a custom title|descriptionEnd| */ void set_window_title(const char* title); From e645aa7cef191ff69b441b5234300f13ae7415bf Mon Sep 17 00:00:00 2001 From: PeachyPeach <72323920+PeachyPeachSM64@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:32:39 +0100 Subject: [PATCH 06/19] Adds compilation fixes for ARM in headless mode (#1151) Co-authored-by: Alfredo --- src/engine/math_util.c | 2 +- src/pc/platform.c | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/engine/math_util.c b/src/engine/math_util.c index f13dc051f..592968651 100644 --- a/src/engine/math_util.c +++ b/src/engine/math_util.c @@ -827,7 +827,7 @@ OPTIMIZE_O3 bool mtxf_inverse_non_affine(VEC_OUT Mat4 dest, Mat4 src) { if (fabsf(aug[i][k]) > fabsf(aug[piv][k])) { piv = i; } } - if (fabsf(aug[piv][k]) < FLT_EPSILON) { return false; } + if (fabsf(aug[piv][k]) < __FLT_EPSILON__) { return false; } // swap pivot row into place if (piv != k) { diff --git a/src/pc/platform.c b/src/pc/platform.c index 331a7c075..185b85bd9 100644 --- a/src/pc/platform.c +++ b/src/pc/platform.c @@ -411,4 +411,12 @@ static void sys_fatal_impl(const char *msg) { exit(1); } +const char *sys_resource_path(void) { + return "."; +} + +const char *sys_exe_path_dir(void) { + return "."; +} + #endif // platform switch From efe794d1d83002525f5e1dc5ba9fcd39795cfaae Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Thu, 19 Mar 2026 20:45:55 +1000 Subject: [PATCH 07/19] more accurate types for these iterators --- src/game/behaviors/texscroll.inc.c | 8 ++++---- src/game/scroll_targets.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/game/behaviors/texscroll.inc.c b/src/game/behaviors/texscroll.inc.c index 5ac1b4b69..10eeed2fa 100644 --- a/src/game/behaviors/texscroll.inc.c +++ b/src/game/behaviors/texscroll.inc.c @@ -137,7 +137,7 @@ void uv_update_scroll(void) { scroll->prevF32 = calloc(scroll->size, sizeof(f32)); u8 bhvIndex = MIN(bhv, 2); - for (u16 k = 0; k < scroll->size; k++) { + for (u32 k = 0; k < scroll->size; k++) { scroll->interpF32[k] = verts[k]->n.ob[bhvIndex]; } } else { @@ -145,7 +145,7 @@ void uv_update_scroll(void) { scroll->prevS16 = calloc(scroll->size, sizeof(s16)); u8 bhvIndex = MIN(bhv-SCROLL_UV_X, 1); - for (u16 k = 0; k < scroll->size; k++) { + for (u32 k = 0; k < scroll->size; k++) { scroll->interpS16[k] = verts[k]->n.tc[bhvIndex]; } } @@ -154,12 +154,12 @@ void uv_update_scroll(void) { // Prepare for interpolation if (bhv < SCROLL_UV_X) { u8 bhvIndex = MIN(bhv, 2); - for (u16 i = 0; i < scroll->size; i++) { + for (u32 i = 0; i < scroll->size; i++) { scroll->prevF32[i] = verts[i]->n.ob[bhvIndex]; } } else { u8 bhvIndex = MIN(bhv-SCROLL_UV_X, 1); - for (u16 i = 0; i < scroll->size; i++) { + for (u32 i = 0; i < scroll->size; i++) { scroll->prevS16[i] = verts[i]->n.tc[bhvIndex]; } } diff --git a/src/game/scroll_targets.c b/src/game/scroll_targets.c index ff00cf244..20d7df51a 100644 --- a/src/game/scroll_targets.c +++ b/src/game/scroll_targets.c @@ -133,13 +133,13 @@ void patch_scroll_targets_interpolated(f32 delta) { Vtx* *verts = scroll->vertices; if (scroll->bhv < SCROLL_UV_X) { u8 bhvIndex = MIN(scroll->bhv, 2); - for (u16 k = 0; k < scroll->size; k++) { + for (u32 k = 0; k < scroll->size; k++) { f32 diff = wrap_f32(scroll->interpF32[k] - scroll->prevF32[k]); verts[k]->n.ob[bhvIndex] = wrap_f32(scroll->prevF32[k] + diff * delta); } } else { u8 bhvIndex = MIN(scroll->bhv-SCROLL_UV_X, 1); - for (u16 k = 0; k < scroll->size; k++) { + for (u32 k = 0; k < scroll->size; k++) { s32 diff = wrap_s32(scroll->interpS16[k] - scroll->prevS16[k]); verts[k]->n.tc[bhvIndex] = wrap_s32(scroll->prevS16[k] + diff * delta); } From 2b76ba23ff730e58c622d2013eec49c9ce8aa5f2 Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Thu, 19 Mar 2026 21:30:42 +1000 Subject: [PATCH 08/19] fix a logic error in scrolling textures --- src/game/behaviors/texscroll.inc.c | 18 ++++++------------ src/game/scroll_targets.c | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/game/behaviors/texscroll.inc.c b/src/game/behaviors/texscroll.inc.c index 10eeed2fa..c2fc62bba 100644 --- a/src/game/behaviors/texscroll.inc.c +++ b/src/game/behaviors/texscroll.inc.c @@ -133,16 +133,16 @@ void uv_update_scroll(void) { scroll->hasInterpInit = true; scroll->bhv = bhv; if (bhv < SCROLL_UV_X) { - scroll->interpF32 = calloc(scroll->size, sizeof(f32)); - scroll->prevF32 = calloc(scroll->size, sizeof(f32)); + scroll->interpF32 = malloc(scroll->size * sizeof(f32)); + scroll->prevF32 = malloc(scroll->size * sizeof(f32)); u8 bhvIndex = MIN(bhv, 2); for (u32 k = 0; k < scroll->size; k++) { scroll->interpF32[k] = verts[k]->n.ob[bhvIndex]; } } else { - scroll->interpS16 = calloc(scroll->size, sizeof(s16)); - scroll->prevS16 = calloc(scroll->size, sizeof(s16)); + scroll->interpS16 = malloc(scroll->size * sizeof(s16)); + scroll->prevS16 = malloc(scroll->size * sizeof(s16)); u8 bhvIndex = MIN(bhv-SCROLL_UV_X, 1); for (u32 k = 0; k < scroll->size; k++) { @@ -153,15 +153,9 @@ void uv_update_scroll(void) { // Prepare for interpolation if (bhv < SCROLL_UV_X) { - u8 bhvIndex = MIN(bhv, 2); - for (u32 i = 0; i < scroll->size; i++) { - scroll->prevF32[i] = verts[i]->n.ob[bhvIndex]; - } + memcpy(scroll->prevF32, scroll->interpF32, scroll->size * sizeof(f32)); } else { - u8 bhvIndex = MIN(bhv-SCROLL_UV_X, 1); - for (u32 i = 0; i < scroll->size; i++) { - scroll->prevS16[i] = verts[i]->n.tc[bhvIndex]; - } + memcpy(scroll->prevS16, scroll->interpS16, scroll->size * sizeof(s16)); } scroll->needInterp = true; diff --git a/src/game/scroll_targets.c b/src/game/scroll_targets.c index 20d7df51a..13d06cefc 100644 --- a/src/game/scroll_targets.c +++ b/src/game/scroll_targets.c @@ -18,7 +18,7 @@ struct ScrollTarget *get_scroll_targets(u32 id, u16 size, u16 offset) { if (size > scroll->size) { size = scroll->size; } // Don't use an invalid size if (size + offset >= scroll->size) { return NULL; } // If the offset is invalid, Abort. scroll->hasOffset = true; - Vtx* *newVtx = calloc(size, sizeof(Vtx*)); + Vtx* *newVtx = malloc(size * sizeof(Vtx*)); if (!newVtx) { return NULL; } for (u32 i = 0; i < size; i++) { newVtx[i] = scroll->vertices[i + offset]; @@ -51,11 +51,18 @@ struct ScrollTarget* find_or_create_scroll_targets(u32 id, bool hasOffset) { } if (scroll == NULL) { - scroll = calloc(1, sizeof(struct ScrollTarget)); + scroll = malloc(sizeof(struct ScrollTarget)); scroll->id = id; scroll->size = 0; scroll->vertices = NULL; scroll->hasOffset = hasOffset; + scroll->hasInterpInit = false; + scroll->needInterp = false; + scroll->interpF32 = NULL; + scroll->prevF32 = NULL; + scroll->interpS16 = NULL; + scroll->prevS16 = NULL; + scroll->bhv = 0; hmap_put(sScrollTargets, id, scroll); } @@ -77,8 +84,10 @@ void add_vtx_scroll_target(u32 id, Vtx *vtx, u32 size, bool hasOffset) { Vtx* *newArray = realloc(scroll->vertices, newSize); if (!newArray) { - newArray = calloc(1, newSize); + newArray = malloc(newSize); + if (!newArray) { return; } memcpy(newArray, scroll->vertices, oldSize); + memset(newArray + scroll->size, 0, size * sizeof(Vtx*)); free(scroll->vertices); } From 34d4069c53dd3d6a626ae5946ffde65864795f16 Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Thu, 19 Mar 2026 22:39:02 +1000 Subject: [PATCH 09/19] more scroll texture fixes --- src/game/behaviors/texscroll.inc.c | 20 ++++++++++++++------ src/game/scroll_targets.c | 16 +++++++--------- src/pc/lua/smlua_functions.c | 5 +++++ 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/src/game/behaviors/texscroll.inc.c b/src/game/behaviors/texscroll.inc.c index c2fc62bba..6a1256fdc 100644 --- a/src/game/behaviors/texscroll.inc.c +++ b/src/game/behaviors/texscroll.inc.c @@ -70,13 +70,9 @@ static inline void shift_UV_NORMAL(struct ScrollTarget *scroll, u16 vertcount, s verts[0]->n.flag++; } else { if (bhv < SCROLL_UV_X) { - for (i = 0; i < vertcount; i++) { - scroll->prevF32[i] = scroll->interpF32[i]; - } + memcpy(scroll->prevF32, scroll->interpF32, vertcount * sizeof(f32)); } else { - for (i = 0; i < vertcount; i++) { - scroll->prevS16[i] = scroll->interpS16[i]; - } + memcpy(scroll->prevS16, scroll->interpS16, vertcount * sizeof(s16)); } } } @@ -135,6 +131,12 @@ void uv_update_scroll(void) { if (bhv < SCROLL_UV_X) { scroll->interpF32 = malloc(scroll->size * sizeof(f32)); scroll->prevF32 = malloc(scroll->size * sizeof(f32)); + if (!scroll->interpF32 || !scroll->prevF32) { + free(scroll->interpF32); + free(scroll->prevF32); + scroll->interpF32 = scroll->prevF32 = NULL; + return; + } u8 bhvIndex = MIN(bhv, 2); for (u32 k = 0; k < scroll->size; k++) { @@ -143,6 +145,12 @@ void uv_update_scroll(void) { } else { scroll->interpS16 = malloc(scroll->size * sizeof(s16)); scroll->prevS16 = malloc(scroll->size * sizeof(s16)); + if (!scroll->interpS16 || !scroll->prevS16) { + free(scroll->interpS16); + free(scroll->prevS16); + scroll->interpS16 = scroll->prevS16 = NULL; + return; + } u8 bhvIndex = MIN(bhv-SCROLL_UV_X, 1); for (u32 k = 0; k < scroll->size; k++) { diff --git a/src/game/scroll_targets.c b/src/game/scroll_targets.c index 13d06cefc..a2af8bc19 100644 --- a/src/game/scroll_targets.c +++ b/src/game/scroll_targets.c @@ -13,19 +13,16 @@ struct ScrollTarget *get_scroll_targets(u32 id, u16 size, u16 offset) { if (scroll) { // If we need to, realloc the block of vertices - if ((!scroll->hasOffset && offset > 0) || size < scroll->size) { - if (scroll->hasOffset) { return NULL; } + if (!scroll->hasOffset && (offset > 0 || size < scroll->size)) { if (size > scroll->size) { size = scroll->size; } // Don't use an invalid size - if (size + offset >= scroll->size) { return NULL; } // If the offset is invalid, Abort. - scroll->hasOffset = true; + if (offset > 0 && size + offset >= scroll->size) { return NULL; } // If the offset is invalid, Abort. Vtx* *newVtx = malloc(size * sizeof(Vtx*)); if (!newVtx) { return NULL; } - for (u32 i = 0; i < size; i++) { - newVtx[i] = scroll->vertices[i + offset]; - } + memcpy(newVtx, scroll->vertices + offset, size * sizeof(Vtx*)); free(scroll->vertices); scroll->vertices = newVtx; scroll->size = size; + scroll->hasOffset = true; } return scroll; @@ -86,8 +83,9 @@ void add_vtx_scroll_target(u32 id, Vtx *vtx, u32 size, bool hasOffset) { if (!newArray) { newArray = malloc(newSize); if (!newArray) { return; } - memcpy(newArray, scroll->vertices, oldSize); - memset(newArray + scroll->size, 0, size * sizeof(Vtx*)); + if (scroll->vertices && oldSize > 0) { + memcpy(newArray, scroll->vertices, oldSize); + } free(scroll->vertices); } diff --git a/src/pc/lua/smlua_functions.c b/src/pc/lua/smlua_functions.c index 4054a3ebd..ba79220a8 100644 --- a/src/pc/lua/smlua_functions.c +++ b/src/pc/lua/smlua_functions.c @@ -815,6 +815,11 @@ int smlua_func_log_to_console(lua_State* L) { //////////////////// int smlua_func_add_scroll_target(lua_State* L) { + if (gLuaLoadingMod == NULL) { + LOG_LUA_LINE("add_scroll_target() can only be called on load."); + return 0; + } + // add_scroll_target used to require offset and size of the vertex buffer to be used if (!smlua_functions_valid_param_range(L, 2, 4)) { return 0; } int paramCount = lua_gettop(L); From 7ec6c77e1c6d91cfa11cb646073e1db20d9f7594 Mon Sep 17 00:00:00 2001 From: ThePlayerRolo Date: Thu, 19 Mar 2026 18:14:24 -0400 Subject: [PATCH 10/19] Implement smlua_audio_utils_allocate_sequence() (#891) --- autogen/lua_definitions/functions.lua | 6 ++++++ docs/lua/functions-6.md | 21 +++++++++++++++++++++ docs/lua/functions.md | 1 + src/pc/lua/smlua_functions_autogen.c | 16 ++++++++++++++++ src/pc/lua/utils/smlua_audio_utils.c | 10 ++++++++++ src/pc/lua/utils/smlua_audio_utils.h | 2 ++ 6 files changed, 56 insertions(+) diff --git a/autogen/lua_definitions/functions.lua b/autogen/lua_definitions/functions.lua index c716033b3..08b0b1587 100644 --- a/autogen/lua_definitions/functions.lua +++ b/autogen/lua_definitions/functions.lua @@ -10208,6 +10208,12 @@ function smlua_audio_utils_replace_sequence(sequenceId, bankId, defaultVolume, m -- ... end +--- @return integer +--- Allocates a new sequence ID +function smlua_audio_utils_allocate_sequence() + -- ... +end + --- @param filename string --- @return ModAudio --- Loads an `audio` stream by `filename` (with extension) diff --git a/docs/lua/functions-6.md b/docs/lua/functions-6.md index 41b220737..a639b7db9 100644 --- a/docs/lua/functions-6.md +++ b/docs/lua/functions-6.md @@ -5616,6 +5616,27 @@ Replaces the sequence corresponding to `sequenceId` with one called `m64Name`.m6
+## [smlua_audio_utils_allocate_sequence](#smlua_audio_utils_allocate_sequence) + +### Description +Allocates a new sequence ID + +### Lua Example +`local integerValue = smlua_audio_utils_allocate_sequence()` + +### Parameters +- None + +### Returns +- `integer` + +### C Prototype +`u8 smlua_audio_utils_allocate_sequence(void);` + +[:arrow_up_small:](#) + +
+ ## [audio_stream_load](#audio_stream_load) ### Description diff --git a/docs/lua/functions.md b/docs/lua/functions.md index ecce742d3..b463ac63e 100644 --- a/docs/lua/functions.md +++ b/docs/lua/functions.md @@ -1828,6 +1828,7 @@ - smlua_audio_utils.h - [smlua_audio_utils_reset_all](functions-6.md#smlua_audio_utils_reset_all) - [smlua_audio_utils_replace_sequence](functions-6.md#smlua_audio_utils_replace_sequence) + - [smlua_audio_utils_allocate_sequence](functions-6.md#smlua_audio_utils_allocate_sequence) - [audio_stream_load](functions-6.md#audio_stream_load) - [audio_stream_destroy](functions-6.md#audio_stream_destroy) - [audio_stream_play](functions-6.md#audio_stream_play) diff --git a/src/pc/lua/smlua_functions_autogen.c b/src/pc/lua/smlua_functions_autogen.c index 70d040309..54bb0672b 100644 --- a/src/pc/lua/smlua_functions_autogen.c +++ b/src/pc/lua/smlua_functions_autogen.c @@ -30424,6 +30424,21 @@ int smlua_func_smlua_audio_utils_replace_sequence(lua_State* L) { return 1; } +int smlua_func_smlua_audio_utils_allocate_sequence(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", "smlua_audio_utils_allocate_sequence", 0, top); + return 0; + } + + + lua_pushinteger(L, smlua_audio_utils_allocate_sequence()); + + return 1; +} + int smlua_func_audio_stream_load(lua_State* L) { if (L == NULL) { return 0; } @@ -38470,6 +38485,7 @@ void smlua_bind_functions_autogen(void) { // smlua_audio_utils.h smlua_bind_function(L, "smlua_audio_utils_reset_all", smlua_func_smlua_audio_utils_reset_all); smlua_bind_function(L, "smlua_audio_utils_replace_sequence", smlua_func_smlua_audio_utils_replace_sequence); + smlua_bind_function(L, "smlua_audio_utils_allocate_sequence", smlua_func_smlua_audio_utils_allocate_sequence); smlua_bind_function(L, "audio_stream_load", smlua_func_audio_stream_load); smlua_bind_function(L, "audio_stream_destroy", smlua_func_audio_stream_destroy); smlua_bind_function(L, "audio_stream_play", smlua_func_audio_stream_play); diff --git a/src/pc/lua/utils/smlua_audio_utils.c b/src/pc/lua/utils/smlua_audio_utils.c index 80aeca7fc..9b02de52e 100644 --- a/src/pc/lua/utils/smlua_audio_utils.c +++ b/src/pc/lua/utils/smlua_audio_utils.c @@ -173,6 +173,16 @@ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolu LOG_LUA_LINE("Could not find m64 at path: %s", m64path); } +u8 smlua_audio_utils_allocate_sequence(void) { + for (u8 seqId = SEQ_COUNT + 1; seqId < MAX_AUDIO_OVERRIDE; seqId++) { + if (!sAudioOverrides[seqId].enabled) { + return seqId; + } + } + LOG_ERROR("Cannot allocate more custom sequences."); + return MAX_AUDIO_OVERRIDE; +} + /////////////// // mod audio // /////////////// diff --git a/src/pc/lua/utils/smlua_audio_utils.h b/src/pc/lua/utils/smlua_audio_utils.h index deca8b0d0..93696c80d 100644 --- a/src/pc/lua/utils/smlua_audio_utils.h +++ b/src/pc/lua/utils/smlua_audio_utils.h @@ -8,6 +8,8 @@ void smlua_audio_utils_reset_all(void); bool smlua_audio_utils_override(u8 sequenceId, s32* bankId, void** seqData); /* |description|Replaces the sequence corresponding to `sequenceId` with one called `m64Name`.m64 with `bankId` and `defaultVolume`|descriptionEnd| */ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolume, const char* m64Name); +/* |description|Allocates a new sequence ID|descriptionEnd| */ +u8 smlua_audio_utils_allocate_sequence(void); //////////////// // mod sounds // From 4284e389a9f5353bd7d4b4383d82c4cd874ab4fd Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:53:31 +1000 Subject: [PATCH 11/19] fix ext sound bank crash when instId > 0x83 --- src/audio/seqplayer.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/audio/seqplayer.c b/src/audio/seqplayer.c index 433ca0b1e..ac9884e07 100644 --- a/src/audio/seqplayer.c +++ b/src/audio/seqplayer.c @@ -212,7 +212,7 @@ struct SequenceChannel *allocate_sequence_channel(void) { #endif } } - + LOG_ERROR("RAN OUT OF SEQUENCE CHANNELS FOR ALLOCATION!"); return &gSequenceChannelNone; } @@ -281,7 +281,7 @@ void sequence_player_init_channels_extended(struct SequencePlayer* seqPlayer, u6 if (!seqPlayer) { return; } u64 channelBits = channelBitsLower; LOG_DEBUG("Enabling channels (extended) with corresponding bits %llX", channelBits); - + for (u32 i = 0; i < CHANNELS_MAX; i++) { if (i == sizeof(u64) * 8) { channelBits = channelBitsUpper; @@ -350,9 +350,9 @@ void sequence_player_disable_channels_extended(struct SequencePlayer* seqPlayer, void sequence_player_disable_all_channels(struct SequencePlayer *seqPlayer) { if (!seqPlayer) { return; } - + MUTEX_LOCK(gAudioThread); - + eu_stubbed_printf_0("SUBTRACK DIM\n"); for (u32 i = 0; i < CHANNELS_MAX; i++) { struct SequenceChannel *seqChannel = seqPlayer->channels[i]; @@ -371,16 +371,16 @@ void sequence_player_disable_all_channels(struct SequencePlayer *seqPlayer) { seqPlayer->channels[i] = &gSequenceChannelNone; } } - + MUTEX_UNLOCK(gAudioThread); } void sequence_channel_enable(struct SequencePlayer *seqPlayer, u8 channelIndex, void *script) { if (!seqPlayer) { return; } if (channelIndex >= CHANNELS_MAX) { return; } - + MUTEX_LOCK(gAudioThread); - + struct SequenceChannel *seqChannel = seqPlayer->channels[channelIndex]; s32 i; if (IS_SEQUENCE_CHANNEL_VALID(seqChannel) == FALSE) { @@ -409,19 +409,19 @@ void sequence_channel_enable(struct SequencePlayer *seqPlayer, u8 channelIndex, seq_channel_layer_free(seqChannel, i); } } - + LOG_DEBUG("Enabled sequence channel %d with script entry of %p", channelIndex, script); } - + MUTEX_UNLOCK(gAudioThread); } void sequence_player_disable(struct SequencePlayer *seqPlayer) { if (!seqPlayer) { return; } MUTEX_LOCK(gAudioThread); - + LOG_DEBUG("Disabling sequence player %p", seqPlayer); - + sequence_player_disable_all_channels(seqPlayer); note_pool_clear(&seqPlayer->notePool); seqPlayer->finished = TRUE; @@ -1620,7 +1620,7 @@ u8 get_instrument(struct SequenceChannel *seqChannel, u8 instId, struct Instrume } void set_instrument(struct SequenceChannel *seqChannel, u8 instId) { - if (instId >= 0x80) { + if (instId >= 0x80 && instId <= 0x83) { seqChannel->instOrWave = instId; seqChannel->instrument = NULL; } else if (instId == 0x7f) { From 111b2e7ea5123e7c4c47bb1932e3cbf33191b644 Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:48:33 +1000 Subject: [PATCH 12/19] bump version [build] --- src/pc/network/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/network/version.h b/src/pc/network/version.h index 473e8fec5..9bda5050c 100644 --- a/src/pc/network/version.h +++ b/src/pc/network/version.h @@ -1,7 +1,7 @@ #ifndef VERSION_H #define VERSION_H -#define SM64COOPDX_VERSION "v1.4.1" +#define SM64COOPDX_VERSION "v1.4.2" // internal version #define VERSION_TEXT "v" From fb8fbd1136fa632ca4d25707f867066be4559549 Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:49:29 +1000 Subject: [PATCH 13/19] forgot to run autogen [build] --- autogen/lua_definitions/constants.lua | 2 +- src/pc/lua/smlua_constants_autogen.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autogen/lua_definitions/constants.lua b/autogen/lua_definitions/constants.lua index 5bbb7065a..1d7dfb2dc 100644 --- a/autogen/lua_definitions/constants.lua +++ b/autogen/lua_definitions/constants.lua @@ -11266,7 +11266,7 @@ COOP_OBJ_FLAG_NON_SYNC = (1 << 2) COOP_OBJ_FLAG_INITIALIZED = (1 << 3) --- @type string -SM64COOPDX_VERSION = "v1.4.1" +SM64COOPDX_VERSION = "v1.4.2" --- @type string VERSION_TEXT = "v" diff --git a/src/pc/lua/smlua_constants_autogen.c b/src/pc/lua/smlua_constants_autogen.c index 60737b707..de7da771c 100644 --- a/src/pc/lua/smlua_constants_autogen.c +++ b/src/pc/lua/smlua_constants_autogen.c @@ -4687,7 +4687,7 @@ char gSmluaConstants[] = "" "COOP_OBJ_FLAG_LUA=(1 << 1)\n" "COOP_OBJ_FLAG_NON_SYNC=(1 << 2)\n" "COOP_OBJ_FLAG_INITIALIZED=(1 << 3)\n" -"SM64COOPDX_VERSION='v1.4.1'\n" +"SM64COOPDX_VERSION='v1.4.2'\n" "VERSION_TEXT='v'\n" "VERSION_NUMBER=41\n" "MINOR_VERSION_NUMBER=1\n" From b5d021b231492c1f921ea8af2570a5d86550e2fc Mon Sep 17 00:00:00 2001 From: Isaac0-dev <62234577+Isaac0-dev@users.noreply.github.com> Date: Tue, 24 Mar 2026 10:10:11 +1000 Subject: [PATCH 14/19] fixed dynos animations on held objects [build] --- data/dynos_mgr_anim.cpp | 4 ++-- src/engine/graph_node.c | 2 +- src/game/mario_misc.c | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/data/dynos_mgr_anim.cpp b/data/dynos_mgr_anim.cpp index af7f319f4..21db5f690 100644 --- a/data/dynos_mgr_anim.cpp +++ b/data/dynos_mgr_anim.cpp @@ -8,7 +8,7 @@ extern "C" { #include "behavior_data.h" #include "pc/lua/smlua_hooks.h" -s8 geo_get_processing_mario_index(void); +s8 geo_get_processing_mario_index(struct Object *obj); } // @@ -77,7 +77,7 @@ void DynOS_Anim_Swap(void *aPtr) { // Animation index s32 _AnimIndex = -1; - s8 index = geo_get_processing_mario_index(); + s8 index = geo_get_processing_mario_index(_Object); if (index != -1) { _AnimIndex = RetrieveCurrentMarioAnimationIndex(index); diff --git a/src/engine/graph_node.c b/src/engine/graph_node.c index c9c0023f6..89d9126cb 100644 --- a/src/engine/graph_node.c +++ b/src/engine/graph_node.c @@ -508,7 +508,7 @@ struct GraphNodeBackground *init_graph_node_background(struct DynamicPool *pool, : (backgroundFunc && background >= BACKGROUND_CUSTOM); if (invalidBackground) { - LOG_ERROR("invalid background id"); + LOG_ERROR("invalid background id %d", background); background = BACKGROUND_HAUNTED; } diff --git a/src/game/mario_misc.c b/src/game/mario_misc.c index 7cb509211..435863e2f 100644 --- a/src/game/mario_misc.c +++ b/src/game/mario_misc.c @@ -334,13 +334,13 @@ static Gfx *make_gfx_mario_alpha(struct GraphNodeGenerated *node, s16 alpha) { } // Calculates if the processing geo is a mirror mario -static s8 geo_get_processing_mirror_mario_index() { - ptrdiff_t ptrDiff = (struct GraphNodeObject *) gCurGraphNodeProcessingObject - gMirrorMario; +static s8 geo_get_processing_mirror_mario_index(struct Object *obj) { + ptrdiff_t ptrDiff = (struct GraphNodeObject *) obj - gMirrorMario; return (ptrDiff >= 0 && ptrDiff < MAX_PLAYERS) ? ptrDiff : -1; } static u8 geo_get_processing_object_index(void) { - s8 index = geo_get_processing_mirror_mario_index(); + s8 index = geo_get_processing_mirror_mario_index(gCurGraphNodeProcessingObject); if (index != -1) { return index; } @@ -351,19 +351,19 @@ static u8 geo_get_processing_object_index(void) { return (index >= MAX_PLAYERS) ? 0 : index; } -s8 geo_get_processing_mario_index(void) { - if (gCurGraphNodeProcessingObject == NULL) { return -1; } +s8 geo_get_processing_mario_index(struct Object *obj) { + if (obj == NULL) { return -1; } - s8 index = geo_get_processing_mirror_mario_index(); + s8 index = geo_get_processing_mirror_mario_index(obj); if (index != -1) { return index; } - if (gCurGraphNodeProcessingObject->behavior != bhvMario) { + if (obj->behavior != bhvMario) { return -1; } - index = gCurGraphNodeProcessingObject->oBehParams - 1; + index = obj->oBehParams - 1; return (index >= MAX_PLAYERS) ? -1 : index; } From 43c697edf7e9c6b984f58e1c7bc23ebb154675ed Mon Sep 17 00:00:00 2001 From: EmeraldLockdown <86802223+EmeraldLoc@users.noreply.github.com> Date: Wed, 25 Mar 2026 21:52:21 -0500 Subject: [PATCH 15/19] Fix issue with mouse buttons being applied to pad (#1156) --- src/pc/controller/controller_sdl2.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pc/controller/controller_sdl2.c b/src/pc/controller/controller_sdl2.c index 95dafbed1..251d8a85f 100644 --- a/src/pc/controller/controller_sdl2.c +++ b/src/pc/controller/controller_sdl2.c @@ -196,6 +196,7 @@ static void controller_sdl_read(OSContPad *pad) { if (mouse & SDL_BUTTON(mouse_binds[i][0])) buttons_down |= mouse_binds[i][1]; } + pad->button |= buttons_down; // remember buttons that changed from 0 to 1 last_mouse = (mouse_prev ^ mouse) & mouse; From b27fa1dd7a6f9775e927ab7b45872a9a4b65fad4 Mon Sep 17 00:00:00 2001 From: Gibson Pilconis Date: Thu, 26 Mar 2026 22:54:05 -0400 Subject: [PATCH 16/19] Fix rumble and add PS4/PS5 controller rumble. (#1007) Previously `thread6_rumble_loop()` was only being called once in `thread5_game_loop()`. This commit removes that call and adds one to `game_loop_one_iteration()`, creating the desired outcome of `thread6_runnable_loop()` running once per frame. Additionally, PS4 and PS5 rumble support is added. PS4 and PS5 rumble requires a feature called 'extended reporting', which can be enabled using two SDL hints. Since extended reported, once enabled, breaks compatibility with DirectInput until the controller is restarted, an option is also added to the configuration menu to toggle this functionality. --- lang/Czech.ini | 1 + lang/Dutch.ini | 1 + lang/English.ini | 1 + lang/French.ini | 1 + lang/German.ini | 1 + lang/Italian.ini | 1 + lang/Japanese.ini | 1 + lang/Polish.ini | 1 + lang/Portuguese.ini | 1 + lang/Russian.ini | 1 + lang/Spanish.ini | 1 + src/game/game_init.c | 3 +-- src/pc/configfile.c | 2 ++ src/pc/configfile.h | 1 + src/pc/controller/controller_sdl2.c | 15 +++++++++++++++ src/pc/djui/djui_panel_controls.c | 2 ++ 16 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lang/Czech.ini b/lang/Czech.ini index ddf7ae547..81a2e89a3 100644 --- a/lang/Czech.ini +++ b/lang/Czech.ini @@ -100,6 +100,7 @@ N64_BINDS = "N64 Ovládání" EXTRA_BINDS = "Extra Ovládání" BACKGROUND_GAMEPAD = "Ovladač v pozadí" DISABLE_GAMEPADS = "Zakažte gamepady" +EXTENDED_REPORTS = "Rozšířené zprávy" GAMEPAD = "Použít ovladač" DEADZONE = "Deadzone" RUMBLE_STRENGTH = "Síla vibrace" diff --git a/lang/Dutch.ini b/lang/Dutch.ini index a06530f8b..c4e32f77c 100644 --- a/lang/Dutch.ini +++ b/lang/Dutch.ini @@ -100,6 +100,7 @@ N64_BINDS = "N64 Toetsen" EXTRA_BINDS = "Extra Toetsen" BACKGROUND_GAMEPAD = "Achtergrond Gamepad" DISABLE_GAMEPADS = "Gamepads uitschakelen" +EXTENDED_REPORTS = "Uitgebreide rapporten" GAMEPAD = "Gamepad" DEADZONE = "Doode-zone" RUMBLE_STRENGTH = "Rommel Kracht" diff --git a/lang/English.ini b/lang/English.ini index 14ec1d462..0560d9ac2 100644 --- a/lang/English.ini +++ b/lang/English.ini @@ -100,6 +100,7 @@ N64_BINDS = "N64 Binds" EXTRA_BINDS = "Extra Binds" BACKGROUND_GAMEPAD = "Background Gamepad" DISABLE_GAMEPADS = "Disable Gamepads" +EXTENDED_REPORTS = "Extended Reports" GAMEPAD = "Gamepad" DEADZONE = "Deadzone" RUMBLE_STRENGTH = "Rumble Strength" diff --git a/lang/French.ini b/lang/French.ini index b0dfe3be2..645ed0c2a 100644 --- a/lang/French.ini +++ b/lang/French.ini @@ -100,6 +100,7 @@ N64_BINDS = "Touches N64" EXTRA_BINDS = "Touches Supplémentaires" BACKGROUND_GAMEPAD = "Manette en arrière plan" DISABLE_GAMEPADS = "Désactiver les manettes de jeu" +EXTENDED_REPORTS = "Rapports détaillés" GAMEPAD = "Manette" DEADZONE = "Zone Morte" RUMBLE_STRENGTH = "Vibrations" diff --git a/lang/German.ini b/lang/German.ini index e44e5c718..1fb8031d7 100644 --- a/lang/German.ini +++ b/lang/German.ini @@ -99,6 +99,7 @@ CONTROLS = "STEUERUNG" N64_BINDS = "N64-Einstellungen" EXTRA_BINDS = "Zusätzliche Einstellungen" BACKGROUND_GAMEPAD = "Hintergrund-Gamepad" +EXTENDED_REPORTS = "Erweiterte Berichte" DISABLE_GAMEPADS = "Gamepads deaktivieren" GAMEPAD = "Gamepad" DEADZONE = "Tote Zone" diff --git a/lang/Italian.ini b/lang/Italian.ini index e0827a2f6..dfe13d3bb 100644 --- a/lang/Italian.ini +++ b/lang/Italian.ini @@ -98,6 +98,7 @@ CONTROLS = "CONTROLLI" N64_BINDS = "Comandi N64" EXTRA_BINDS = "Comandi Extra" BACKGROUND_GAMEPAD = "Attivi in Background" +EXTENDED_REPORTS = "Rapporti estesi" DISABLE_GAMEPADS = "Disabilita i Gamepad" GAMEPAD = "Controller" DEADZONE = "Zona Morta" diff --git a/lang/Japanese.ini b/lang/Japanese.ini index a15f74b3a..05b20a216 100644 --- a/lang/Japanese.ini +++ b/lang/Japanese.ini @@ -99,6 +99,7 @@ CONTROLS = "CONTROLS" N64_BINDS = "ニンテンドウ64のボタン割り当て" EXTRA_BINDS = "追加のボタン割り当て" BACKGROUND_GAMEPAD = "バックグラウンドでのコントローラー認識" +EXTENDED_REPORTS = "拡張レポート" DISABLE_GAMEPADS = "コントローラーを無効化" GAMEPAD = "コントローラー" DEADZONE = "デッドゾーン" diff --git a/lang/Polish.ini b/lang/Polish.ini index 9781c549e..cb983027d 100644 --- a/lang/Polish.ini +++ b/lang/Polish.ini @@ -99,6 +99,7 @@ CONTROLS = "STEROWANIE" N64_BINDS = "Przypisania N64" EXTRA_BINDS = "Dodatkowe Przypisania" BACKGROUND_GAMEPAD = "Gamepad w Tle" +EXTENDED_REPORTS = "Rozszerzone raporty" DISABLE_GAMEPADS = "Wyłącz Gamepady" GAMEPAD = "Gamepad" DEADZONE = "Martwa Strefa" diff --git a/lang/Portuguese.ini b/lang/Portuguese.ini index e935499a6..d3b9e0c6b 100644 --- a/lang/Portuguese.ini +++ b/lang/Portuguese.ini @@ -99,6 +99,7 @@ CONTROLS = "CONTROLES" N64_BINDS = "N64" EXTRA_BINDS = "Outros" BACKGROUND_GAMEPAD = "Controle de fundo" +EXTENDED_REPORTS = "Relatórios detalhados" DISABLE_GAMEPADS = "Desativar controles" GAMEPAD = "Controle" DEADZONE = "Zona morta" diff --git a/lang/Russian.ini b/lang/Russian.ini index 52861a4cf..85d39ae37 100644 --- a/lang/Russian.ini +++ b/lang/Russian.ini @@ -98,6 +98,7 @@ CONTROLS = "CONTROLS" N64_BINDS = "Кнопки N64" EXTRA_BINDS = "Дополнительные кнопки" BACKGROUND_GAMEPAD = "Фоновый ввод" +EXTENDED_REPORTS = "Расширенные отчеты" DISABLE_GAMEPADS = "Отключить геймпады" GAMEPAD = "Геймпад" DEADZONE = "Mёртвая зона" diff --git a/lang/Spanish.ini b/lang/Spanish.ini index f083c8ba2..50660a637 100644 --- a/lang/Spanish.ini +++ b/lang/Spanish.ini @@ -100,6 +100,7 @@ N64_BINDS = "Botones de N64" EXTRA_BINDS = "Botones Adicionales" BACKGROUND_GAMEPAD = "Mando en segundo plano" DISABLE_GAMEPADS = "Desactivar mandos" +EXTENDED_REPORTS = "Informes ampliados" GAMEPAD = "Mando" DEADZONE = "Zona muerta" RUMBLE_STRENGTH = "Intensidad de vibración" diff --git a/src/game/game_init.c b/src/game/game_init.c index 2e13c7e57..058cc9697 100644 --- a/src/game/game_init.c +++ b/src/game/game_init.c @@ -594,8 +594,6 @@ void thread5_game_loop(UNUSED void *arg) { play_music(SEQ_PLAYER_SFX, SEQUENCE_ARGS(0, SEQ_SOUND_PLAYER), 0); set_sound_mode(save_file_get_sound_mode()); - thread6_rumble_loop(NULL); - gGlobalTimer++; } @@ -609,6 +607,7 @@ void game_loop_one_iteration(void) { osContStartReadData(&gSIEventMesgQueue); } + thread6_rumble_loop(NULL); audio_game_loop_tick(); config_gfx_pool(); read_controller_inputs(); diff --git a/src/pc/configfile.c b/src/pc/configfile.c index f2267a7db..4e52b50b6 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -127,6 +127,7 @@ unsigned int configStickDeadzone = 16; unsigned int configRumbleStrength = 50; unsigned int configGamepadNumber = 0; bool configBackgroundGamepad = true; +bool configExtendedReports = false; bool configDisableGamepads = false; bool configUseStandardKeyBindingsChat = false; bool configSmoothScrolling = false; @@ -270,6 +271,7 @@ static const struct ConfigOption options[] = { {.name = "rumble_strength", .type = CONFIG_TYPE_UINT, .uintValue = &configRumbleStrength}, {.name = "gamepad_number", .type = CONFIG_TYPE_UINT, .uintValue = &configGamepadNumber}, {.name = "background_gamepad", .type = CONFIG_TYPE_UINT, .boolValue = &configBackgroundGamepad}, + {.name = "extended_reports", .type = CONFIG_TYPE_BOOL, .boolValue = &configExtendedReports}, #ifndef HANDHELD {.name = "disable_gamepads", .type = CONFIG_TYPE_BOOL, .boolValue = &configDisableGamepads}, #endif diff --git a/src/pc/configfile.h b/src/pc/configfile.h index d537970e4..11cdbd5bc 100644 --- a/src/pc/configfile.h +++ b/src/pc/configfile.h @@ -93,6 +93,7 @@ extern unsigned int configStickDeadzone; extern unsigned int configRumbleStrength; extern unsigned int configGamepadNumber; extern bool configBackgroundGamepad; +extern bool configExtendedReports; extern bool configDisableGamepads; extern bool configUseStandardKeyBindingsChat; extern bool configSmoothScrolling; diff --git a/src/pc/controller/controller_sdl2.c b/src/pc/controller/controller_sdl2.c index 251d8a85f..6d4fc30ed 100644 --- a/src/pc/controller/controller_sdl2.c +++ b/src/pc/controller/controller_sdl2.c @@ -40,6 +40,7 @@ static SDL_GameController *sdl_cntrl = NULL; static SDL_Joystick *sdl_joystick = NULL; static SDL_Haptic *sdl_haptic = NULL; +static bool sExtendedReports = false; static bool sBackgroundGamepad = false; static u32 num_joy_binds = 0; @@ -102,6 +103,13 @@ static void controller_sdl_bind(void) { } static void controller_sdl_init(void) { + // Allows extended reports on PS4 and PS5 controllers + if (configExtendedReports) { + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + } + sExtendedReports = configExtendedReports; + // Allows game to be controlled by gamepad when not in focus if (configBackgroundGamepad) { SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); @@ -200,6 +208,13 @@ static void controller_sdl_read(OSContPad *pad) { // remember buttons that changed from 0 to 1 last_mouse = (mouse_prev ^ mouse) & mouse; + if (configExtendedReports != sExtendedReports) { + sExtendedReports = configExtendedReports; + char* hint = sExtendedReports ? "1" : "0"; + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, hint); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, hint); + } + if (configBackgroundGamepad != sBackgroundGamepad) { sBackgroundGamepad = configBackgroundGamepad; SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, sBackgroundGamepad ? "1" : "0"); diff --git a/src/pc/djui/djui_panel_controls.c b/src/pc/djui/djui_panel_controls.c index 8088580a9..e8b39396d 100644 --- a/src/pc/djui/djui_panel_controls.c +++ b/src/pc/djui/djui_panel_controls.c @@ -43,6 +43,8 @@ void djui_panel_controls_create(struct DjuiBase* caller) { djui_checkbox_create(body, DLANG(MISC, USE_STANDARD_KEY_BINDINGS_CHAT), &configUseStandardKeyBindingsChat, NULL); #ifdef HAVE_SDL2 + djui_checkbox_create(body, DLANG(CONTROLS, EXTENDED_REPORTS), &configExtendedReports, NULL); + int numJoys = SDL_NumJoysticks(); if (numJoys == 0) { numJoys = 1; } From dfb3b2523d7abd1e2a2e8339cfad667e5f71a059 Mon Sep 17 00:00:00 2001 From: Blockyyy <88585273+Blockyyy@users.noreply.github.com> Date: Fri, 27 Mar 2026 04:32:44 +0100 Subject: [PATCH 17/19] Fix lives (#1119) - Fix lives going below 0 right before a game over (also fixes a crash where if you exited with -1 lives and went into a subarea you would crash) - Restore how lives get removed upon death exit (like in vanilla) - Prevent bubbling from happening if you don't have enough lives (this fixes the annoying bug where you would get put back in a bubble and get popped immediately after over and over again) --- src/game/interaction.c | 2 +- src/game/level_update.c | 6 ++---- src/game/mario.c | 2 +- src/game/mario_actions_airborne.c | 2 +- src/game/mario_actions_cutscene.c | 21 +++++++++++++++------ src/game/mario_actions_submerged.c | 6 +++--- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/game/interaction.c b/src/game/interaction.c index 4304a23ae..599d6c65a 100644 --- a/src/game/interaction.c +++ b/src/game/interaction.c @@ -2445,7 +2445,7 @@ void check_death_barrier(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { switch (gCurrCourseNum) { case COURSE_COTMC: // (20) Cavern of the Metal Cap case COURSE_TOTWC: // (21) Tower of the Wing Cap diff --git a/src/game/level_update.c b/src/game/level_update.c index 1ad390601..ff6ae25b1 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -878,8 +878,7 @@ void verify_warp(struct MarioState *m, bool killMario) { return; } - m->numLives--; - if (m->numLives < 0) { + if (m->numLives <= 0) { sDelayedWarpOp = WARP_OP_GAME_OVER; } else { sSourceWarpNodeId = WARP_NODE_DEATH; @@ -934,8 +933,7 @@ s16 level_trigger_warp(struct MarioState *m, s32 warpOp) { break; case WARP_OP_DEATH: - m->numLives--; - if (m->numLives <= -1) { + if (m->numLives <= 0) { sDelayedWarpOp = WARP_OP_GAME_OVER; } sDelayedWarpTimer = 48; diff --git a/src/game/mario.c b/src/game/mario.c index 897bbc3fe..d929544f6 100644 --- a/src/game/mario.c +++ b/src/game/mario.c @@ -447,7 +447,7 @@ void mario_set_bubbled(struct MarioState* m) { gLocalBubbleCounter = 20; drop_and_set_mario_action(m, ACT_BUBBLED, 0); - if (m->numLives > -1) { + if (m->numLives > 0) { m->numLives--; } m->healCounter = 0; diff --git a/src/game/mario_actions_airborne.c b/src/game/mario_actions_airborne.c index 00b338447..6112358eb 100644 --- a/src/game/mario_actions_airborne.c +++ b/src/game/mario_actions_airborne.c @@ -1734,7 +1734,7 @@ s32 act_lava_boost(struct MarioState *m) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { m->health = 0xFF; mario_set_bubbled(m); } else { diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index 9162cd84a..293079dc4 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -849,7 +849,7 @@ s32 common_death_handler(struct MarioState *m, s32 animation, s32 frameToDeathWa smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return animFrame; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -922,8 +922,7 @@ s32 act_quicksand_death(struct MarioState *m) { bool allowDeath = true; smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -947,7 +946,7 @@ s32 act_eaten_by_bubba(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { m->health = 0xFF; mario_set_bubbled(m); } else { @@ -1437,6 +1436,12 @@ s32 act_exit_land_save_dialog(struct MarioState *m) { return FALSE; } +static void lose_life_after_death_exit(struct MarioState *m) { + if (sDelayedWarpArg != WARP_ARG_EXIT_COURSE) { + m->numLives--; + } +} + s32 act_death_exit(struct MarioState *m) { if (!m) { return 0; } if (15 < m->actionTimer++ @@ -1447,6 +1452,7 @@ s32 act_death_exit(struct MarioState *m) { play_character_sound(m, CHAR_SOUND_OOOF2); #endif queue_rumble_data_mario(m, 5, 80); + lose_life_after_death_exit(m); // restore 7.75 units of health m->healCounter = 31; } @@ -1463,6 +1469,7 @@ s32 act_unused_death_exit(struct MarioState *m) { #else play_character_sound(m, CHAR_SOUND_OOOF2); #endif + lose_life_after_death_exit(m); // restore 7.75 units of health m->healCounter = 31; } @@ -1479,6 +1486,7 @@ s32 act_falling_death_exit(struct MarioState *m) { #else play_character_sound(m, CHAR_SOUND_OOOF2); #endif + lose_life_after_death_exit(m); queue_rumble_data_mario(m, 5, 80); // restore 7.75 units of health m->healCounter = 31; @@ -1526,6 +1534,7 @@ s32 act_special_death_exit(struct MarioState *m) { if (launch_mario_until_land(m, ACT_HARD_BACKWARD_GROUND_KB, CHAR_ANIM_BACKWARD_AIR_KB, -24.0f)) { queue_rumble_data_mario(m, 5, 80); + lose_life_after_death_exit(m); m->healCounter = 31; } // show Mario @@ -1829,7 +1838,7 @@ s32 act_squished(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1880,7 +1889,7 @@ s32 act_squished(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { // 0 units of health diff --git a/src/game/mario_actions_submerged.c b/src/game/mario_actions_submerged.c index 61f3a0e15..e4195f64c 100644 --- a/src/game/mario_actions_submerged.c +++ b/src/game/mario_actions_submerged.c @@ -1003,7 +1003,7 @@ static s32 act_drowning(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1038,7 +1038,7 @@ static s32 act_water_death(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); @@ -1164,7 +1164,7 @@ static s32 act_caught_in_whirlpool(struct MarioState *m) { smlua_call_event_hooks(HOOK_ON_DEATH, m, &allowDeath); if (!allowDeath) { reset_rumble_timers(m); return FALSE; } - if (mario_can_bubble(m)) { + if ((mario_can_bubble(m) && m->numLives > 0)) { mario_set_bubbled(m); } else { level_trigger_warp(m, WARP_OP_DEATH); From 5288ac5b2d5455bdf0828bf552829301fb74f5e5 Mon Sep 17 00:00:00 2001 From: PeachyPeachSM64 <72323920+PeachyPeachSM64@users.noreply.github.com> Date: Sun, 29 Mar 2026 19:24:28 +0200 Subject: [PATCH 18/19] fix text interpolation with legacy font --- src/pc/djui/djui_hud_utils.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/pc/djui/djui_hud_utils.c b/src/pc/djui/djui_hud_utils.c index 8ad0a5b15..d36938e79 100644 --- a/src/pc/djui/djui_hud_utils.c +++ b/src/pc/djui/djui_hud_utils.c @@ -533,8 +533,6 @@ static void djui_hud_print_text_internal(const char* message, f32 x, f32 y, f32 if (message == NULL) { return; } gDjuiHudUtilsZ += 0.001f; - if (djui_hud_text_font_is_legacy()) { scale *= 0.5f; } - const struct DjuiFont* font = djui_hud_get_text_font(); f32 fontScale = font->defaultFontScale * scale; @@ -654,13 +652,22 @@ static void djui_hud_print_text_internal(const char* message, f32 x, f32 y, f32 } void djui_hud_print_text(const char* message, f32 x, f32 y, f32 scale) { + if (message == NULL) { return; } + + if (djui_hud_text_font_is_legacy()) { + scale *= 0.5f; + } + djui_hud_print_text_internal(message, x, y, scale, NULL); } void djui_hud_print_text_interpolated(const char* message, f32 prevX, f32 prevY, f32 prevScale, f32 x, f32 y, f32 scale) { if (message == NULL) { return; } - if (djui_hud_text_font_is_legacy()) { prevScale *= 0.5f; } + if (djui_hud_text_font_is_legacy()) { + scale *= 0.5f; + prevScale *= 0.5f; + } struct InterpHud *interp = djui_hud_create_interp(); if (interp) { From 7604ef9297f9cc789ce04a4cf6bc08a991b186bd Mon Sep 17 00:00:00 2001 From: PeachyPeachSM64 <72323920+PeachyPeachSM64@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:05:29 +0200 Subject: [PATCH 19/19] check surface type and special object preset type in dynos collision validation --- data/dynos_bin_col.cpp | 132 ++++++++++++++++++++++++++++++----------- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/data/dynos_bin_col.cpp b/data/dynos_bin_col.cpp index cd5a58e50..331cbb130 100644 --- a/data/dynos_bin_col.cpp +++ b/data/dynos_bin_col.cpp @@ -3,7 +3,9 @@ extern "C" { #include "include/surface_terrains.h" #include "include/level_misc_macros.h" +#include "include/special_presets.h" #include "include/special_preset_names.h" +#include "src/engine/surface_load.h" } // Free data pointers, but keep nodes and tokens intact @@ -34,12 +36,32 @@ struct CollisionValidationData { u32 vtxCount; u32 triAlloc; u32 triCount; + s16 surfaceType; u32 specialAlloc; u32 specialCount; u32 waterBoxAlloc; u32 waterBoxCount; }; +static u8 GetSpecialObjectType(u8 preset) { + for (s32 i = 0; i < ARRAY_COUNT(SpecialObjectPresets); ++i) { + if (SpecialObjectPresets[i].preset_id == preset) { + return SpecialObjectPresets[i].type; + } + } + return SPTYPE_UNKNOWN; +} + +static const char *GetCorrectSpecialObjectCommand(u8 presetType) { + switch (presetType) { + case SPTYPE_NO_YROT_OR_PARAMS: return "SPECIAL_OBJECT"; + case SPTYPE_YROT_NO_PARAMS: return "SPECIAL_OBJECT_WITH_YAW"; + case SPTYPE_PARAMS_AND_YROT: return "SPECIAL_OBJECT_WITH_YAW_AND_PARAM"; + case SPTYPE_DEF_PARAM_AND_YROT: return "SPECIAL_OBJECT_WITH_YAW"; + default: return ""; + } +} + static void ValidateColSectionChange(GfxData* aGfxData, struct CollisionValidationData& aColValData, u8 section) { if (aColValData.section == COL_SECTION_END) { PrintDataError("Found new col section after COL_END"); @@ -67,51 +89,70 @@ static void ValidateColInit(GfxData* aGfxData, struct CollisionValidationData& a ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_VTX); } -static void ValidateColVertexInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { +static void ValidateColVertexInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 vertexCount) { if (strcmp(aColValData.lastSymbol, "COL_INIT") != 0) { PrintDataError("COL_VERTEX_INIT found outside of vertex section"); } - if (arg0 < 0) { - PrintDataError("COL_VERTEX_INIT with a negative count: %d", arg0); + if (vertexCount < 0) { + PrintDataError("COL_VERTEX_INIT with a negative count: %d", vertexCount); } - aColValData.vtxAlloc = arg0; + aColValData.vtxAlloc = vertexCount; aColValData.vtxCount = 0; } -static void ValidateColVertex(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2) { +static void ValidateColVertex(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 x, s16 y, s16 z) { if (aColValData.section != COL_SECTION_VTX) { PrintDataError("COL_VERTEX found outside of vertex section"); } aColValData.vtxCount++; } -static void ValidateColTriInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1) { - if (arg1 < 0) { - PrintDataError("COL_TRI_INIT with a negative count: %d", arg1); +static void ValidateColTriInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 surfaceType, s16 triangleCount) { + if (triangleCount < 0) { + PrintDataError("COL_TRI_INIT with a negative count: %d", triangleCount); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_TRI); - aColValData.triAlloc = arg1; + aColValData.triAlloc = triangleCount; aColValData.triCount = 0; + aColValData.surfaceType = surfaceType; } -static void ValidateColTri(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2) { +static void ValidateColTri(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 vertex0, s16 vertex1, s16 vertex2) { if (aColValData.section != COL_SECTION_TRI) { PrintDataError("COL_TRI found outside of triangle section"); } - if (arg0 < 0 || arg0 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for first param: %d", arg0); + if (surface_has_force(aColValData.surfaceType)) { + PrintDataError("COL_TRI cannot be used by surface types with a force parameter: %d (use COL_TRI_SPECIAL instead)", aColValData.surfaceType); } - if (arg1 < 0 || arg1 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for second param: %d", arg1); + if (vertex0 < 0 || vertex0 > aColValData.vtxCount) { + PrintDataError("COL_TRI used vertex outside of known range for first param: %d", vertex0); } - if (arg2 < 0 || arg2 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for third param: %d", arg2); + if (vertex1 < 0 || vertex1 > aColValData.vtxCount) { + PrintDataError("COL_TRI used vertex outside of known range for second param: %d", vertex1); + } + if (vertex2 < 0 || vertex2 > aColValData.vtxCount) { + PrintDataError("COL_TRI used vertex outside of known range for third param: %d", vertex2); } aColValData.triCount++; } -static void ValidateColTriSpecial(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3) { - ValidateColTri(aGfxData, aColValData, arg0, arg1, arg2); +static void ValidateColTriSpecial(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 vertex0, s16 vertex1, s16 vertex2, s16 force) { + if (aColValData.section != COL_SECTION_TRI) { + PrintDataError("COL_TRI_SPECIAL found outside of triangle section"); + } + if (!surface_has_force(aColValData.surfaceType)) { + PrintDataError("COL_TRI_SPECIAL cannot be used by surface types with no force parameter: %d (use COL_TRI instead)", aColValData.surfaceType); + } + if (vertex0 < 0 || vertex0 > aColValData.vtxCount) { + PrintDataError("COL_TRI_SPECIAL used vertex outside of known range for first param: %d", vertex0); + } + if (vertex1 < 0 || vertex1 > aColValData.vtxCount) { + PrintDataError("COL_TRI_SPECIAL used vertex outside of known range for second param: %d", vertex1); + } + if (vertex2 < 0 || vertex2 > aColValData.vtxCount) { + PrintDataError("COL_TRI_SPECIAL used vertex outside of known range for third param: %d", vertex2); + } + aColValData.triCount++; } static void ValidateColStop(GfxData* aGfxData, struct CollisionValidationData& aColValData) { @@ -122,49 +163,70 @@ static void ValidateColEnd(GfxData* aGfxData, struct CollisionValidationData& aC ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_END); } -static void ValidateColSpecialInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { - if (arg0 < 0) { - PrintDataError("COL_SPECIAL_INIT with a negative count: %d", arg0); +static void ValidateColSpecialInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 specialCount) { + if (specialCount < 0) { + PrintDataError("COL_SPECIAL_INIT with a negative count: %d", specialCount); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_SPECIAL); - aColValData.specialAlloc = arg0; + aColValData.specialAlloc = specialCount; aColValData.specialCount = 0; } -static void ValidateColWaterBoxInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { - if (arg0 < 0) { - PrintDataError("COL_WATER_BOX_INIT with a negative count: %d", arg0); +static void ValidateColWaterBoxInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 waterBoxCount) { + if (waterBoxCount < 0) { + PrintDataError("COL_WATER_BOX_INIT with a negative count: %d", waterBoxCount); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_WATER_BOX); - aColValData.waterBoxAlloc = arg0; + aColValData.waterBoxAlloc = waterBoxCount; aColValData.waterBoxCount = 0; } -static void ValidateColWaterBox(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4, s16 arg5) { +static void ValidateColWaterBox(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 id, s16 x1, s16 z1, s16 x2, s16 z2, s16 y) { if (aColValData.section != COL_SECTION_WATER_BOX) { PrintDataError("COL_WATER_BOX found outside of water box section"); } aColValData.waterBoxCount++; } -static void ValidateColSpecialObject(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3) { +static void ValidateColSpecialObject(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 preset, s16 posX, s16 posY, s16 posZ) { if (aColValData.section != COL_SECTION_SPECIAL) { PrintDataError("SPECIAL_OBJECT found outside of special section"); } - aColValData.specialCount++; -} - -static void ValidateColSpecialObjectWithYaw(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4) { - if (aColValData.section != COL_SECTION_SPECIAL) { - PrintDataError("SPECIAL_OBJECT_WITH_YAW found outside of special section"); + u8 presetType = GetSpecialObjectType(preset); + if (presetType == SPTYPE_UNKNOWN) { + PrintDataError("SPECIAL_OBJECT has invalid preset: %d", preset); + } + if (presetType != SPTYPE_NO_YROT_OR_PARAMS) { + PrintDataError("SPECIAL_OBJECT cannot be used with preset: %d (use %s instead)", preset, GetCorrectSpecialObjectCommand(presetType)); } aColValData.specialCount++; } -static void ValidateColSpecialObjectWithYawAndParam(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4, s16 arg5) { +static void ValidateColSpecialObjectWithYaw(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 preset, s16 posX, s16 posY, s16 posZ, s16 yaw) { + if (aColValData.section != COL_SECTION_SPECIAL) { + PrintDataError("SPECIAL_OBJECT_WITH_YAW found outside of special section"); + } + u8 presetType = GetSpecialObjectType(preset); + if (presetType == SPTYPE_UNKNOWN) { + PrintDataError("SPECIAL_OBJECT_WITH_YAW has invalid preset: %d", preset); + } + if (presetType != SPTYPE_YROT_NO_PARAMS && presetType != SPTYPE_DEF_PARAM_AND_YROT) { + PrintDataError("SPECIAL_OBJECT_WITH_YAW cannot be used with preset: %d (use %s instead)", preset, GetCorrectSpecialObjectCommand(presetType)); + } + aColValData.specialCount++; +} + +static void ValidateColSpecialObjectWithYawAndParam(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 preset, s16 posX, s16 posY, s16 posZ, s16 yaw, s16 param) { if (aColValData.section != COL_SECTION_SPECIAL) { PrintDataError("SPECIAL_OBJECT_WITH_YAW_AND_PARAM found outside of special section"); } + u8 presetType = GetSpecialObjectType(preset); + if (presetType == SPTYPE_UNKNOWN) { + PrintDataError("SPECIAL_OBJECT_WITH_YAW_AND_PARAM has invalid preset: %d", preset); + } + if (presetType != SPTYPE_PARAMS_AND_YROT) { + PrintDataError("SPECIAL_OBJECT_WITH_YAW_AND_PARAM cannot be used with preset: %d (use %s instead)", preset, GetCorrectSpecialObjectCommand(presetType)); + } aColValData.specialCount++; }