You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1222 lines
33 KiB

#include "controls/plrctrls.h"
#include <cstdint>
#include <algorithm>
#include <list>
#include "controls/controller.h"
#include "controls/controller_motion.h"
#include "controls/game_controls.h"
#define SPLICONLENGTH 56
namespace dvl {
bool sgbControllerActive = false;
coords speedspellscoords[50];
const int repeatRate = 100;
int speedspellcount = 0;
/**
* Native game menu, controlled by simulating a keyboard.
*/
bool InGameMenu()
{
return stextflag > 0
|| helpflag
|| talkflag
|| qtextflag
|| gmenu_is_active()
|| PauseMode == 2
|| plr[myplr]._pInvincible;
}
namespace {
DWORD invmove = 0;
int slot = SLOTXY_INV_FIRST;
/**
* Number of angles to turn to face the coordinate
* @param x Tile coordinates
* @param y Tile coordinates
* @return -1 == down
*/
int GetRotaryDistance(int x, int y)
{
int d, d1, d2;
if (plr[myplr]._pfutx == x && plr[myplr]._pfuty == y)
return -1;
d1 = plr[myplr]._pdir;
d2 = GetDirection(plr[myplr]._pfutx, plr[myplr]._pfuty, x, y);
d = abs(d1 - d2);
if (d > 4)
return 4 - (d % 4);
return d;
}
/**
* @brief Get the best case walking steps to coordinates
* @param dx Tile coordinates
* @param dy Tile coordinates
*/
int GetMinDistance(int dx, int dy)
{
return std::max(abs(plr[myplr]._pfutx - dx), abs(plr[myplr]._pfuty - dy));
}
/**
* @brief Get walking steps to coordinate
* @param dx Tile coordinates
* @param dy Tile coordinates
* @param maxDistance the max number of steps to search
* @return number of steps, or 0 if not reachable
*/
int GetDistance(int dx, int dy, int maxDistance)
{
if (GetMinDistance(dx, dy) > maxDistance) {
return 0;
}
char walkpath[MAX_PATH_LENGTH];
int steps = FindPath(PosOkPlayer, myplr, plr[myplr]._pfutx, plr[myplr]._pfuty, dx, dy, walkpath);
if (steps > maxDistance)
return 0;
return steps;
}
/**
* @brief Get distance to coordinate
* @param dx Tile coordinates
* @param dy Tile coordinates
*/
int GetDistanceRanged(int dx, int dy)
{
int a = plr[myplr]._pfutx - dx;
int b = plr[myplr]._pfuty - dy;
return sqrt(a * a + b * b);
}
void FindItemOrObject()
{
int mx = plr[myplr]._pfutx;
int my = plr[myplr]._pfuty;
int rotations = 5;
// As the player can not stand on the edge fo 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 (item[i]._itype == ITYPE_NONE
|| item[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(towner[i]._tx, towner[i]._ty, 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))
return false;
if (monst._mhitpoints >> 6 <= 0) // dead
return false;
const int mx = monst._mx;
const int my = monst._my;
if (!(dFlags[mx][my] & BFLAG_LIT)) // not visable
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++) {
const MonsterStruct &monst = monster[mi];
const int mx = monst._mfutx;
const int my = monst._mfuty;
if (!CanTargetMonster(mi))
continue;
const bool newCanTalk = CanTalkToMonst(mi);
if (pcursmonst != -1 && !canTalk && newCanTalk)
continue;
const int newDdistance = GetDistanceRanged(mx, my);
const int newRotations = GetRotaryDistance(mx, my);
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] = { { 0 } };
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 start_x = plr[myplr]._pfutx;
const int start_y = plr[myplr]._pfuty;
visited[start_x][start_y] = true;
queue.push_back({ start_x, start_y, 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.x = node.x;
pPath.y = 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;
int spl = plr[myplr]._pRSpell;
if (FriendlyMode && spl != SPL_RESURRECT && spl != SPL_HEALOTHER)
return;
for (int i = 0; i < MAX_PLRS; i++) {
if (i == myplr)
continue;
const int mx = plr[i]._pfutx;
const int my = plr[i]._pfuty;
if (dPlayer[mx][my] == 0
|| !(dFlags[mx][my] & BFLAG_LIT)
|| (plr[i]._pHitPoints == 0 && spl != SPL_RESURRECT))
continue;
if (plr[myplr]._pwtype == WT_RANGED || HasRangedSpell() || spl == SPL_HEALOTHER) {
newDdistance = GetDistanceRanged(mx, my);
} else {
newDdistance = GetDistance(mx, my, distance);
if (newDdistance == 0)
continue;
}
if (pcursplr != -1 && distance < newDdistance)
continue;
const int newRotations = GetRotaryDistance(mx, my);
if (pcursplr != -1 && distance == newDdistance && rotations < newRotations)
continue;
distance = newDdistance;
rotations = newRotations;
pcursplr = i;
}
}
void FindActor()
{
if (leveltype != DTYPE_TOWN)
CheckMonstersNearby();
else
CheckTownersNearby();
if (gbMaxPlayers != 1)
CheckPlayerNearby();
}
int pcursmissile;
int pcurstrig;
int pcursquest;
void FindTrigger()
{
int rotations;
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) {
int mix = missile[mi]._mix;
int miy = missile[mi]._miy;
const int newDdistance = GetDistance(mix, miy, 2);
if (newDdistance == 0)
continue;
if (pcursmissile != -1 && distance < newDdistance)
continue;
const int newRotations = GetRotaryDistance(mix, miy);
if (pcursmissile != -1 && distance == newDdistance && rotations < newRotations)
continue;
cursmx = mix;
cursmy = miy;
pcursmissile = mi;
distance = newDdistance;
rotations = newRotations;
}
}
if (pcursmissile == -1) {
for (int i = 0; i < numtrigs; i++) {
int tx = trigs[i]._tx;
int ty = trigs[i]._ty;
if (trigs[i]._tlvl == 13)
ty -= 1;
const int newDdistance = GetDistance(tx, ty, 2);
if (newDdistance == 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 newDdistance = GetDistance(quests[i]._qtx, quests[i]._qty, 2);
if (newDdistance == 0)
continue;
cursmx = quests[i]._qtx;
cursmy = quests[i]._qty;
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, towner[pcursmonst]._tx, towner[pcursmonst]._ty, 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 && !FriendlyMode) {
NetSendCmdParam1(true, plr[myplr]._pwtype == WT_RANGED ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr);
}
}
void AttrIncBtnSnap(MoveDirectionY dir)
{
if (dir == MoveDirectionY_NONE) {
invmove = 0;
return;
}
if (chrbtnactive && plr[myplr]._pStatPts <= 0)
return;
DWORD ticks = SDL_GetTicks();
if (ticks - invmove < repeatRate) {
return;
}
invmove = ticks;
// first, find our cursor location
int slot = 0;
for (int i = 0; i < 4; i++) {
if (MouseX >= ChrBtnsRect[i].x
&& MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
&& MouseY >= ChrBtnsRect[i].y
&& MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
slot = i;
break;
}
}
if (dir == MoveDirectionY_UP) {
if (slot > 0)
--slot;
} else if (dir == MoveDirectionY_DOWN) {
if (slot < 3)
++slot;
}
// move cursor to our new location
int x = ChrBtnsRect[slot].x + (ChrBtnsRect[slot].w / 2);
int y = ChrBtnsRect[slot].y + (ChrBtnsRect[slot].h / 2);
SetCursorPos(x, 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(MoveDirection dir)
{
if (dir.x == MoveDirectionX_NONE && dir.y == MoveDirectionY_NONE) {
invmove = 0;
return;
}
DWORD ticks = SDL_GetTicks();
if (ticks - invmove < repeatRate) {
return;
}
invmove = ticks;
int x = MouseX;
int y = MouseY;
// check which inventory rectangle the mouse is in, if any
for (int r = 0; (DWORD)r < NUM_XY_SLOTS; r++) {
int xo = RIGHT_PANEL;
int yo = 0;
if (r >= SLOTXY_BELT_FIRST) {
xo = PANEL_LEFT;
yo = PANEL_TOP;
}
if (x >= InvRect[r].X + xo && x < InvRect[r].X + xo + (INV_SLOT_SIZE_PX + 1) && y >= InvRect[r].Y + yo - (INV_SLOT_SIZE_PX + 1) && y < InvRect[r].Y + yo) {
slot = r;
break;
}
}
if (slot < 0)
slot = 0;
if (slot > SLOTXY_BELT_LAST)
slot = SLOTXY_BELT_LAST;
// when item is on cursor, this is the real cursor XY
if (dir.x == MoveDirectionX_LEFT) {
if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) {
x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
x = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_AMULET) {
x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_RING_RIGHT) {
x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_BELT_FIRST) {
// do nothing
} else if (slot == SLOTXY_RING_LEFT) { // left ring
// do nothing
} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) { // left hand
// do nothing
} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) { // head
// do nothing
} else if (slot > SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) { // general inventory
if (slot != SLOTXY_INV_FIRST && slot != 35 && slot != 45 && slot != 55) { // left bounds
slot -= 1;
x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
}
} else if (slot > SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) { // belt
slot -= 1;
x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
}
} else if (dir.x == MoveDirectionX_RIGHT) {
if (slot == SLOTXY_RING_LEFT) {
x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) {
x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) { // head to amulet
x = InvRect[SLOTXY_AMULET].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_AMULET].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) { // right hand
// do nothing
} else if (slot == SLOTXY_AMULET) {
// do nothing
} else if (slot == SLOTXY_RING_RIGHT) {
// do nothing
} else if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) { // general inventory
if (slot != 34 && slot != 44 && slot != 54 && slot != SLOTXY_INV_LAST) { // right bounds
slot += 1;
x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
}
} else if (slot >= SLOTXY_BELT_FIRST && slot < SLOTXY_BELT_LAST) { // belt
slot += 1;
x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
}
}
if (dir.y == MoveDirectionY_UP) {
if (slot > 24 && slot <= 27) { // first 3 general slots
x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= 28 && slot <= 32) { // middle 4 general slots
x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= 33 && slot < 35) { // last 3 general slots
x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) { // chest to head
x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_RING_LEFT) { // left ring to left hand
x = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_RING_RIGHT) { // right ring to right hand
x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) { // right hand to amulet
x = InvRect[SLOTXY_AMULET].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_AMULET].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) {
// do nothing
} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) { // left hand to head
x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_AMULET) {
// do nothing
} else if (slot >= (SLOTXY_INV_FIRST + 10)) { // general inventory
slot -= 10;
x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
}
} else if (dir.y == MoveDirectionY_DOWN) {
if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) {
x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
x = InvRect[30].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[30].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) {
x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_RING_LEFT) {
x = InvRect[26].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[26].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_RING_RIGHT) {
x = InvRect[34].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[34].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot == SLOTXY_AMULET) {
x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) {
x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot <= (SLOTXY_INV_LAST - 10)) { // general inventory
slot += 10;
x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
} else if (slot <= (SLOTXY_BELT_LAST - 10)) { // general inventory
slot += 10;
x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
}
}
if (x == MouseX && y == MouseY) {
return; // Avoid wobeling when scalled
}
if (pcurs > 1) { // [3] Keep item in the same slot, don't jump it up
if (x != MouseX) { // without this, the cursor keeps moving -10
x -= 10;
y -= 10;
}
}
SetCursorPos(x, y);
}
/**
* check if hot spell at X Y exists
*/
bool HSExists(int x, int y)
{
for (int r = 0; r < speedspellcount; r++) {
if (x >= speedspellscoords[r].x - SPLICONLENGTH / 2
&& x < speedspellscoords[r].x + SPLICONLENGTH / 2
&& y >= speedspellscoords[r].y - SPLICONLENGTH / 2
&& y < speedspellscoords[r].y + SPLICONLENGTH / 2
) {
return true;
}
}
return false;
}
void HotSpellMove(MoveDirection dir)
{
if (dir.x == MoveDirectionX_NONE && dir.y == MoveDirectionY_NONE) {
invmove = 0;
return;
}
DWORD ticks = SDL_GetTicks();
if (ticks - invmove < repeatRate) {
return;
}
invmove = ticks;
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 == MoveDirectionX_LEFT) {
if (spbslot < speedspellcount - 1) {
x = speedspellscoords[spbslot + 1].x;
y = speedspellscoords[spbslot + 1].y;
}
} else if (dir.x == MoveDirectionX_RIGHT) {
if (spbslot > 0) {
x = speedspellscoords[spbslot - 1].x;
y = speedspellscoords[spbslot - 1].y;
}
}
if (dir.y == MoveDirectionY_UP) {
if (HSExists(x, y - SPLICONLENGTH)) {
y -= SPLICONLENGTH;
}
} else if (dir.y == MoveDirectionY_DOWN) {
if (HSExists(x, y + SPLICONLENGTH)) {
y += SPLICONLENGTH;
}
}
if (x != MouseX || y != MouseY) {
SetCursorPos(x, y);
}
}
void SpellBookMove(MoveDirection dir)
{
if (dir.x == MoveDirectionX_NONE && dir.y == MoveDirectionY_NONE) {
invmove = 0;
return;
}
DWORD ticks = SDL_GetTicks();
if (ticks - invmove < repeatRate) {
return;
}
invmove = ticks;
if (dir.x == MoveDirectionX_LEFT) {
if (sbooktab > 0)
sbooktab--;
} else if (dir.x == MoveDirectionX_RIGHT) {
if (sbooktab < 3)
sbooktab++;
}
}
static const direction kFaceDir[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
};
static const int kOffsets[8][2] = {
{ 1, 1 }, // DIR_S
{ 0, 1 }, // DIR_SW
{ -1, 1 }, // DIR_W
{ -1, 0 }, // DIR_NW
{ -1, -1 }, // DIR_N
{ 0, -1 }, // DIR_NE
{ 1, -1 }, // DIR_E
{ 1, 0 }, // DIR_SE
};
/**
* @brief check if stepping in direction (dir) from x, y 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(int x, int y, int dir)
{
int d1, d2, d1x, d1y, d2x, d2y;
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;
}
d1x = x + kOffsets[d1][0];
d1y = y + kOffsets[d1][1];
d2x = x + kOffsets[d2][0];
d2y = y + kOffsets[d2][1];
if (!nSolidTable[dPiece[d1x][d1y]] && !nSolidTable[dPiece[d2x][d2y]])
return false;
return !PosOkPlayer(myplr, d1x, d1y) && !PosOkPlayer(myplr, d2x, d2y);
}
void WalkInDir(MoveDirection dir)
{
const int x = plr[myplr]._pfutx;
const int y = plr[myplr]._pfuty;
if (dir.x == MoveDirectionX_NONE && dir.y == MoveDirectionY_NONE) {
if (sgbControllerActive && plr[myplr].walkpath[0] != WALK_NONE && plr[myplr].destAction == ACTION_NONE)
NetSendCmdLoc(true, CMD_WALKXY, x, y); // Stop walking
return;
}
const int pdir = kFaceDir[static_cast<std::size_t>(dir.x)][static_cast<std::size_t>(dir.y)];
const int dx = x + kOffsets[pdir][0];
const int dy = y + kOffsets[pdir][1];
plr[myplr]._pdir = pdir;
if (PosOkPlayer(myplr, dx, dy) && IsPathBlocked(x, y, pdir))
return; // Don't start backtrack around obstacles
NetSendCmdLoc(true, CMD_WALKXY, dx, dy);
}
void Movement()
{
if (InGameMenu() || questlog
|| IsControllerButtonPressed(ControllerButton_BUTTON_START)
|| IsControllerButtonPressed(ControllerButton_BUTTON_BACK))
return;
MoveDirection move_dir = GetMoveDirection();
if (move_dir.x != MoveDirectionX_NONE || move_dir.y != MoveDirectionY_NONE) {
sgbControllerActive = true;
}
if (invflag) {
InvMove(move_dir);
} else if (chrflag && plr[myplr]._pStatPts > 0) {
AttrIncBtnSnap(move_dir.y);
} else if (spselflag) {
HotSpellMove(move_dir);
} else if (sbookflag) {
SpellBookMove(move_dir);
} else {
WalkInDir(move_dir);
}
}
struct RightStickAccumulator {
RightStickAccumulator()
{
lastTc = SDL_GetTicks();
hiresDX = 0;
hiresDY = 0;
}
void pool(int *x, int *y, int slowdown)
{
DWORD tc = SDL_GetTicks();
hiresDX += rightStickX * (tc - lastTc);
hiresDY += rightStickY * (tc - lastTc);
*x += hiresDX / slowdown;
*y += -hiresDY / slowdown;
lastTc = tc;
// keep track of remainder for sub-pixel motion
hiresDX %= slowdown;
hiresDY %= slowdown;
}
void clear()
{
lastTc = SDL_GetTicks();
}
DWORD lastTc;
int hiresDX;
int hiresDY;
};
} // namespace
void StoreSpellCoords()
{
const int START_X = PANEL_LEFT + 12 + SPLICONLENGTH / 2;
const int END_X = START_X + SPLICONLENGTH * 10;
const int END_Y = PANEL_TOP - 17 - SPLICONLENGTH / 2;
speedspellcount = 0;
int xo = END_X;
int yo = END_Y;
for (int i = 0; i < 4; i++) {
std::uint64_t spells;
switch (i) {
case RSPLTYPE_SKILL:
spells = plr[myplr]._pAblSpells;
break;
case RSPLTYPE_SPELL:
spells = plr[myplr]._pMemSpells;
break;
case RSPLTYPE_SCROLL:
spells = plr[myplr]._pScrlSpells;
break;
case RSPLTYPE_CHARGES:
spells = plr[myplr]._pISpells;
break;
default:
continue;
}
std::uint64_t spell = 1;
for (int j = 1; j < MAX_SPELLS; j++) {
if ((spell & spells)) {
speedspellscoords[speedspellcount] = { xo, yo };
++speedspellcount;
xo -= SPLICONLENGTH;
if (xo < START_X) {
xo = END_X;
yo -= SPLICONLENGTH;
}
}
spell <<= 1;
}
if (spells && xo != END_X)
xo -= SPLICONLENGTH;
if (xo < START_X) {
xo = END_X;
yo -= SPLICONLENGTH;
}
}
}
bool IsAutomapActive()
{
return automapflag && leveltype != DTYPE_TOWN;
}
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);
AutoMapXOfs += dy + dx;
AutoMapYOfs += 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), SCREEN_WIDTH - 1);
y = std::min(std::max(y, 0), SCREEN_HEIGHT - 1);
SetCursorPos(x, y);
}
}
/**
* @brief Moves the mouse to the first inventory slot.
*/
void FocusOnInventory()
{
SetCursorPos(InvRect[25].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2), InvRect[25].Y - (INV_SLOT_SIZE_PX / 2));
}
void plrctrls_after_check_curs_move()
{
HandleRightStickMotion();
// 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 (!invflag) {
*infostr = '\0';
ClearPanel();
FindActor();
FindItemOrObject();
FindTrigger();
}
}
}
void plrctrls_after_game_logic()
{
Movement();
}
void UseBeltItem(int type)
{
for (int i = 0; i < MAXBELTITEMS; i++) {
const int id = AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId;
const int spellId = AllItemsList[plr[myplr].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 (plr[myplr].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();
SetCursor_(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].x
&& MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
&& MouseY >= ChrBtnsRect[i].y
&& MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
chrbtn[i] = 1;
chrbtnactive = true;
ReleaseChrBtns();
}
}
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]._mx;
cursmy = monster[pcursmonst]._my;
}
return pcursplr != -1 || pcursmonst != -1;
}
void UpdateSpellTarget()
{
if (SpellHasActorTarget())
return;
pcursplr = -1;
pcursmonst = -1;
const PlayerStruct &player = plr[myplr];
int range = 1;
if (plr[myplr]._pRSpell == SPL_TELEPORT)
range = 4;
cursmx = player._pfutx + kOffsets[player._pdir][0] * range;
cursmy = player._pfuty + kOffsets[player._pdir][1] * range;
}
/**
* @brief Try dropping item in all 9 possible places
*/
bool TryDropItem()
{
cursmx = plr[myplr]._pfutx + 1;
cursmy = plr[myplr]._pfuty;
if (!DropItemBeforeTrig()) {
// Try to drop on the other side
cursmx = plr[myplr]._pfutx;
cursmy = plr[myplr]._pfuty + 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();
SetCursor_(CURSOR_HAND);
}
return;
}
if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
return;
if (pcurs > CURSOR_HAND)
SetCursor_(CURSOR_HAND);
if (spselflag) {
SetSpell();
return;
}
int spl = plr[myplr]._pRSpell;
if ((pcursplr == -1 && (spl == SPL_RESURRECT || spl == SPL_HEALOTHER))
|| (pcursobj == -1 && spl == SPL_DISARM)) {
if (plr[myplr]._pClass == PC_WARRIOR) {
PlaySFX(PS_WARR27);
} else if (plr[myplr]._pClass == PC_ROGUE) {
PlaySFX(PS_ROGUE27);
} else if (plr[myplr]._pClass == PC_SORCERER) {
PlaySFX(PS_MAGE27);
}
return;
}
UpdateSpellTarget();
CheckPlrSpell();
}
void CtrlUseInvItem()
{
ItemStruct *Item;
if (pcursinvitem == -1)
return;
if (pcursinvitem <= INVITEM_INV_LAST)
Item = &plr[myplr].InvList[pcursinvitem - INVITEM_INV_FIRST];
else
Item = &plr[myplr].SpdList[pcursinvitem - INVITEM_BELT_FIRST];
if ((Item->_iMiscId == IMISC_SCROLLT || Item->_iMiscId == IMISC_SCROLL) && spelldata[Item->_iSpell].sTargeted) {
return;
}
UseInvItem(myplr, pcursinvitem);
}
void PerformSecondaryAction()
{
if (invflag) {
CtrlUseInvItem();
return;
}
if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
return;
if (pcurs > CURSOR_HAND)
SetCursor_(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]._mix, missile[pcursmissile]._miy, true);
plr[myplr].destAction = ACTION_WALK;
} else if (pcurstrig != -1) {
MakePlrPath(myplr, trigs[pcurstrig]._tx, trigs[pcurstrig]._ty, true);
plr[myplr].destAction = ACTION_WALK;
} else if (pcursquest != -1) {
MakePlrPath(myplr, quests[pcursquest]._qtx, quests[pcursquest]._qty, true);
plr[myplr].destAction = ACTION_WALK;
}
}
} // namespace dvl