Repository navigation
Complete mouse support for title, Journal, save prompts, and item pickers #194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
dc2b46b
21e3976
61a2367
d2f10ed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| #include "menu_event_pointer.hpp" | ||
|
|
||
| #include "menu_pointer.hpp" | ||
| #include "menu_save.hpp" | ||
| #include "platform/input.hpp" | ||
|
|
||
| /** | ||
| * Runs the original item picker selection and drawing logic. | ||
| */ | ||
| int PortEventItemSelectLoop(int *result); | ||
|
|
||
| /** The item picker's selection and transition state. */ | ||
| extern MINI_MENU_INFO MiniMenu; | ||
| /** The item board's logical screen origin. */ | ||
| extern float EventBoardPos[2]; | ||
| /** The animated vertical origin of the first item row. */ | ||
| extern int EventItemMoveY; | ||
| /** The item picker's animated hand position. */ | ||
| extern float MiniCur[2]; | ||
| /** Whether the item picker textures are ready. */ | ||
| extern int MiniEventTexReadFlag; | ||
|
|
||
| namespace { | ||
|
|
||
| /** The pointer owned by the door/object item picker. */ | ||
| MenuPointer event_pointer; | ||
| /** Whether the item picker remained active after its last update. */ | ||
| bool event_active = false; | ||
|
|
||
| /** | ||
| * Finds an occupied item cell within the animated board's visible clip. | ||
| */ | ||
| int ItemHit(float x, float y) { | ||
| const float left = EventBoardPos[0] + 28.0f; | ||
| const float top = EventBoardPos[1] + 56.0f; | ||
| if (x < left || x >= left + 210.0f || y < top || y >= top + 80.0f) { | ||
| return -1; | ||
| } | ||
| for (int index = 0; index < MiniMenu.event_item_num; ++index) { | ||
| const float cell_left = left + (index % 5) * 42.0f; | ||
| const float cell_top = static_cast<float>(EventItemMoveY + (index / 5) * 40); | ||
| if (x >= cell_left && x < cell_left + 42.0f && y >= cell_top && y < cell_top + 40.0f) { | ||
| return index; | ||
| } | ||
| } | ||
| return -1; | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| /** | ||
| * Tracks the item picker's lifetime while preserving its original item handling. | ||
| */ | ||
| int EventItemSelectLoop(int *result) { | ||
| const int done = PortEventItemSelectLoop(result); | ||
| event_active = done == 0; | ||
| return done; | ||
| } | ||
|
|
||
| bool EventItemPointerUpdate() { | ||
| const bool active = event_active; | ||
| event_active = false; | ||
| if (!active) { | ||
| return !event_pointer.Close(); | ||
| } | ||
| if (!event_pointer.IsOpen()) { | ||
| event_pointer.Open(); | ||
| } | ||
| const MenuPointerTake take = event_pointer.Take(MiniCur[0], MiniCur[1]); | ||
| MenuPointerShow(&event_pointer); | ||
| if (!MiniEventTexReadFlag) { | ||
| return true; | ||
| } | ||
| if (MiniMenu.state == MINI_MENU_REFUSED) { | ||
| if ((take.clicked & 3) != 0) { | ||
| MenuPointerPress(kInputCircle); | ||
| } | ||
| return true; | ||
| } | ||
| if (MiniMenu.state != MINI_MENU_CHOOSING) { | ||
| return true; | ||
| } | ||
| if ((take.clicked & 2) != 0) { | ||
| MenuPointerPress(kInputCircle); | ||
| return true; | ||
| } | ||
| const int hit = take.pointing ? ItemHit(event_pointer.x, event_pointer.y) : -1; | ||
| if (hit >= 0 && take.moved && take.wheel == 0.0f) { | ||
| MiniMenu.cursor = hit; | ||
| if ((take.clicked & 1) != 0) { | ||
| MenuPointerPress(kInputCross); | ||
| } | ||
| } | ||
| if (take.pointing && take.wheel != 0.0f) { | ||
| MenuPointerPress(take.wheel > 0.0f ? kInputUp : kInputDown); | ||
| } | ||
| return true; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| #pragma once | ||
|
|
||
| /** | ||
| * Handles pointer input while the door/object item picker owns the menu. | ||
| */ | ||
| bool EventItemPointerUpdate(); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,9 +1,11 @@ | ||
| #include "menu_pointer.hpp" | ||
| #include "menu_event_pointer.hpp" | ||
|
|
||
| #include <algorithm> | ||
| #include <cmath> | ||
|
|
||
| #include "battlemenu.hpp" | ||
| #include "clsmes.hpp" | ||
| #include "itemdata.hpp" | ||
| #include "dngstatusdata.hpp" | ||
| #include "editpartsinfo.hpp" | ||
|
|
@@ -13,6 +15,7 @@ | |
| #include "menu_draw.hpp" | ||
| #include "menu_inventory.hpp" | ||
| #include "menu_manual.hpp" | ||
| #include "menu_journal_chest.hpp" | ||
| #include "weapon_buildup.hpp" | ||
| #include "menu_misc.hpp" | ||
| #include "menu_option.hpp" | ||
|
|
@@ -53,23 +56,10 @@ int RingIconCount() { | |
| return BtlMenuMode == BATTLE_MENU_MODE_DUNGEON ? 8 : 9; | ||
| } | ||
|
|
||
| // Matches port/src/battlemenu.cpp GetSafeIconPos() so icons >= orig_max have valid hitboxes. | ||
| void GetRingIconPos(int index, float &out_x, float &out_y) { | ||
| const int orig_max = (BtlMenuMode == BATTLE_MENU_MODE_DUNGEON) ? 7 : 8; | ||
| if (index < orig_max) { | ||
| out_x = NorMenuIcon[index].x; | ||
| out_y = NorMenuIcon[index].y; | ||
| } else { | ||
| const int diff = index - (orig_max - 1); | ||
| out_x = NorMenuIcon[orig_max - 1].x - (16.0f * diff); | ||
| out_y = NorMenuIcon[orig_max - 1].y + (40.0f * diff); | ||
| } | ||
| } | ||
|
|
||
| // The row of one ring icon: its icon and the label beside it, where the brackets stand. | ||
| Rect RingRow(int icon) { | ||
| float ix = 0.0f, iy = 0.0f; | ||
| GetRingIconPos(icon, ix, iy); | ||
| BattleMenuIconPosition(icon, ix, iy); | ||
| int left = static_cast<int>(ix) - 18; | ||
| int top = static_cast<int>(iy) - 8; | ||
| return Rect{left, top, left + 250, top + 34}; | ||
|
|
@@ -329,6 +319,10 @@ void SaveMouse() { | |
| } | ||
| MenuPointerTake take = g_save.Take(320.0f, 240.0f); | ||
| MenuPointerShow(&g_save); | ||
| if ((take.clicked & 2) != 0) { | ||
| MenuPointerPress(SaveMenu.key_no == SAVE_KEY_END_SAVE_ENDING ? kInputCross : kInputCircle); | ||
| return; | ||
| } | ||
| bool slots = SaveMenu.key_no == SAVE_KEY_FILE_SELECT; | ||
| if (slots) { | ||
| int count = SaveSlotRows(SaveSlots, SaveMenu.access_kind == SAVE_ACCESS_SAVE); | ||
|
|
@@ -349,12 +343,35 @@ void SaveMouse() { | |
| } else { | ||
| bool confirm = SaveMenu.key_no == SAVE_KEY_LOAD_DECIDE || SaveMenu.key_no == SAVE_KEY_SAVE_DECIDE || | ||
| SaveMenu.key_no == SAVE_KEY_AFTER_ENDING; | ||
| int x = SaveMenu.key_no == SAVE_KEY_AFTER_ENDING ? 196 : 246; | ||
| int y = SaveMenu.key_no == SAVE_KEY_AFTER_ENDING ? 140 : 156; | ||
| int hit = take.pointing ? YesNoHit(x, y, g_save.x, g_save.y) : -1; | ||
| if (confirm && hit >= 0) { | ||
| if ((take.clicked & 1) != 0) MenuPointerPress(hit == 0 ? kInputCross : kInputCircle); | ||
| if ((take.clicked & 2) != 0) MenuPointerPress(kInputCircle); | ||
| if (!take.pointing || (take.clicked & 1) == 0) return; | ||
| const auto &mes = CommonMenuMes2; | ||
| if (confirm) { | ||
| // These are inline Cross/Yes and Circle/No prompts in a centred message, | ||
| // not the dungeon's vertical yes/no plate. Follow the laid-out glyphs. | ||
| for (int i = 0; i < mes.win_line_num; ++i) { | ||
| const auto &line = mes.win_line[i]; | ||
| if (line.code != -0x2F8 && line.code != -0x2FA) continue; | ||
| float left = mes.text_x + line.x; | ||
| const int row_offset = mes.fukidashi == 0 && mes.centre_rows != 0 | ||
| ? (mes.char_height * (mes.rows - mes.text_rows)) >> 1 | ||
| : 0; | ||
| float top = mes.text_y + row_offset + line.y; | ||
| float right = mes.text_x + mes.text_width; | ||
| for (int j = i + 1; j < mes.win_line_num && mes.win_line[j].y == line.y; ++j) { | ||
| if (mes.win_line[j].code == -0x2F8 || mes.win_line[j].code == -0x2FA) { | ||
| right = mes.text_x + mes.win_line[j].x; | ||
| break; | ||
| } | ||
| } | ||
| if (g_save.x >= left && g_save.x < right && g_save.y >= top && g_save.y < top + mes.char_height) { | ||
| MenuPointerPress(line.code == -0x2F8 ? kInputCross : kInputCircle); | ||
| break; | ||
| } | ||
| } | ||
| } else if (SaveMenu.key_no == SAVE_KEY_END_SAVE || SaveMenu.key_no == SAVE_KEY_END_SAVE_ENDING || | ||
| SaveMenu.key_no == SAVE_KEY_ALERT) { | ||
| // Informational messages have no choice: a click anywhere acknowledges them. | ||
| MenuPointerPress(kInputCross); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -989,6 +1006,35 @@ bool WeaponMouse(const MenuPointerTake &take) { | |
| } | ||
| } | ||
|
|
||
| // The new journal categories and tracker use their displayed row positions. | ||
| void JournalMouse(const MenuPointerTake &take) { | ||
| if ((take.clicked & 2) != 0) { | ||
| MenuPointerPress(kInputCircle); | ||
| return; | ||
| } | ||
| if (!take.pointing || (take.clicked & 1) == 0) return; | ||
| const float x = g_battle.x, y = g_battle.y; | ||
| if (ManualMenu.mode == 100) { | ||
| JournalChest::HandlePointer(x, y); | ||
| } else if (ManualMenu.mode == MANUAL_MODE_SELECT && ManualMenu.selection_level == -2) { | ||
| if (x >= 135 && x < 355 && y >= 96 && y < 236) { | ||
| ManualMenu.category = static_cast<int>((y - 96) / 28); | ||
| MenuPointerPress(kInputCross); | ||
| } | ||
| } else if (ManualMenu.mode == MANUAL_MODE_SELECT && ManualMenu.selection_level == -1) { | ||
| constexpr int counts[] = {5, 5, 4, 3}; | ||
| const auto &text = CommonMenuMes3; | ||
| if (ManualMenu.category >= 0 && ManualMenu.category < 4 && text.char_height > 0 && | ||
| x >= text.text_x && x < text.text_x + text.char_width * text.text_columns && | ||
| y >= text.text_y && y < text.text_y + counts[ManualMenu.category] * text.char_height) { | ||
| ManualMenu.entry = static_cast<int>((y - text.text_y) / text.char_height); | ||
| MenuPointerPress(kInputCross); | ||
| } | ||
| } else if (ManualMenu.mode == MANUAL_MODE_READING && x >= 0 && x < 640 && y >= 0 && y < 480) { | ||
| MenuPointerPress(x < 160 ? kInputLeft : kInputCross); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the battle-menu Journal reaches Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
|
|
||
| void BattleMouse() { | ||
| if (!g_battle.IsOpen()) { | ||
| g_battle.Open(); | ||
|
|
@@ -1021,6 +1067,11 @@ void BattleMouse() { | |
| case BTLMENU_STATE_CHARA: | ||
| AlliesMouse(take); | ||
| break; | ||
| case BTLMENU_STATE_MANUAL: | ||
| case BTLMENU_STATE_MANUAL_OPEN: | ||
| // Like Georama, the manual keeps its OPEN state after the slide finishes. | ||
| if (BtlEffectFlag == BTLEFFECT_NONE) JournalMouse(take); | ||
| break; | ||
| case BTLMENU_STATE_MOVE: | ||
| MoveMouse(take); | ||
| break; | ||
|
|
@@ -1047,6 +1098,13 @@ void MenuMouseUpdate() { | |
| bool battle = g_battle_drawn; | ||
| g_battle_drawn = false; | ||
|
|
||
| if (EventItemPointerUpdate()) { | ||
| g_battle.Abandon(); | ||
| g_save.Abandon(); | ||
| g_save_open = false; | ||
| return; | ||
| } | ||
|
|
||
| bool save = SaveMenu.texture_ready != 0 && SaveMenu.key_no != SAVE_KEY_FADE_OUT; | ||
| if (save) { | ||
| g_battle.Abandon(); | ||
|
|
@@ -1082,6 +1140,10 @@ void MenuMouseUpdate() { | |
| } | ||
|
|
||
| void MenuMouseDrawOverlay() { | ||
| if ((BattleMenuFlag == BTLMENU_STATE_MANUAL || BattleMenuFlag == BTLMENU_STATE_MANUAL_OPEN) && | ||
| ManualMenu.mode == MANUAL_MODE_READING && g_battle.IsOpen() && g_battle.pointing) { | ||
| DrawMenuObjectVibe(0, 0, 1, 0x40); | ||
| } | ||
| if (!g_discard.active) { | ||
| return; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the ending-only save reaches
SAVE_KEY_END_SAVE_ENDING, this branch turns every right click intoPAD_CIRCLEand returns, butSaveMenuKeyEndSaveEndinginport/src/memcard.cpponly checksPAD_CROSS. Consequently the completion message cannot be dismissed through the right-click/Back path (unlikeSAVE_KEY_END_SAVE); send the acknowledgement input for this state or make its state handler accept Circle.Useful? React with 👍 / 👎.