1818// per-frame `OnMouseDown` scripts — these fire even when the click
1919// misses any UI frame.
2020//
21+ // `IsMouseButtonDown([button])` — query the current held state of a
22+ // specific button by name or 1-based ID, or "any button held" when
23+ // called with no args. State is maintained from the same hook stream
24+ // as the events (single source of truth — events firing and the
25+ // state being readable happen at the same point in the pipeline).
26+ //
2127// Same `WH_GETMESSAGE` thread-message hook pattern as
2228// [Input::Modifier](Modifier.cpp): we intercept the WM_*BUTTON*
2329// messages on the engine's main thread *before* dispatch. The hook
2834#include " Game.h"
2935#include " event/Custom.h"
3036
37+ #include < cstdint>
38+ #include < cstring>
39+
3140#include < windows.h>
3241
3342namespace Input ::GlobalMouse {
@@ -37,6 +46,19 @@ namespace {
3746constexpr const char *kDownEvent = " GLOBAL_MOUSE_DOWN" ;
3847constexpr const char *kUpEvent = " GLOBAL_MOUSE_UP" ;
3948
49+ // Held-state bitmap — bit `i` set means the button with 1-based
50+ // `IsMouseButtonDown` ID `i+1` is currently held. Modern API IDs:
51+ // 1 = LeftButton, 2 = RightButton, 3 = MiddleButton,
52+ // 4 = Button4 (XBUTTON1), 5 = Button5 (XBUTTON2).
53+ constexpr int BIT_LEFT_BUTTON = 0 ;
54+ constexpr int BIT_RIGHT_BUTTON = 1 ;
55+ constexpr int BIT_MIDDLE_BUTTON = 2 ;
56+ constexpr int BIT_BUTTON4 = 3 ;
57+ constexpr int BIT_BUTTON5 = 4 ;
58+ constexpr uint32_t MASK_ANY_BUTTON = 0x1F ;
59+
60+ uint32_t g_mouseButtonMask = 0 ;
61+
4062HHOOK g_msgHook = nullptr ;
4163
4264// Maps WM_*BUTTON{DOWN,UP} → ("LeftButton"|..., isDown). Returns
@@ -60,16 +82,60 @@ const char *DecodeButton(UINT msg, WPARAM wParam, bool *outDown) {
6082 }
6183}
6284
85+ // Maps a "LeftButton"/.../"Button5" name to its bit index. -1 for
86+ // any other string.
87+ int NameToBitIdx (const char *name) {
88+ if (name == nullptr )
89+ return -1 ;
90+ if (std::strcmp (name, " LeftButton" ) == 0 ) return BIT_LEFT_BUTTON ;
91+ if (std::strcmp (name, " RightButton" ) == 0 ) return BIT_RIGHT_BUTTON ;
92+ if (std::strcmp (name, " MiddleButton" ) == 0 ) return BIT_MIDDLE_BUTTON ;
93+ if (std::strcmp (name, " Button4" ) == 0 ) return BIT_BUTTON4 ;
94+ if (std::strcmp (name, " Button5" ) == 0 ) return BIT_BUTTON5 ;
95+ return -1 ;
96+ }
97+
6398void ProcessMouseMessage (UINT msg, WPARAM wParam) {
6499 bool down = false ;
65100 const char *name = DecodeButton (msg, wParam, &down);
66101 if (name == nullptr )
67102 return ;
103+
104+ const int bitIdx = NameToBitIdx (name);
105+ if (bitIdx >= 0 ) {
106+ const uint32_t bit = 1u << bitIdx;
107+ if (down) g_mouseButtonMask |= bit;
108+ else g_mouseButtonMask &= ~bit;
109+ }
110+
68111 const int slot = Event::Custom::Lookup (down ? kDownEvent : kUpEvent );
69112 if (slot >= 0 )
70113 Event::Custom::Fire (slot, " %s" , name);
71114}
72115
116+ int __fastcall Script_IsMouseButtonDown (void *L) {
117+ bool down;
118+ if (Game::Lua::GetTop (L) == 0 ) {
119+ // No-arg form: any button held.
120+ down = (g_mouseButtonMask & MASK_ANY_BUTTON ) != 0 ;
121+ } else {
122+ int bitIdx = -1 ;
123+ if (Game::Lua::IsNumber (L, 1 )) {
124+ const int id = static_cast <int >(Game::Lua::ToNumber (L, 1 ));
125+ if (id >= 1 && id <= 5 )
126+ bitIdx = id - 1 ;
127+ } else if (Game::Lua::IsString (L, 1 )) {
128+ bitIdx = NameToBitIdx (Game::Lua::ToString (L, 1 ));
129+ }
130+ // Unrecognized button (bad id, unknown name) → false, matching
131+ // the modern API's "did this specific button happen to be held"
132+ // semantic. Bad input doesn't promote to the any-button check.
133+ down = (bitIdx >= 0 ) && (g_mouseButtonMask & (1u << bitIdx)) != 0 ;
134+ }
135+ Game::Lua::PushBool (L, down);
136+ return 1 ;
137+ }
138+
73139LRESULT CALLBACK GetMsgHook (int code, WPARAM wParam, LPARAM lParam) {
74140 // Only process when the message is being removed from the queue
75141 // (`PM_REMOVE` / `GetMessage`); `PM_NOREMOVE` peeks would deliver
@@ -88,7 +154,11 @@ void InstallHook() {
88154 GetCurrentThreadId ());
89155}
90156
91- void RegisterLuaFunctions () { InstallHook (); }
157+ void RegisterLuaFunctions () {
158+ Game::Lua::RegisterGlobalFunction (" IsMouseButtonDown" ,
159+ &Script_IsMouseButtonDown);
160+ InstallHook ();
161+ }
92162
93163const Event::Custom::AutoReserve _reserveDown{kDownEvent };
94164const Event::Custom::AutoReserve _reserveUp{kUpEvent };
0 commit comments