diff --git a/Source/controls/plrctrls.cpp b/Source/controls/plrctrls.cpp index f54860b91..aaba75f40 100644 --- a/Source/controls/plrctrls.cpp +++ b/Source/controls/plrctrls.cpp @@ -1479,7 +1479,7 @@ void PerformSpellAction() } UpdateSpellTarget(); - CheckPlrSpell(); + CheckPlrSpell(false); } void CtrlUseInvItem() diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 8bda033f4..d2ffee241 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -373,6 +373,32 @@ void CheckCursMove() my = MAXDUNY - 1; } + // While holding down left click we should retain target (but potentially lose it if it dies, goes out of view, etc) + if (sgbMouseDown == CLICK_LEFT && pcursinvitem == -1) { + if (pcursmonst != -1) { + if (Monsters[pcursmonst]._mDelFlag || Monsters[pcursmonst]._mhitpoints >> 6 <= 0 + || !(dFlags[Monsters[pcursmonst].position.tile.x][Monsters[pcursmonst].position.tile.y] & BFLAG_VISIBLE)) + pcursmonst = -1; + } else if (pcursobj != -1) { + if (Objects[pcursobj]._oSelFlag < 1) + pcursobj = -1; + } else if (pcursplr != -1) { + if (Players[pcursplr]._pmode == PM_DEATH || Players[pcursplr]._pmode == PM_QUIT || !Players[pcursplr].plractive + || currlevel != Players[pcursplr].plrlevel || Players[pcursplr]._pHitPoints >> 6 <= 0 + || !(dFlags[Players[pcursplr].position.tile.x][Players[pcursplr].position.tile.y] & BFLAG_VISIBLE)) + pcursplr = -1; + } + + if (pcursmonst == -1 && pcursobj == -1 && pcursitem == -1 && pcursinvitem == -1 && pcursplr == -1) { + cursmx = mx; + cursmy = my; + CheckTrigForce(); + CheckTown(); + CheckRportal(); + } + return; + } + bool flipflag = (flipy && flipx) || ((flipy || flipx) && px < TILE_WIDTH / 2); pcurstemp = pcursmonst; diff --git a/Source/diablo.cpp b/Source/diablo.cpp index c19ef478e..6634d05b0 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -19,6 +19,7 @@ #endif #include "DiabloUI/diabloui.h" #include "controls/keymapper.hpp" +#include "diablo.h" #include "doom.h" #include "drlg_l1.h" #include "drlg_l2.h" @@ -129,6 +130,11 @@ QuickMessage QuickMessages[QUICK_MESSAGE_OPTIONS] = { { "QuickMessage4", N_("Now you DIE!") } }; +MouseActionType lastLeftMouseButtonAction = MouseActionType::None; // This and the following mouse variables are for handling in-game click-and-hold actions +MouseActionType lastRightMouseButtonAction = MouseActionType::None; +Uint32 lastLeftMouseButtonTime = 0; +Uint32 lastRightMouseButtonTime = 0; + // Controller support: Actions to run after updating the cursor state. // Defined in SourceX/controls/plctrls.cpp. extern void finish_simulated_mouse_clicks(int currentMouseX, int currentMouseY); @@ -248,14 +254,17 @@ bool LeftMouseCmd(bool bShift) NetSendCmdLocParam1(true, pcurs == CURSOR_DISARM ? CMD_DISARMXY : CMD_OPOBJXY, { cursmx, cursmy }, pcursobj); } else if (myPlayer._pwtype == WT_RANGED) { if (bShift) { + lastLeftMouseButtonAction = MouseActionType::Attack; NetSendCmdLoc(MyPlayerId, true, CMD_RATTACKXY, { cursmx, cursmy }); } else if (pcursmonst != -1) { if (CanTalkToMonst(Monsters[pcursmonst])) { NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst); } else { + lastLeftMouseButtonAction = MouseActionType::Attack_MonsterTarget; NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst); } } else if (pcursplr != -1 && !gbFriendlyMode) { + lastLeftMouseButtonAction = MouseActionType::Attack_PlayerTarget; NetSendCmdParam1(true, CMD_RATTACKPID, pcursplr); } } else { @@ -264,14 +273,18 @@ bool LeftMouseCmd(bool bShift) if (CanTalkToMonst(Monsters[pcursmonst])) { NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst); } else { + lastLeftMouseButtonAction = MouseActionType::Attack; NetSendCmdLoc(MyPlayerId, true, CMD_SATTACKXY, { cursmx, cursmy }); } } else { + lastLeftMouseButtonAction = MouseActionType::Attack; NetSendCmdLoc(MyPlayerId, true, CMD_SATTACKXY, { cursmx, cursmy }); } } else if (pcursmonst != -1) { + lastLeftMouseButtonAction = MouseActionType::Attack_MonsterTarget; NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst); } else if (pcursplr != -1 && !gbFriendlyMode) { + lastLeftMouseButtonAction = MouseActionType::Attack_PlayerTarget; NetSendCmdParam1(true, CMD_ATTACKPID, pcursplr); } } @@ -284,6 +297,9 @@ bool LeftMouseCmd(bool bShift) bool LeftMouseDown(int wParam) { + lastLeftMouseButtonAction = MouseActionType::Other; + lastLeftMouseButtonTime = SDL_GetTicks(); + if (gmenu_left_mouse(true)) return false; @@ -372,6 +388,9 @@ void LeftMouseUp(int wParam) void RightMouseDown() { + lastRightMouseButtonAction = MouseActionType::Other; + lastRightMouseButtonTime = SDL_GetTicks(); + if (gmenu_is_active() || sgnTimeoutCurs != CURSOR_NONE || PauseMode == 2 || Players[MyPlayerId]._pInvincible) { return; } @@ -392,7 +411,7 @@ void RightMouseDown() && (pcursinvitem == -1 || !UseInvItem(MyPlayerId, pcursinvitem)))) { if (pcurs == CURSOR_HAND) { if (pcursinvitem == -1 || !UseInvItem(MyPlayerId, pcursinvitem)) - CheckPlrSpell(); + CheckPlrSpell(true); } else if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) { NewCursor(CURSOR_HAND); } @@ -720,6 +739,7 @@ void GameEventHandler(uint32_t uMsg, int32_t wParam, int32_t lParam) return; case DVL_WM_LBUTTONUP: GetMousePos(lParam); + lastLeftMouseButtonAction = MouseActionType::None; if (sgbMouseDown == CLICK_LEFT) { sgbMouseDown = CLICK_NONE; LeftMouseUp(wParam); @@ -735,6 +755,7 @@ void GameEventHandler(uint32_t uMsg, int32_t wParam, int32_t lParam) return; case DVL_WM_RBUTTONUP: GetMousePos(lParam); + lastRightMouseButtonAction = MouseActionType::None; if (sgbMouseDown == CLICK_RIGHT) { sgbMouseDown = CLICK_NONE; } @@ -2118,6 +2139,13 @@ void LoadGameLevel(bool firstflag, lvl_entry lvldir) if (!gbIsSpawn && setlevel && setlvlnum == SL_SKELKING && Quests[Q_SKELKING]._qactive == QUEST_ACTIVE) PlaySFX(USFX_SKING1); + + // Reset mouse selection of entities + pcursmonst = -1; + pcursobj = -1; + pcursitem = -1; + pcursinvitem = -1; + pcursplr = -1; } /** diff --git a/Source/diablo.h b/Source/diablo.h index 538051d80..f14a2d94c 100644 --- a/Source/diablo.h +++ b/Source/diablo.h @@ -43,6 +43,16 @@ enum class GameLogicStep { ProcessMissilesTown, }; +enum class MouseActionType : int { + None, + Spell, + Spell_ComplainedAboutMana, + Attack, + Attack_MonsterTarget, + Attack_PlayerTarget, + Other, +}; + extern SDL_Window *ghMainWnd; extern DWORD glSeedTbl[NUMLEVELS]; extern dungeon_type gnLevelTypeTbl[NUMLEVELS]; @@ -69,6 +79,11 @@ extern clicktype sgbMouseDown; extern uint16_t gnTickDelay; extern char gszProductName[64]; +extern MouseActionType lastLeftMouseButtonAction; +extern MouseActionType lastRightMouseButtonAction; +extern Uint32 lastLeftMouseButtonTime; +extern Uint32 lastRightMouseButtonTime; + void FreeGameMem(); bool StartGame(bool bNewGame, bool bSinglePlayer); [[noreturn]] void diablo_quit(int exitStatus); diff --git a/Source/items.cpp b/Source/items.cpp index 4ab1e4f53..5e7f99935 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -3710,6 +3710,8 @@ void DeleteItem(int ii, int i) ActiveItemCount--; if (ActiveItemCount > 0 && i != ActiveItemCount) ActiveItems[i] = ActiveItems[ActiveItemCount]; + if (pcursitem == ii) // Unselect item if player has it highlighted + pcursitem = -1; } void ProcessItems() diff --git a/Source/monster.cpp b/Source/monster.cpp index 3527f94cd..1dda0d277 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -4446,6 +4446,8 @@ void DeleteMonsterList() for (int i = MAX_PLRS; i < ActiveMonsterCount;) { if (Monsters[ActiveMonsters[i]]._mDelFlag) { + if (pcursmonst == ActiveMonsters[i]) // Unselect monster if player highlighted it + pcursmonst = -1; DeleteMonster(i); } else { i++; diff --git a/Source/objects.cpp b/Source/objects.cpp index a572a945c..32add173c 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -1212,6 +1212,8 @@ void DeleteObject(int oi, int i) dObject[ox][oy] = 0; AvailableObjects[-ActiveObjectCount + MAXOBJECTS] = oi; ActiveObjectCount--; + if (pcursobj == oi) // Unselect object if this was highlighted by player + pcursobj = -1; if (ActiveObjectCount > 0 && i != ActiveObjectCount) ActiveObjects[i] = ActiveObjects[ActiveObjectCount]; } diff --git a/Source/player.cpp b/Source/player.cpp index c3b4dbae3..55f0c8bb5 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -3572,7 +3572,7 @@ void CalcPlrStaff(PlayerStruct &player) } } -void CheckPlrSpell() +void CheckPlrSpell(bool mouseClick) { bool addflag = false; int sl; @@ -3643,11 +3643,16 @@ void CheckPlrSpell() sl = GetSpellLevel(MyPlayerId, myPlayer._pRSpell); NetSendCmdLocParam2(true, CMD_SPELLXY, { cursmx, cursmy }, myPlayer._pRSpell, sl); } + if (mouseClick) + lastRightMouseButtonAction = MouseActionType::Spell; return; } if (myPlayer._pRSplType == RSPLTYPE_SPELL) { - myPlayer.Say(HeroSpeech::NotEnoughMana); + if (!mouseClick || lastRightMouseButtonAction != MouseActionType::Spell_ComplainedAboutMana) + myPlayer.Say(HeroSpeech::NotEnoughMana); + if (mouseClick) + lastRightMouseButtonAction = MouseActionType::Spell_ComplainedAboutMana; } } diff --git a/Source/player.h b/Source/player.h index dc3099b29..7021053ca 100644 --- a/Source/player.h +++ b/Source/player.h @@ -490,7 +490,7 @@ void ClrPlrPath(PlayerStruct &player); bool PosOkPlayer(int pnum, Point position); void MakePlrPath(int pnum, Point targetPosition, bool endspace); void CalcPlrStaff(PlayerStruct &player); -void CheckPlrSpell(); +void CheckPlrSpell(bool mouseClick); void SyncPlrAnim(int pnum); void SyncInitPlrPos(int pnum); void SyncInitPlr(int pnum); diff --git a/Source/track.cpp b/Source/track.cpp index a863f3ac0..7c1f9203d 100644 --- a/Source/track.cpp +++ b/Source/track.cpp @@ -21,8 +21,60 @@ bool sgbIsWalking; } // namespace +static bool RepeatMouseAttack(bool leftButton) +{ + if (pcurs != CURSOR_HAND) + return false; + + Uint32 *timePressed; + MouseActionType lastAction; + if (leftButton) { + if (sgbMouseDown != CLICK_LEFT) + return false; + timePressed = &lastLeftMouseButtonTime; + lastAction = lastLeftMouseButtonAction; + } else { + if (sgbMouseDown != CLICK_RIGHT) + return false; + timePressed = &lastRightMouseButtonTime; + lastAction = lastRightMouseButtonAction; + } + + if (lastAction != MouseActionType::Attack && lastAction != MouseActionType::Attack_MonsterTarget && lastAction != MouseActionType::Attack_PlayerTarget && lastAction != MouseActionType::Spell && lastAction != MouseActionType::Spell_ComplainedAboutMana) + return false; + + if (Players[MyPlayerId]._pmode != PM_DEATH && Players[MyPlayerId]._pmode != PM_QUIT && + Players[MyPlayerId].destAction == ACTION_NONE && SDL_GetTicks() - *timePressed >= (Uint32)gnTickDelay * 4) { + bool rangedAttack = Players[MyPlayerId]._pwtype == WT_RANGED; + *timePressed = SDL_GetTicks(); + switch (lastAction) { + case MouseActionType::Attack: + if (cursmx >= 0 && cursmx < MAXDUNX && cursmy >= 0 && cursmy < MAXDUNY) + NetSendCmdLoc(MyPlayerId, true, rangedAttack ? CMD_RATTACKXY : CMD_SATTACKXY, { cursmx, cursmy }); + break; + case MouseActionType::Attack_MonsterTarget: + if (pcursmonst != -1) + NetSendCmdParam1(true, rangedAttack ? CMD_RATTACKID : CMD_ATTACKID, pcursmonst); + break; + case MouseActionType::Attack_PlayerTarget: + if (pcursplr != -1 && !gbFriendlyMode) + NetSendCmdParam1(true, rangedAttack ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr); + break; + case MouseActionType::Spell: + case MouseActionType::Spell_ComplainedAboutMana: + CheckPlrSpell(true); + break; + } + } + + return true; +} + void track_process() { + if (RepeatMouseAttack(true) || RepeatMouseAttack(false)) + return; + if (!sgbIsWalking) return;