mirror of
https://github.com/libretro/devilutionX.git
synced 2026-11-13 04:34:45 -10:00
* Refactor GetRotaryDistance to use Point instead of int x/y params * Refactor HSExists to use Point instead of int x/y params * Refactor IsPathBlocked to take typed params (Point, Direction) The caller was already passing in a Direction value so this matches usage better. I also pulled the Direction to Point helper function up so it is available as a static class member, this allows replacing the use of the Offset array in plrctrls.cpp. When adding a Direction to a point (and not scaling it first) I avoid explicitly creating a Point object since the operator+ overload will do that conversion implicitly. * Replace Offsets array with Point::fromDirection * Refactor GetDistanceRanged to use Point I've added ExactDistance as a member function of Point to match ApproxDistance instead of only having it defined in GetDistanceRanged, it seemed more appropriate to be part of the class. Also removed temporary variables from callers of GetDistanceRanged as they were mainly used as a convenience for avoiding repetition when passing values into this function. * Refactor GetDistance to use Point
1541 lines
42 KiB
C++
1541 lines
42 KiB
C++
#include "controls/plrctrls.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstdint>
|
|
#include <list>
|
|
|
|
#include "automap.h"
|
|
#include "control.h"
|
|
#include "controls/controller_motion.h"
|
|
#include "controls/controller.h"
|
|
#include "controls/game_controls.h"
|
|
#include "cursor.h"
|
|
#include "doom.h"
|
|
#include "engine/point.hpp"
|
|
#include "gmenu.h"
|
|
#include "help.h"
|
|
#include "inv.h"
|
|
#include "minitext.h"
|
|
#include "missiles.h"
|
|
#include "stores.h"
|
|
#include "towners.h"
|
|
#include "trigs.h"
|
|
|
|
#define SPLICONLENGTH 56
|
|
|
|
namespace devilution {
|
|
|
|
bool sgbControllerActive = false;
|
|
Point speedspellscoords[50];
|
|
int speedspellcount = 0;
|
|
|
|
/**
|
|
* Native game menu, controlled by simulating a keyboard.
|
|
*/
|
|
bool InGameMenu()
|
|
{
|
|
return stextflag != STORE_NONE
|
|
|| helpflag
|
|
|| talkflag
|
|
|| qtextflag
|
|
|| gmenu_is_active()
|
|
|| PauseMode == 2
|
|
|| plr[myplr]._pInvincible;
|
|
}
|
|
|
|
namespace {
|
|
|
|
int slot = SLOTXY_INV_FIRST;
|
|
|
|
/**
|
|
* Number of angles to turn to face the coordinate
|
|
* @param destination Tile coordinates
|
|
* @return -1 == down
|
|
*/
|
|
int GetRotaryDistance(Point destination)
|
|
{
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
if (myPlayer.position.future == destination)
|
|
return -1;
|
|
|
|
int d1 = myPlayer._pdir;
|
|
int d2 = GetDirection(myPlayer.position.future, destination);
|
|
|
|
int d = abs(d1 - d2);
|
|
if (d > 4)
|
|
return 4 - (d % 4);
|
|
|
|
return d;
|
|
}
|
|
|
|
/**
|
|
* @brief Get the best case walking steps to coordinates
|
|
* @param Position Tile coordinates
|
|
*/
|
|
int GetMinDistance(Point position)
|
|
{
|
|
return plr[myplr].position.future.WalkingDistance(position);
|
|
}
|
|
|
|
/**
|
|
* @brief Get walking steps to coordinate
|
|
* @param destination Tile coordinates
|
|
* @param maxDistance the max number of steps to search
|
|
* @return number of steps, or 0 if not reachable
|
|
*/
|
|
int GetDistance(Point destination, int maxDistance)
|
|
{
|
|
if (GetMinDistance(destination) > maxDistance) {
|
|
return 0;
|
|
}
|
|
|
|
int8_t walkpath[MAX_PATH_LENGTH];
|
|
int steps = FindPath(PosOkPlayer, myplr, plr[myplr].position.future.x, plr[myplr].position.future.y, destination.x, destination.y, walkpath);
|
|
if (steps > maxDistance)
|
|
return 0;
|
|
|
|
return steps;
|
|
}
|
|
|
|
/**
|
|
* @brief Get distance to coordinate
|
|
* @param destination Tile coordinates
|
|
*/
|
|
int GetDistanceRanged(Point destination)
|
|
{
|
|
return plr[myplr].position.future.ExactDistance(destination);
|
|
}
|
|
|
|
/**
|
|
* @todo The loops in this function are iterating through tiles offset from the player position. This
|
|
* could be accomplished by looping over the values in the Direction enum and making use of
|
|
* Point math instead of nested loops from [-1, 1].
|
|
*/
|
|
void FindItemOrObject()
|
|
{
|
|
int mx = plr[myplr].position.future.x;
|
|
int my = plr[myplr].position.future.y;
|
|
int rotations = 5;
|
|
|
|
// As the player can not stand on the edge of the map this is safe from OOB
|
|
for (int xx = -1; xx < 2; xx++) {
|
|
for (int yy = -1; yy < 2; yy++) {
|
|
if (dItem[mx + xx][my + yy] <= 0)
|
|
continue;
|
|
int i = dItem[mx + xx][my + yy] - 1;
|
|
if (items[i].isEmpty()
|
|
|| items[i]._iSelFlag == 0)
|
|
continue;
|
|
int newRotations = GetRotaryDistance({ mx + xx, my + yy });
|
|
if (rotations < newRotations)
|
|
continue;
|
|
if (xx != 0 && yy != 0 && GetDistance({ mx + xx, my + yy }, 1) == 0)
|
|
continue;
|
|
rotations = newRotations;
|
|
pcursitem = i;
|
|
cursmx = mx + xx;
|
|
cursmy = my + yy;
|
|
}
|
|
}
|
|
|
|
if (leveltype == DTYPE_TOWN || pcursitem != -1)
|
|
return; // Don't look for objects in town
|
|
|
|
for (int xx = -1; xx < 2; xx++) {
|
|
for (int yy = -1; yy < 2; yy++) {
|
|
if (dObject[mx + xx][my + yy] == 0)
|
|
continue;
|
|
int o = dObject[mx + xx][my + yy] > 0 ? dObject[mx + xx][my + yy] - 1 : -(dObject[mx + xx][my + yy] + 1);
|
|
if (object[o]._oSelFlag == 0)
|
|
continue;
|
|
if (xx == 0 && yy == 0 && object[o]._oDoorFlag)
|
|
continue; // Ignore doorway so we don't get stuck behind barrels
|
|
int newRotations = GetRotaryDistance({ mx + xx, my + yy });
|
|
if (rotations < newRotations)
|
|
continue;
|
|
if (xx != 0 && yy != 0 && GetDistance({ mx + xx, my + yy }, 1) == 0)
|
|
continue;
|
|
rotations = newRotations;
|
|
pcursobj = o;
|
|
cursmx = mx + xx;
|
|
cursmy = my + yy;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckTownersNearby()
|
|
{
|
|
for (int i = 0; i < 16; i++) {
|
|
int distance = GetDistance(towners[i].position, 2);
|
|
if (distance == 0)
|
|
continue;
|
|
pcursmonst = i;
|
|
}
|
|
}
|
|
|
|
bool HasRangedSpell()
|
|
{
|
|
int spl = plr[myplr]._pRSpell;
|
|
|
|
return spl != SPL_INVALID
|
|
&& spl != SPL_TOWN
|
|
&& spl != SPL_TELEPORT
|
|
&& spelldata[spl].sTargeted
|
|
&& !spelldata[spl].sTownSpell;
|
|
}
|
|
|
|
bool CanTargetMonster(int mi)
|
|
{
|
|
const MonsterStruct &monst = monster[mi];
|
|
|
|
if ((monst._mFlags & (MFLAG_HIDDEN | MFLAG_GOLEM)) != 0)
|
|
return false;
|
|
if (monst._mhitpoints >> 6 <= 0) // dead
|
|
return false;
|
|
|
|
const int mx = monst.position.tile.x;
|
|
const int my = monst.position.tile.y;
|
|
if ((dFlags[mx][my] & BFLAG_LIT) == 0) // not visible
|
|
return false;
|
|
if (dMonster[mx][my] == 0)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void FindRangedTarget()
|
|
{
|
|
int rotations = 0;
|
|
int distance = 0;
|
|
bool canTalk = false;
|
|
|
|
// The first MAX_PLRS monsters are reserved for players' golems.
|
|
for (int mi = MAX_PLRS; mi < MAXMONSTERS; mi++) {
|
|
if (!CanTargetMonster(mi))
|
|
continue;
|
|
|
|
const bool newCanTalk = CanTalkToMonst(mi);
|
|
if (pcursmonst != -1 && !canTalk && newCanTalk)
|
|
continue;
|
|
const int newDdistance = GetDistanceRanged(monster[mi].position.future);
|
|
const int newRotations = GetRotaryDistance(monster[mi].position.future);
|
|
if (pcursmonst != -1 && canTalk == newCanTalk) {
|
|
if (distance < newDdistance)
|
|
continue;
|
|
if (distance == newDdistance && rotations < newRotations)
|
|
continue;
|
|
}
|
|
distance = newDdistance;
|
|
rotations = newRotations;
|
|
canTalk = newCanTalk;
|
|
pcursmonst = mi;
|
|
}
|
|
}
|
|
|
|
void FindMeleeTarget()
|
|
{
|
|
bool visited[MAXDUNX][MAXDUNY] = { {} };
|
|
int maxSteps = 25; // Max steps for FindPath is 25
|
|
int rotations = 0;
|
|
bool canTalk = false;
|
|
|
|
struct SearchNode {
|
|
int x, y;
|
|
int steps;
|
|
};
|
|
std::list<SearchNode> queue;
|
|
|
|
{
|
|
const int startX = plr[myplr].position.future.x;
|
|
const int startY = plr[myplr].position.future.y;
|
|
visited[startX][startY] = true;
|
|
queue.push_back({ startX, startY, 0 });
|
|
}
|
|
|
|
while (!queue.empty()) {
|
|
SearchNode node = queue.front();
|
|
queue.pop_front();
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
const int dx = node.x + pathxdir[i];
|
|
const int dy = node.y + pathydir[i];
|
|
|
|
if (visited[dx][dy])
|
|
continue; // already visisted
|
|
|
|
if (node.steps > maxSteps) {
|
|
visited[dx][dy] = true;
|
|
continue;
|
|
}
|
|
|
|
if (!PosOkPlayer(myplr, { dx, dy })) {
|
|
visited[dx][dy] = true;
|
|
|
|
if (dMonster[dx][dy] != 0) {
|
|
const int mi = dMonster[dx][dy] > 0 ? dMonster[dx][dy] - 1 : -(dMonster[dx][dy] + 1);
|
|
if (CanTargetMonster(mi)) {
|
|
const bool newCanTalk = CanTalkToMonst(mi);
|
|
if (pcursmonst != -1 && !canTalk && newCanTalk)
|
|
continue;
|
|
const int newRotations = GetRotaryDistance({ dx, dy });
|
|
if (pcursmonst != -1 && canTalk == newCanTalk && rotations < newRotations)
|
|
continue;
|
|
rotations = newRotations;
|
|
canTalk = newCanTalk;
|
|
pcursmonst = mi;
|
|
if (!canTalk)
|
|
maxSteps = node.steps; // Monsters found, cap search to current steps
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
PATHNODE pPath;
|
|
pPath.position = { node.x, node.y };
|
|
|
|
if (path_solid_pieces(&pPath, dx, dy)) {
|
|
queue.push_back({ dx, dy, node.steps + 1 });
|
|
visited[dx][dy] = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckMonstersNearby()
|
|
{
|
|
if (plr[myplr]._pwtype == WT_RANGED || HasRangedSpell()) {
|
|
FindRangedTarget();
|
|
return;
|
|
}
|
|
|
|
FindMeleeTarget();
|
|
}
|
|
|
|
void CheckPlayerNearby()
|
|
{
|
|
int newDdistance;
|
|
int rotations = 0;
|
|
int distance = 0;
|
|
|
|
if (pcursmonst != -1)
|
|
return;
|
|
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
int spl = myPlayer._pRSpell;
|
|
if (gbFriendlyMode && spl != SPL_RESURRECT && spl != SPL_HEALOTHER)
|
|
return;
|
|
|
|
for (int i = 0; i < MAX_PLRS; i++) {
|
|
if (i == myplr)
|
|
continue;
|
|
const auto &player = plr[i];
|
|
const int mx = player.position.future.x;
|
|
const int my = player.position.future.y;
|
|
if (dPlayer[mx][my] == 0
|
|
|| (dFlags[mx][my] & BFLAG_LIT) == 0
|
|
|| (player._pHitPoints == 0 && spl != SPL_RESURRECT))
|
|
continue;
|
|
|
|
if (myPlayer._pwtype == WT_RANGED || HasRangedSpell() || spl == SPL_HEALOTHER) {
|
|
newDdistance = GetDistanceRanged(player.position.future);
|
|
} else {
|
|
newDdistance = GetDistance(player.position.future, distance);
|
|
if (newDdistance == 0)
|
|
continue;
|
|
}
|
|
|
|
if (pcursplr != -1 && distance < newDdistance)
|
|
continue;
|
|
const int newRotations = GetRotaryDistance(player.position.future);
|
|
if (pcursplr != -1 && distance == newDdistance && rotations < newRotations)
|
|
continue;
|
|
|
|
distance = newDdistance;
|
|
rotations = newRotations;
|
|
pcursplr = i;
|
|
}
|
|
}
|
|
|
|
void FindActor()
|
|
{
|
|
if (leveltype != DTYPE_TOWN)
|
|
CheckMonstersNearby();
|
|
else
|
|
CheckTownersNearby();
|
|
|
|
if (gbIsMultiplayer)
|
|
CheckPlayerNearby();
|
|
}
|
|
|
|
int pcursmissile;
|
|
int pcurstrig;
|
|
int pcursquest;
|
|
|
|
void FindTrigger()
|
|
{
|
|
int rotations = 0;
|
|
int distance = 0;
|
|
|
|
if (pcursitem != -1 || pcursobj != -1)
|
|
return; // Prefer showing items/objects over triggers (use of cursm* conflicts)
|
|
|
|
for (int i = 0; i < nummissiles; i++) {
|
|
int mi = missileactive[i];
|
|
if (missile[mi]._mitype == MIS_TOWN || missile[mi]._mitype == MIS_RPORTAL) {
|
|
const int newDistance = GetDistance(missile[mi].position.tile, 2);
|
|
if (newDistance == 0)
|
|
continue;
|
|
if (pcursmissile != -1 && distance < newDistance)
|
|
continue;
|
|
const int newRotations = GetRotaryDistance(missile[mi].position.tile);
|
|
if (pcursmissile != -1 && distance == newDistance && rotations < newRotations)
|
|
continue;
|
|
cursmx = missile[mi].position.tile.x;
|
|
cursmy = missile[mi].position.tile.y;
|
|
pcursmissile = mi;
|
|
distance = newDistance;
|
|
rotations = newRotations;
|
|
}
|
|
}
|
|
|
|
if (pcursmissile == -1) {
|
|
for (int i = 0; i < numtrigs; i++) {
|
|
int tx = trigs[i].position.x;
|
|
int ty = trigs[i].position.y;
|
|
if (trigs[i]._tlvl == 13)
|
|
ty -= 1;
|
|
const int newDistance = GetDistance({ tx, ty }, 2);
|
|
if (newDistance == 0)
|
|
continue;
|
|
cursmx = tx;
|
|
cursmy = ty;
|
|
pcurstrig = i;
|
|
}
|
|
|
|
if (pcurstrig == -1) {
|
|
for (int i = 0; i < MAXQUESTS; i++) {
|
|
if (i == Q_BETRAYER || currlevel != quests[i]._qlevel || quests[i]._qslvl == 0)
|
|
continue;
|
|
const int newDistance = GetDistance(quests[i].position, 2);
|
|
if (newDistance == 0)
|
|
continue;
|
|
cursmx = quests[i].position.x;
|
|
cursmy = quests[i].position.y;
|
|
pcursquest = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pcursmonst != -1 || pcursplr != -1 || cursmx == -1 || cursmy == -1)
|
|
return; // Prefer monster/player info text
|
|
|
|
CheckTrigForce();
|
|
CheckTown();
|
|
CheckRportal();
|
|
}
|
|
|
|
void Interact()
|
|
{
|
|
if (leveltype == DTYPE_TOWN && pcursmonst != -1) {
|
|
NetSendCmdLocParam1(true, CMD_TALKXY, towners[pcursmonst].position, pcursmonst);
|
|
} else if (pcursmonst != -1) {
|
|
if (plr[myplr]._pwtype != WT_RANGED || CanTalkToMonst(pcursmonst)) {
|
|
NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst);
|
|
} else {
|
|
NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst);
|
|
}
|
|
} else if (leveltype != DTYPE_TOWN && pcursplr != -1 && !gbFriendlyMode) {
|
|
NetSendCmdParam1(true, plr[myplr]._pwtype == WT_RANGED ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr);
|
|
}
|
|
}
|
|
|
|
void AttrIncBtnSnap(AxisDirection dir)
|
|
{
|
|
static AxisDirectionRepeater repeater;
|
|
dir = repeater.Get(dir);
|
|
if (dir.y == AxisDirectionY_NONE)
|
|
return;
|
|
|
|
if (chrbtnactive && plr[myplr]._pStatPts <= 0)
|
|
return;
|
|
|
|
// first, find our cursor location
|
|
int slot = 0;
|
|
for (int i = 0; i < 4; i++) {
|
|
if (MouseX >= ChrBtnsRect[i].position.x
|
|
&& MouseX <= ChrBtnsRect[i].position.x + ChrBtnsRect[i].size.width
|
|
&& MouseY >= ChrBtnsRect[i].position.y
|
|
&& MouseY <= ChrBtnsRect[i].size.height + ChrBtnsRect[i].position.y) {
|
|
slot = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (dir.y == AxisDirectionY_UP) {
|
|
if (slot > 0)
|
|
--slot;
|
|
} else if (dir.y == AxisDirectionY_DOWN) {
|
|
if (slot < 3)
|
|
++slot;
|
|
}
|
|
|
|
// move cursor to our new location
|
|
int x = ChrBtnsRect[slot].position.x + (ChrBtnsRect[slot].size.width / 2);
|
|
int y = ChrBtnsRect[slot].position.y + (ChrBtnsRect[slot].size.height / 2);
|
|
SetCursorPos(x, y);
|
|
}
|
|
|
|
Point InvGetEquipSlotCoord(const inv_body_loc inv_slot)
|
|
{
|
|
Point result { RIGHT_PANEL, 0 };
|
|
result.x -= (icursW28 - 1) * (InventorySlotSizeInPixels.width / 2);
|
|
switch (inv_slot) {
|
|
case INVLOC_HEAD:
|
|
result.x += ((InvRect[SLOTXY_HEAD_FIRST].x + InvRect[SLOTXY_HEAD_LAST].x) / 2);
|
|
result.y += ((InvRect[SLOTXY_HEAD_FIRST].y + InvRect[SLOTXY_HEAD_LAST].y) / 2);
|
|
break;
|
|
case INVLOC_RING_LEFT:
|
|
result.x += InvRect[SLOTXY_RING_LEFT].x;
|
|
result.y += InvRect[SLOTXY_RING_LEFT].y;
|
|
break;
|
|
case INVLOC_RING_RIGHT:
|
|
result.x += InvRect[SLOTXY_RING_RIGHT].x;
|
|
result.y += InvRect[SLOTXY_RING_RIGHT].y;
|
|
break;
|
|
case INVLOC_AMULET:
|
|
result.x += InvRect[SLOTXY_AMULET].x;
|
|
result.y += InvRect[SLOTXY_AMULET].y;
|
|
break;
|
|
case INVLOC_HAND_LEFT:
|
|
result.x += ((InvRect[SLOTXY_HAND_LEFT_FIRST].x + InvRect[SLOTXY_HAND_LEFT_LAST].x) / 2);
|
|
result.y += ((InvRect[SLOTXY_HAND_LEFT_FIRST].y + InvRect[SLOTXY_HAND_LEFT_LAST].y) / 2);
|
|
break;
|
|
case INVLOC_HAND_RIGHT:
|
|
result.x += ((InvRect[SLOTXY_HAND_RIGHT_FIRST].x + InvRect[SLOTXY_HAND_RIGHT_LAST].x) / 2);
|
|
result.y += ((InvRect[SLOTXY_HAND_RIGHT_FIRST].y + InvRect[SLOTXY_HAND_RIGHT_LAST].y) / 2);
|
|
break;
|
|
case INVLOC_CHEST:
|
|
result.x += ((InvRect[SLOTXY_CHEST_FIRST].x + InvRect[SLOTXY_CHEST_LAST].x) / 2);
|
|
result.y += ((InvRect[SLOTXY_CHEST_FIRST].y + InvRect[SLOTXY_CHEST_LAST].y) / 2);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
Point InvGetEquipSlotCoordFromInvSlot(const inv_xy_slot slot)
|
|
{
|
|
switch (slot) {
|
|
case SLOTXY_HEAD_FIRST:
|
|
case SLOTXY_HEAD_LAST:
|
|
return InvGetEquipSlotCoord(INVLOC_HEAD);
|
|
case SLOTXY_RING_LEFT:
|
|
return InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
case SLOTXY_RING_RIGHT:
|
|
return InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
case SLOTXY_AMULET:
|
|
return InvGetEquipSlotCoord(INVLOC_AMULET);
|
|
case SLOTXY_HAND_LEFT_FIRST:
|
|
case SLOTXY_HAND_LEFT_LAST:
|
|
return InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
case SLOTXY_HAND_RIGHT_FIRST:
|
|
case SLOTXY_HAND_RIGHT_LAST:
|
|
return InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
case SLOTXY_CHEST_FIRST:
|
|
case SLOTXY_CHEST_LAST:
|
|
return InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get coordinates for a given inventory slot (for belt use BeltGetSlotCoord)
|
|
*/
|
|
Point InvGetSlotCoord(int slot)
|
|
{
|
|
assert(slot <= SLOTXY_INV_LAST);
|
|
return { InvRect[slot].x + RIGHT_PANEL, InvRect[slot].y };
|
|
}
|
|
|
|
/**
|
|
* Get coordinates for a given belt slot (for normal inventory use InvGetSlotCoord)
|
|
*/
|
|
Point BeltGetSlotCoord(int slot)
|
|
{
|
|
assert(slot >= SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST);
|
|
return { InvRect[slot].x + PANEL_LEFT, InvRect[slot].y + PANEL_TOP };
|
|
}
|
|
|
|
/**
|
|
* Get item size (grid size) on the slot specified. Returns 1x1 if none exists.
|
|
*/
|
|
Size GetItemSizeOnSlot(int slot, char &itemInvId)
|
|
{
|
|
if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
int ig = slot - SLOTXY_INV_FIRST;
|
|
auto &myPlayer = plr[myplr];
|
|
char ii = myPlayer.InvGrid[ig];
|
|
if (ii != 0) {
|
|
int iv = ii;
|
|
if (ii <= 0) {
|
|
iv = -ii;
|
|
}
|
|
|
|
ItemStruct &item = myPlayer.InvList[iv - 1];
|
|
if (!item.isEmpty()) {
|
|
auto size = GetInvItemSize(item._iCurs + CURSOR_FIRSTITEM);
|
|
size.width /= InventorySlotSizeInPixels.width;
|
|
size.height /= InventorySlotSizeInPixels.height;
|
|
|
|
itemInvId = ii;
|
|
return size;
|
|
}
|
|
}
|
|
}
|
|
itemInvId = 0;
|
|
return { 1, 1 };
|
|
}
|
|
|
|
/**
|
|
* Reset cursor position based on the current slot.
|
|
*/
|
|
void ResetInvCursorPosition()
|
|
{
|
|
Point mousePos {};
|
|
if (slot < SLOTXY_INV_FIRST) {
|
|
mousePos = InvGetEquipSlotCoordFromInvSlot((inv_xy_slot)slot);
|
|
} else if (slot < SLOTXY_BELT_FIRST) {
|
|
char itemInvId;
|
|
auto itemSize = GetItemSizeOnSlot(slot, itemInvId);
|
|
|
|
// search the 'first slot' for that item in the inventory, it should have the positive number of that same InvId
|
|
if (itemInvId < 0) {
|
|
for (int s = 0; s < SLOTXY_INV_LAST - SLOTXY_INV_FIRST; ++s) {
|
|
if (plr[myplr].InvGrid[s] == -itemInvId) {
|
|
slot = SLOTXY_INV_FIRST + s;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// offset the slot to always move to the top-left most slot of that item
|
|
slot -= ((itemSize.height - 1) * INV_ROW_SLOT_SIZE);
|
|
mousePos = InvGetSlotCoord(slot);
|
|
mousePos.x += ((itemSize.width - 1) * InventorySlotSizeInPixels.width) / 2;
|
|
mousePos.y += ((itemSize.height - 1) * InventorySlotSizeInPixels.height) / 2;
|
|
} else {
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
}
|
|
|
|
mousePos.x += (InventorySlotSizeInPixels.width / 2);
|
|
mousePos.y -= (InventorySlotSizeInPixels.height / 2);
|
|
|
|
SetCursorPos(mousePos.x, mousePos.y);
|
|
}
|
|
|
|
/**
|
|
* Move the cursor around in our inventory
|
|
* If mouse coords are at SLOTXY_CHEST_LAST, consider this center of equipment
|
|
* small inventory squares are 29x29 (roughly)
|
|
*/
|
|
void InvMove(AxisDirection dir)
|
|
{
|
|
static AxisDirectionRepeater repeater(/*min_interval_ms=*/150);
|
|
dir = repeater.Get(dir);
|
|
if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE)
|
|
return;
|
|
|
|
char itemInvId;
|
|
auto itemSize = GetItemSizeOnSlot(slot, itemInvId);
|
|
|
|
Point mousePos { MouseX, MouseY };
|
|
|
|
const bool isHoldingItem = pcurs > 1;
|
|
|
|
// normalize slots
|
|
if (slot < 0)
|
|
slot = 0;
|
|
else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST)
|
|
slot = SLOTXY_HEAD_FIRST;
|
|
else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST)
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST)
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST)
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
else if (slot > SLOTXY_BELT_LAST)
|
|
slot = SLOTXY_BELT_LAST;
|
|
|
|
const int initialSlot = slot;
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
// when item is on cursor (pcurs > 1), this is the real cursor XY
|
|
if (dir.x == AxisDirectionX_LEFT) {
|
|
if (isHoldingItem) {
|
|
if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
if (slot == SLOTXY_INV_ROW1_FIRST || slot == SLOTXY_INV_ROW2_FIRST || slot == SLOTXY_INV_ROW3_FIRST || slot == SLOTXY_INV_ROW4_FIRST) {
|
|
slot += INV_ROW_SLOT_SIZE - icursW28;
|
|
} else {
|
|
slot -= 1;
|
|
}
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot > SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) {
|
|
slot -= 1;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
} else if (myPlayer.HoldItem._itype == ITYPE_RING) {
|
|
slot = SLOTXY_RING_LEFT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
} else if (myPlayer.HoldItem.isWeapon() || myPlayer.HoldItem.isShield()) {
|
|
if (slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (slot == SLOTXY_HAND_RIGHT_FIRST) {
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
}
|
|
}
|
|
} else {
|
|
if (slot == SLOTXY_HAND_RIGHT_FIRST) {
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
} else if (slot == SLOTXY_CHEST_FIRST) {
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
} else if (slot == SLOTXY_AMULET) {
|
|
slot = SLOTXY_HEAD_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HEAD);
|
|
} else if (slot == SLOTXY_RING_RIGHT) {
|
|
slot = SLOTXY_RING_LEFT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
} else if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
if (slot == SLOTXY_INV_ROW1_FIRST || slot == SLOTXY_INV_ROW2_FIRST || slot == SLOTXY_INV_ROW3_FIRST || slot == SLOTXY_INV_ROW4_FIRST) {
|
|
slot += INV_ROW_SLOT_SIZE - 1;
|
|
} else {
|
|
slot -= 1;
|
|
}
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot > SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) {
|
|
slot -= 1;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
}
|
|
}
|
|
} else if (dir.x == AxisDirectionX_RIGHT) {
|
|
if (isHoldingItem) {
|
|
if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
if (
|
|
slot == SLOTXY_INV_ROW1_LAST + 1 - icursW28 || slot == SLOTXY_INV_ROW2_LAST + 1 - icursW28 || slot == SLOTXY_INV_ROW3_LAST + 1 - icursW28 || slot == SLOTXY_INV_ROW4_LAST + 1 - icursW28) {
|
|
slot -= INV_ROW_SLOT_SIZE - icursW28;
|
|
} else {
|
|
slot += 1;
|
|
}
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot >= SLOTXY_BELT_FIRST && slot < SLOTXY_BELT_LAST) {
|
|
slot += 1;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
} else if (myPlayer.HoldItem._itype == ITYPE_RING) {
|
|
slot = SLOTXY_RING_RIGHT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
} else if (myPlayer.HoldItem.isWeapon() || myPlayer.HoldItem.isShield()) {
|
|
if (slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (slot == SLOTXY_HAND_RIGHT_FIRST) {
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
}
|
|
}
|
|
} else {
|
|
if (slot == SLOTXY_RING_LEFT) {
|
|
slot = SLOTXY_RING_RIGHT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
} else if (slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
} else if (slot == SLOTXY_CHEST_FIRST) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (slot == SLOTXY_HEAD_FIRST) {
|
|
slot = SLOTXY_AMULET;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_AMULET);
|
|
} else if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
if (
|
|
slot == SLOTXY_INV_ROW1_LAST + 1 - itemSize.width || slot == SLOTXY_INV_ROW2_LAST + 1 - itemSize.width || slot == SLOTXY_INV_ROW3_LAST + 1 - itemSize.width || slot == SLOTXY_INV_ROW4_LAST + 1 - itemSize.width) {
|
|
slot -= INV_ROW_SLOT_SIZE - itemSize.width;
|
|
} else {
|
|
slot += itemSize.width;
|
|
}
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot >= SLOTXY_BELT_FIRST && slot < SLOTXY_BELT_LAST) {
|
|
slot += 1;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
}
|
|
}
|
|
}
|
|
if (dir.y == AxisDirectionY_UP) {
|
|
if (isHoldingItem) {
|
|
if (slot >= SLOTXY_INV_ROW2_FIRST) { // general inventory
|
|
slot -= INV_ROW_SLOT_SIZE;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot >= SLOTXY_INV_FIRST) {
|
|
if (myPlayer.HoldItem._itype == ITYPE_RING) {
|
|
if (slot >= SLOTXY_INV_ROW1_FIRST && slot <= SLOTXY_INV_ROW1_FIRST + (INV_ROW_SLOT_SIZE / 2) - 1) {
|
|
slot = SLOTXY_RING_LEFT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
} else {
|
|
slot = SLOTXY_RING_RIGHT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
}
|
|
} else if (myPlayer.HoldItem.isWeapon()) {
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
} else if (myPlayer.HoldItem.isShield()) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (myPlayer.HoldItem.isHelm()) {
|
|
slot = SLOTXY_HEAD_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HEAD);
|
|
} else if (myPlayer.HoldItem.isArmor()) {
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
} else if (myPlayer.HoldItem._itype == ITYPE_AMULET) {
|
|
slot = SLOTXY_AMULET;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_AMULET);
|
|
}
|
|
}
|
|
} else {
|
|
if (slot >= SLOTXY_INV_ROW1_FIRST && slot < SLOTXY_INV_ROW1_FIRST + 3) { // first 3 general slots
|
|
slot = SLOTXY_RING_LEFT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
} else if (slot >= SLOTXY_INV_ROW1_FIRST + 3 && slot < SLOTXY_INV_ROW1_FIRST + 7) { // middle 4 general slots
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
} else if (slot >= SLOTXY_INV_ROW1_FIRST + 7 && slot < SLOTXY_INV_ROW1_LAST) { // last 3 general slots
|
|
slot = SLOTXY_RING_RIGHT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
} else if (slot == SLOTXY_CHEST_FIRST || slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_HEAD_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HEAD);
|
|
} else if (slot == SLOTXY_RING_LEFT) {
|
|
slot = SLOTXY_HAND_LEFT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_LEFT);
|
|
} else if (slot == SLOTXY_RING_RIGHT) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (slot == SLOTXY_HAND_RIGHT_FIRST) {
|
|
slot = SLOTXY_AMULET;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_AMULET);
|
|
} else if (slot >= SLOTXY_INV_ROW2_FIRST) {
|
|
slot -= INV_ROW_SLOT_SIZE;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
}
|
|
}
|
|
} else if (dir.y == AxisDirectionY_DOWN) {
|
|
if (isHoldingItem) {
|
|
if (slot == SLOTXY_HEAD_FIRST || slot == SLOTXY_CHEST_FIRST) {
|
|
slot = SLOTXY_INV_ROW1_FIRST + 4;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot == SLOTXY_RING_LEFT || slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_INV_ROW1_FIRST + 1;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot == SLOTXY_RING_RIGHT || slot == SLOTXY_HAND_RIGHT_FIRST || slot == SLOTXY_AMULET) {
|
|
slot = SLOTXY_INV_ROW1_LAST - 1;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot <= (SLOTXY_INV_ROW4_LAST - (icursH28 * INV_ROW_SLOT_SIZE))) {
|
|
slot += INV_ROW_SLOT_SIZE;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot <= SLOTXY_INV_LAST && myPlayer.HoldItem._itype == ITYPE_MISC && icursW28 == 1 && icursH28 == 1) { // forcing only 1x1 misc items
|
|
slot += INV_ROW_SLOT_SIZE;
|
|
if (slot > SLOTXY_BELT_LAST)
|
|
slot = SLOTXY_BELT_LAST;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
}
|
|
} else {
|
|
if (slot == SLOTXY_HEAD_FIRST) {
|
|
slot = SLOTXY_CHEST_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_CHEST);
|
|
} else if (slot == SLOTXY_CHEST_FIRST) {
|
|
slot = SLOTXY_INV_ROW1_FIRST + (INV_ROW_SLOT_SIZE / 2);
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot == SLOTXY_HAND_LEFT_FIRST) {
|
|
slot = SLOTXY_RING_LEFT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_LEFT);
|
|
} else if (slot == SLOTXY_RING_LEFT) {
|
|
slot = SLOTXY_INV_ROW1_FIRST + 1;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot == SLOTXY_RING_RIGHT) {
|
|
slot = SLOTXY_INV_ROW1_LAST - 1;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot == SLOTXY_AMULET) {
|
|
slot = SLOTXY_HAND_RIGHT_FIRST;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_HAND_RIGHT);
|
|
} else if (slot == SLOTXY_HAND_RIGHT_FIRST) {
|
|
slot = SLOTXY_RING_RIGHT;
|
|
mousePos = InvGetEquipSlotCoord(INVLOC_RING_RIGHT);
|
|
} else if (slot <= (SLOTXY_INV_ROW4_LAST - (itemSize.height * INV_ROW_SLOT_SIZE))) {
|
|
slot += itemSize.height * INV_ROW_SLOT_SIZE;
|
|
mousePos = InvGetSlotCoord(slot);
|
|
} else if (slot <= SLOTXY_INV_LAST) {
|
|
slot += itemSize.height * INV_ROW_SLOT_SIZE;
|
|
if (slot > SLOTXY_BELT_LAST)
|
|
slot = SLOTXY_BELT_LAST;
|
|
mousePos = BeltGetSlotCoord(slot);
|
|
}
|
|
}
|
|
}
|
|
|
|
// no movement was made
|
|
if (slot == initialSlot)
|
|
return;
|
|
|
|
// get item under new slot if navigating on the inventory
|
|
if (!isHoldingItem && slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {
|
|
itemSize = GetItemSizeOnSlot(slot, itemInvId);
|
|
|
|
// search the 'first slot' for that item in the inventory, it should have the positive number of that same InvId
|
|
if (itemInvId < 0) {
|
|
for (int s = 0; s < SLOTXY_INV_LAST - SLOTXY_INV_FIRST; ++s) {
|
|
if (myPlayer.InvGrid[s] == -itemInvId) {
|
|
slot = SLOTXY_INV_FIRST + s;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// offset the slot to always move to the top-left most slot of that item
|
|
slot -= ((itemSize.height - 1) * INV_ROW_SLOT_SIZE);
|
|
mousePos = InvGetSlotCoord(slot);
|
|
mousePos.x += ((itemSize.width - 1) * InventorySlotSizeInPixels.width) / 2;
|
|
mousePos.y += ((itemSize.height - 1) * InventorySlotSizeInPixels.height) / 2;
|
|
}
|
|
|
|
// move cursor to the center of the slot if not holding anything or top left is holding an object
|
|
if (isHoldingItem) {
|
|
if (slot >= SLOTXY_INV_FIRST)
|
|
mousePos.y -= InventorySlotSizeInPixels.height;
|
|
else
|
|
mousePos.y -= (int)((icursH28 / 2.f) * InventorySlotSizeInPixels.height) + (InventorySlotSizeInPixels.height / 2);
|
|
} else {
|
|
mousePos.x += (InventorySlotSizeInPixels.width / 2);
|
|
mousePos.y -= (InventorySlotSizeInPixels.height / 2);
|
|
}
|
|
|
|
if (mousePos.x == MouseX && mousePos.y == MouseY) {
|
|
return; // Avoid wobeling when scalled
|
|
}
|
|
|
|
SetCursorPos(mousePos.x, mousePos.y);
|
|
}
|
|
|
|
/**
|
|
* check if hot spell at X Y exists
|
|
*/
|
|
bool HSExists(Point target)
|
|
{
|
|
for (int r = 0; r < speedspellcount; r++) {
|
|
if (target.x >= speedspellscoords[r].x - SPLICONLENGTH / 2
|
|
&& target.x < speedspellscoords[r].x + SPLICONLENGTH / 2
|
|
&& target.y >= speedspellscoords[r].y - SPLICONLENGTH / 2
|
|
&& target.y < speedspellscoords[r].y + SPLICONLENGTH / 2) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void HotSpellMove(AxisDirection dir)
|
|
{
|
|
static AxisDirectionRepeater repeater;
|
|
dir = repeater.Get(dir);
|
|
if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE)
|
|
return;
|
|
|
|
int spbslot = plr[myplr]._pRSpell;
|
|
for (int r = 0; r < speedspellcount; r++) {
|
|
if (MouseX >= speedspellscoords[r].x - SPLICONLENGTH / 2
|
|
&& MouseX < speedspellscoords[r].x + SPLICONLENGTH / 2
|
|
&& MouseY >= speedspellscoords[r].y - SPLICONLENGTH / 2
|
|
&& MouseY < speedspellscoords[r].y + SPLICONLENGTH / 2) {
|
|
spbslot = r;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int x = speedspellscoords[spbslot].x;
|
|
int y = speedspellscoords[spbslot].y;
|
|
|
|
if (dir.x == AxisDirectionX_LEFT) {
|
|
if (spbslot < speedspellcount - 1) {
|
|
x = speedspellscoords[spbslot + 1].x;
|
|
y = speedspellscoords[spbslot + 1].y;
|
|
}
|
|
} else if (dir.x == AxisDirectionX_RIGHT) {
|
|
if (spbslot > 0) {
|
|
x = speedspellscoords[spbslot - 1].x;
|
|
y = speedspellscoords[spbslot - 1].y;
|
|
}
|
|
}
|
|
|
|
if (dir.y == AxisDirectionY_UP) {
|
|
if (HSExists({ x, y - SPLICONLENGTH })) {
|
|
y -= SPLICONLENGTH;
|
|
}
|
|
} else if (dir.y == AxisDirectionY_DOWN) {
|
|
if (HSExists({ x, y + SPLICONLENGTH })) {
|
|
y += SPLICONLENGTH;
|
|
}
|
|
}
|
|
|
|
if (x != MouseX || y != MouseY) {
|
|
SetCursorPos(x, y);
|
|
}
|
|
}
|
|
|
|
void SpellBookMove(AxisDirection dir)
|
|
{
|
|
static AxisDirectionRepeater repeater;
|
|
dir = repeater.Get(dir);
|
|
|
|
if (dir.x == AxisDirectionX_LEFT) {
|
|
if (sbooktab > 0)
|
|
sbooktab--;
|
|
} else if (dir.x == AxisDirectionX_RIGHT) {
|
|
if ((gbIsHellfire && sbooktab < 4) || (!gbIsHellfire && sbooktab < 3))
|
|
sbooktab++;
|
|
}
|
|
}
|
|
|
|
static const Direction FaceDir[3][3] = {
|
|
// NONE UP DOWN
|
|
{ DIR_OMNI, DIR_N, DIR_S }, // NONE
|
|
{ DIR_W, DIR_NW, DIR_SW }, // LEFT
|
|
{ DIR_E, DIR_NE, DIR_SE }, // RIGHT
|
|
};
|
|
|
|
/**
|
|
* @brief check if stepping in direction (dir) from position is blocked.
|
|
*
|
|
* If you step from A to B, at leat one of the Xs need to be clear:
|
|
*
|
|
* AX
|
|
* XB
|
|
*
|
|
* @return true if step is blocked
|
|
*/
|
|
bool IsPathBlocked(Point position, Direction dir)
|
|
{
|
|
Direction d1, d2;
|
|
switch (dir) {
|
|
case DIR_N:
|
|
d1 = DIR_NW;
|
|
d2 = DIR_NE;
|
|
break;
|
|
case DIR_E:
|
|
d1 = DIR_NE;
|
|
d2 = DIR_SE;
|
|
break;
|
|
case DIR_S:
|
|
d1 = DIR_SE;
|
|
d2 = DIR_SW;
|
|
break;
|
|
case DIR_W:
|
|
d1 = DIR_SW;
|
|
d2 = DIR_NW;
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
auto leftStep { position + d1 };
|
|
auto rightStep { position + d2 };
|
|
|
|
if (!nSolidTable[dPiece[leftStep.x][leftStep.y]] && !nSolidTable[dPiece[rightStep.x][rightStep.y]])
|
|
return false;
|
|
|
|
return !PosOkPlayer(myplr, leftStep) && !PosOkPlayer(myplr, rightStep);
|
|
}
|
|
|
|
bool CanChangeDirection(const PlayerStruct &player)
|
|
{
|
|
if (player._pmode == PM_STAND)
|
|
return true;
|
|
if (player._pmode == PM_ATTACK && player.AnimInfo.CurrentFrame > player._pAFNum)
|
|
return true;
|
|
if (player._pmode == PM_RATTACK && player.AnimInfo.CurrentFrame > player._pAFNum)
|
|
return true;
|
|
if (player._pmode == PM_SPELL && player.AnimInfo.CurrentFrame > player._pSFNum)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void WalkInDir(int playerId, AxisDirection dir)
|
|
{
|
|
auto &player = plr[playerId];
|
|
|
|
if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE) {
|
|
if (sgbControllerActive && player.walkpath[0] != WALK_NONE && player.destAction == ACTION_NONE)
|
|
NetSendCmdLoc(playerId, true, CMD_WALKXY, player.position.future); // Stop walking
|
|
return;
|
|
}
|
|
|
|
const Direction pdir = FaceDir[static_cast<std::size_t>(dir.x)][static_cast<std::size_t>(dir.y)];
|
|
const auto delta = player.position.future + pdir;
|
|
|
|
if (CanChangeDirection(player))
|
|
player._pdir = pdir;
|
|
|
|
if (PosOkPlayer(playerId, delta) && IsPathBlocked(player.position.future, pdir))
|
|
return; // Don't start backtrack around obstacles
|
|
|
|
NetSendCmdLoc(playerId, true, CMD_WALKXY, delta);
|
|
}
|
|
|
|
void QuestLogMove(AxisDirection moveDir)
|
|
{
|
|
static AxisDirectionRepeater repeater;
|
|
moveDir = repeater.Get(moveDir);
|
|
if (moveDir.y == AxisDirectionY_UP)
|
|
QuestlogUp();
|
|
else if (moveDir.y == AxisDirectionY_DOWN)
|
|
QuestlogDown();
|
|
}
|
|
|
|
void StoreMove(AxisDirection moveDir)
|
|
{
|
|
static AxisDirectionRepeater repeater;
|
|
moveDir = repeater.Get(moveDir);
|
|
if (moveDir.y == AxisDirectionY_UP)
|
|
STextUp();
|
|
else if (moveDir.y == AxisDirectionY_DOWN)
|
|
STextDown();
|
|
}
|
|
|
|
using HandleLeftStickOrDPadFn = void (*)(devilution::AxisDirection);
|
|
|
|
HandleLeftStickOrDPadFn GetLeftStickOrDPadGameUIHandler()
|
|
{
|
|
if (invflag) {
|
|
return &InvMove;
|
|
}
|
|
if (chrflag && plr[myplr]._pStatPts > 0) {
|
|
return &AttrIncBtnSnap;
|
|
} else if (spselflag) {
|
|
return &HotSpellMove;
|
|
} else if (sbookflag) {
|
|
return &SpellBookMove;
|
|
} else if (questlog) {
|
|
return &QuestLogMove;
|
|
} else if (stextflag != STORE_NONE) {
|
|
return &StoreMove;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void ProcessLeftStickOrDPadGameUI()
|
|
{
|
|
HandleLeftStickOrDPadFn handler = GetLeftStickOrDPadGameUIHandler();
|
|
if (handler != nullptr)
|
|
handler(GetLeftStickOrDpadDirection(true));
|
|
}
|
|
|
|
void Movement(int playerId)
|
|
{
|
|
if (InGameMenu()
|
|
|| IsControllerButtonPressed(ControllerButton_BUTTON_START)
|
|
|| IsControllerButtonPressed(ControllerButton_BUTTON_BACK))
|
|
return;
|
|
|
|
AxisDirection moveDir = GetMoveDirection();
|
|
if (moveDir.x != AxisDirectionX_NONE || moveDir.y != AxisDirectionY_NONE) {
|
|
sgbControllerActive = true;
|
|
}
|
|
|
|
if (GetLeftStickOrDPadGameUIHandler() == nullptr) {
|
|
WalkInDir(playerId, moveDir);
|
|
}
|
|
}
|
|
|
|
struct RightStickAccumulator {
|
|
|
|
RightStickAccumulator()
|
|
{
|
|
lastTc = SDL_GetTicks();
|
|
hiresDX = 0;
|
|
hiresDY = 0;
|
|
}
|
|
|
|
void Pool(int *x, int *y, int slowdown)
|
|
{
|
|
const Uint32 tc = SDL_GetTicks();
|
|
const int dtc = tc - lastTc;
|
|
hiresDX += rightStickX * dtc;
|
|
hiresDY += rightStickY * dtc;
|
|
const int dx = hiresDX / slowdown;
|
|
const int dy = hiresDY / slowdown;
|
|
*x += dx;
|
|
*y -= dy;
|
|
lastTc = tc;
|
|
// keep track of remainder for sub-pixel motion
|
|
hiresDX -= dx * slowdown;
|
|
hiresDY -= dy * slowdown;
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
lastTc = SDL_GetTicks();
|
|
}
|
|
|
|
DWORD lastTc;
|
|
float hiresDX;
|
|
float hiresDY;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
void StoreSpellCoords()
|
|
{
|
|
const int startX = PANEL_LEFT + 12 + SPLICONLENGTH / 2;
|
|
const int endX = startX + SPLICONLENGTH * 10;
|
|
const int endY = PANEL_TOP - 17 - SPLICONLENGTH / 2;
|
|
speedspellcount = 0;
|
|
int xo = endX;
|
|
int yo = endY;
|
|
for (int i = RSPLTYPE_SKILL; i <= RSPLTYPE_CHARGES; i++) {
|
|
std::uint64_t spells;
|
|
auto &myPlayer = plr[myplr];
|
|
switch (i) {
|
|
case RSPLTYPE_SKILL:
|
|
spells = myPlayer._pAblSpells;
|
|
break;
|
|
case RSPLTYPE_SPELL:
|
|
spells = myPlayer._pMemSpells;
|
|
break;
|
|
case RSPLTYPE_SCROLL:
|
|
spells = myPlayer._pScrlSpells;
|
|
break;
|
|
case RSPLTYPE_CHARGES:
|
|
spells = myPlayer._pISpells;
|
|
break;
|
|
}
|
|
std::uint64_t spell = 1;
|
|
for (int j = 1; j < MAX_SPELLS; j++) {
|
|
if ((spell & spells) != 0) {
|
|
speedspellscoords[speedspellcount] = { xo, yo };
|
|
++speedspellcount;
|
|
xo -= SPLICONLENGTH;
|
|
if (xo < startX) {
|
|
xo = endX;
|
|
yo -= SPLICONLENGTH;
|
|
}
|
|
}
|
|
spell <<= 1;
|
|
}
|
|
if (spells != 0 && xo != endX)
|
|
xo -= SPLICONLENGTH;
|
|
if (xo < startX) {
|
|
xo = endX;
|
|
yo -= SPLICONLENGTH;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool IsAutomapActive()
|
|
{
|
|
return AutomapActive && leveltype != DTYPE_TOWN;
|
|
}
|
|
|
|
bool IsMovingMouseCursorWithController()
|
|
{
|
|
return rightStickX != 0 || rightStickY != 0;
|
|
}
|
|
|
|
void HandleRightStickMotion()
|
|
{
|
|
static RightStickAccumulator acc;
|
|
// deadzone is handled in ScaleJoystickAxes() already
|
|
if (rightStickX == 0 && rightStickY == 0) {
|
|
acc.Clear();
|
|
return;
|
|
}
|
|
|
|
if (IsAutomapActive()) { // move map
|
|
int dx = 0, dy = 0;
|
|
acc.Pool(&dx, &dy, 32);
|
|
AutomapOffset.x += dy + dx;
|
|
AutomapOffset.y += dy - dx;
|
|
return;
|
|
}
|
|
|
|
{ // move cursor
|
|
sgbControllerActive = false;
|
|
int x = MouseX;
|
|
int y = MouseY;
|
|
acc.Pool(&x, &y, 2);
|
|
x = std::min(std::max(x, 0), gnScreenWidth - 1);
|
|
y = std::min(std::max(y, 0), gnScreenHeight - 1);
|
|
|
|
// We avoid calling `SetCursorPos` within the same SDL tick because
|
|
// that can cause all stick motion events to arrive before all
|
|
// cursor position events.
|
|
static int lastMouseSetTick = 0;
|
|
const int now = SDL_GetTicks();
|
|
if (now - lastMouseSetTick > 0) {
|
|
SetCursorPos(x, y);
|
|
lastMouseSetTick = now;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Moves the mouse to the first inventory slot.
|
|
*/
|
|
void FocusOnInventory()
|
|
{
|
|
slot = SLOTXY_INV_FIRST;
|
|
ResetInvCursorPosition();
|
|
}
|
|
|
|
void plrctrls_after_check_curs_move()
|
|
{
|
|
// check for monsters first, then items, then towners.
|
|
if (sgbControllerActive) {
|
|
// Clear focuse set by cursor
|
|
pcursplr = -1;
|
|
pcursmonst = -1;
|
|
pcursitem = -1;
|
|
pcursobj = -1;
|
|
pcursmissile = -1;
|
|
pcurstrig = -1;
|
|
pcursquest = -1;
|
|
cursmx = -1;
|
|
cursmy = -1;
|
|
if (plr[myplr]._pInvincible) {
|
|
return;
|
|
}
|
|
if (DoomFlag) {
|
|
return;
|
|
}
|
|
if (!invflag) {
|
|
*infostr = '\0';
|
|
ClearPanel();
|
|
FindActor();
|
|
FindItemOrObject();
|
|
FindTrigger();
|
|
}
|
|
}
|
|
}
|
|
|
|
void plrctrls_every_frame()
|
|
{
|
|
ProcessLeftStickOrDPadGameUI();
|
|
HandleRightStickMotion();
|
|
}
|
|
|
|
void plrctrls_after_game_logic()
|
|
{
|
|
Movement(myplr);
|
|
}
|
|
|
|
void UseBeltItem(int type)
|
|
{
|
|
for (int i = 0; i < MAXBELTITEMS; i++) {
|
|
auto &myPlayer = plr[myplr];
|
|
const int id = AllItemsList[myPlayer.SpdList[i].IDidx].iMiscId;
|
|
const int spellId = AllItemsList[myPlayer.SpdList[i].IDidx].iSpell;
|
|
if ((type == BLT_HEALING && (id == IMISC_HEAL || id == IMISC_FULLHEAL || (id == IMISC_SCROLL && spellId == SPL_HEAL)))
|
|
|| (type == BLT_MANA && (id == IMISC_MANA || id == IMISC_FULLMANA))
|
|
|| id == IMISC_REJUV || id == IMISC_FULLREJUV) {
|
|
if (myPlayer.SpdList[i]._itype > -1) {
|
|
UseInvItem(myplr, INVITEM_BELT_FIRST + i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void PerformPrimaryAction()
|
|
{
|
|
if (invflag) { // inventory is open
|
|
if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
|
|
TryIconCurs();
|
|
NewCursor(CURSOR_HAND);
|
|
} else {
|
|
CheckInvItem();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (spselflag) {
|
|
SetSpell();
|
|
return;
|
|
}
|
|
|
|
if (chrflag && !chrbtnactive && plr[myplr]._pStatPts > 0) {
|
|
CheckChrBtns();
|
|
for (int i = 0; i < 4; i++) {
|
|
if (MouseX >= ChrBtnsRect[i].position.x
|
|
&& MouseX <= ChrBtnsRect[i].position.x + ChrBtnsRect[i].size.width
|
|
&& MouseY >= ChrBtnsRect[i].position.y
|
|
&& MouseY <= ChrBtnsRect[i].size.height + ChrBtnsRect[i].position.y) {
|
|
chrbtn[i] = true;
|
|
chrbtnactive = true;
|
|
ReleaseChrBtns(false);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
Interact();
|
|
}
|
|
|
|
bool SpellHasActorTarget()
|
|
{
|
|
int spl = plr[myplr]._pRSpell;
|
|
if (spl == SPL_TOWN || spl == SPL_TELEPORT)
|
|
return false;
|
|
|
|
if (spl == SPL_FIREWALL && pcursmonst != -1) {
|
|
cursmx = monster[pcursmonst].position.tile.x;
|
|
cursmy = monster[pcursmonst].position.tile.y;
|
|
}
|
|
|
|
return pcursplr != -1 || pcursmonst != -1;
|
|
}
|
|
|
|
void UpdateSpellTarget()
|
|
{
|
|
if (SpellHasActorTarget())
|
|
return;
|
|
|
|
pcursplr = -1;
|
|
pcursmonst = -1;
|
|
|
|
int range = 1;
|
|
if (plr[myplr]._pRSpell == SPL_TELEPORT)
|
|
range = 4;
|
|
|
|
auto cursm = plr[myplr].position.future + Point::fromDirection(plr[myplr]._pdir) * range;
|
|
cursmx = cursm.x;
|
|
cursmy = cursm.y;
|
|
}
|
|
|
|
/**
|
|
* @brief Try dropping item in all 9 possible places
|
|
*/
|
|
bool TryDropItem()
|
|
{
|
|
const auto &myPlayer = plr[myplr];
|
|
|
|
cursmx = myPlayer.position.future.x + 1;
|
|
cursmy = myPlayer.position.future.y;
|
|
if (!DropItemBeforeTrig()) {
|
|
// Try to drop on the other side
|
|
cursmx = myPlayer.position.future.x;
|
|
cursmy = myPlayer.position.future.y + 1;
|
|
DropItemBeforeTrig();
|
|
}
|
|
|
|
return pcurs == CURSOR_HAND;
|
|
}
|
|
|
|
void PerformSpellAction()
|
|
{
|
|
if (InGameMenu() || questlog || sbookflag)
|
|
return;
|
|
|
|
if (invflag) {
|
|
if (pcurs >= CURSOR_FIRSTITEM)
|
|
TryDropItem();
|
|
else if (pcurs > CURSOR_HAND) {
|
|
TryIconCurs();
|
|
NewCursor(CURSOR_HAND);
|
|
} else {
|
|
CheckInvItem(true);
|
|
ResetInvCursorPosition();
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
|
|
return;
|
|
if (pcurs > CURSOR_HAND)
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
if (spselflag) {
|
|
SetSpell();
|
|
return;
|
|
}
|
|
|
|
const auto &myPlayer = plr[myplr];
|
|
int spl = myPlayer._pRSpell;
|
|
if ((pcursplr == -1 && (spl == SPL_RESURRECT || spl == SPL_HEALOTHER))
|
|
|| (pcursobj == -1 && spl == SPL_DISARM)) {
|
|
myPlayer.Say(HeroSpeech::ICantCastThatHere);
|
|
return;
|
|
}
|
|
|
|
UpdateSpellTarget();
|
|
CheckPlrSpell();
|
|
}
|
|
|
|
void CtrlUseInvItem()
|
|
{
|
|
ItemStruct *item;
|
|
|
|
if (pcursinvitem == -1)
|
|
return;
|
|
|
|
auto &myPlayer = plr[myplr];
|
|
|
|
if (pcursinvitem <= INVITEM_INV_LAST)
|
|
item = &myPlayer.InvList[pcursinvitem - INVITEM_INV_FIRST];
|
|
else
|
|
item = &myPlayer.SpdList[pcursinvitem - INVITEM_BELT_FIRST];
|
|
|
|
if ((item->_iMiscId == IMISC_SCROLLT || item->_iMiscId == IMISC_SCROLL) && spelldata[item->_iSpell].sTargeted) {
|
|
return;
|
|
}
|
|
|
|
if (item->isEquipment()) {
|
|
CheckInvItem(true); // auto-equip if it's an equipment
|
|
ResetInvCursorPosition();
|
|
} else {
|
|
UseInvItem(myplr, pcursinvitem);
|
|
}
|
|
}
|
|
|
|
void PerformSecondaryAction()
|
|
{
|
|
if (invflag) {
|
|
CtrlUseInvItem();
|
|
return;
|
|
}
|
|
|
|
if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
|
|
return;
|
|
if (pcurs > CURSOR_HAND)
|
|
NewCursor(CURSOR_HAND);
|
|
|
|
if (pcursitem != -1) {
|
|
NetSendCmdLocParam1(true, CMD_GOTOAGETITEM, { cursmx, cursmy }, pcursitem);
|
|
} else if (pcursobj != -1) {
|
|
NetSendCmdLocParam1(true, CMD_OPOBJXY, { cursmx, cursmy }, pcursobj);
|
|
} else if (pcursmissile != -1) {
|
|
MakePlrPath(myplr, missile[pcursmissile].position.tile, true);
|
|
plr[myplr].destAction = ACTION_WALK;
|
|
} else if (pcurstrig != -1) {
|
|
MakePlrPath(myplr, trigs[pcurstrig].position, true);
|
|
plr[myplr].destAction = ACTION_WALK;
|
|
} else if (pcursquest != -1) {
|
|
MakePlrPath(myplr, quests[pcursquest].position, true);
|
|
plr[myplr].destAction = ACTION_WALK;
|
|
}
|
|
}
|
|
|
|
} // namespace devilution
|