1 #include "controls/plrctrls.h"
2 
3 #include <cstdint>
4 #include <algorithm>
5 #include <list>
6 
7 #include "controls/controller.h"
8 #include "controls/controller_motion.h"
9 #include "controls/game_controls.h"
10 
11 #define SPLICONLENGTH 56
12 
13 namespace dvl {
14 
15 bool sgbControllerActive = false;
16 coords speedspellscoords[50];
17 int speedspellcount = 0;
18 
19 /**
20  * Native game menu, controlled by simulating a keyboard.
21  */
InGameMenu()22 bool InGameMenu()
23 {
24 	return stextflag > 0
25 	    || helpflag
26 	    || talkflag
27 	    || qtextflag
28 	    || gmenu_is_active()
29 	    || PauseMode == 2
30 	    || plr[myplr]._pInvincible;
31 }
32 
33 namespace {
34 
35 int slot = SLOTXY_INV_FIRST;
36 
37 /**
38  * Number of angles to turn to face the coordinate
39  * @param x Tile coordinates
40  * @param y Tile coordinates
41  * @return -1 == down
42  */
GetRotaryDistance(int x,int y)43 int GetRotaryDistance(int x, int y)
44 {
45 	int d, d1, d2;
46 
47 	if (plr[myplr]._pfutx == x && plr[myplr]._pfuty == y)
48 		return -1;
49 
50 	d1 = plr[myplr]._pdir;
51 	d2 = GetDirection(plr[myplr]._pfutx, plr[myplr]._pfuty, x, y);
52 
53 	d = abs(d1 - d2);
54 	if (d > 4)
55 		return 4 - (d % 4);
56 
57 	return d;
58 }
59 
60 /**
61  * @brief Get the best case walking steps to coordinates
62  * @param dx Tile coordinates
63  * @param dy Tile coordinates
64  */
GetMinDistance(int dx,int dy)65 int GetMinDistance(int dx, int dy)
66 {
67 	return std::max(abs(plr[myplr]._pfutx - dx), abs(plr[myplr]._pfuty - dy));
68 }
69 
70 /**
71  * @brief Get walking steps to coordinate
72  * @param dx Tile coordinates
73  * @param dy Tile coordinates
74  * @param maxDistance the max number of steps to search
75  * @return number of steps, or 0 if not reachable
76  */
GetDistance(int dx,int dy,int maxDistance)77 int GetDistance(int dx, int dy, int maxDistance)
78 {
79 	if (GetMinDistance(dx, dy) > maxDistance) {
80 		return 0;
81 	}
82 
83 	Sint8 walkpath[MAX_PATH_LENGTH];
84 	int steps = FindPath(PosOkPlayer, myplr, plr[myplr]._pfutx, plr[myplr]._pfuty, dx, dy, walkpath);
85 	if (steps > maxDistance)
86 		return 0;
87 
88 	return steps;
89 }
90 
91 /**
92  * @brief Get distance to coordinate
93  * @param dx Tile coordinates
94  * @param dy Tile coordinates
95  */
GetDistanceRanged(int dx,int dy)96 int GetDistanceRanged(int dx, int dy)
97 {
98 	int a = plr[myplr]._pfutx - dx;
99 	int b = plr[myplr]._pfuty - dy;
100 
101 	return sqrt(a * a + b * b);
102 }
103 
FindItemOrObject()104 void FindItemOrObject()
105 {
106 	int mx = plr[myplr]._pfutx;
107 	int my = plr[myplr]._pfuty;
108 	int rotations = 5;
109 
110 	// As the player can not stand on the edge fo the map this is safe from OOB
111 	for (int xx = -1; xx < 2; xx++) {
112 		for (int yy = -1; yy < 2; yy++) {
113 			if (dItem[mx + xx][my + yy] <= 0)
114 				continue;
115 			int i = dItem[mx + xx][my + yy] - 1;
116 			if (item[i].isEmpty()
117 			    || item[i]._iSelFlag == 0)
118 				continue;
119 			int newRotations = GetRotaryDistance(mx + xx, my + yy);
120 			if (rotations < newRotations)
121 				continue;
122 			if (xx != 0 && yy != 0 && GetDistance(mx + xx, my + yy, 1) == 0)
123 				continue;
124 			rotations = newRotations;
125 			pcursitem = i;
126 			cursmx = mx + xx;
127 			cursmy = my + yy;
128 		}
129 	}
130 
131 	if (leveltype == DTYPE_TOWN || pcursitem != -1)
132 		return; // Don't look for objects in town
133 
134 	for (int xx = -1; xx < 2; xx++) {
135 		for (int yy = -1; yy < 2; yy++) {
136 			if (dObject[mx + xx][my + yy] == 0)
137 				continue;
138 			int o = dObject[mx + xx][my + yy] > 0 ? dObject[mx + xx][my + yy] - 1 : -(dObject[mx + xx][my + yy] + 1);
139 			if (object[o]._oSelFlag == 0)
140 				continue;
141 			if (xx == 0 && yy == 0 && object[o]._oDoorFlag)
142 				continue; // Ignore doorway so we don't get stuck behind barrels
143 			int newRotations = GetRotaryDistance(mx + xx, my + yy);
144 			if (rotations < newRotations)
145 				continue;
146 			if (xx != 0 && yy != 0 && GetDistance(mx + xx, my + yy, 1) == 0)
147 				continue;
148 			rotations = newRotations;
149 			pcursobj = o;
150 			cursmx = mx + xx;
151 			cursmy = my + yy;
152 		}
153 	}
154 }
155 
CheckTownersNearby()156 void CheckTownersNearby()
157 {
158 	for (int i = 0; i < 16; i++) {
159 		int distance = GetDistance(towner[i]._tx, towner[i]._ty, 2);
160 		if (distance == 0)
161 			continue;
162 		pcursmonst = i;
163 	}
164 }
165 
HasRangedSpell()166 bool HasRangedSpell()
167 {
168 	int spl = plr[myplr]._pRSpell;
169 
170 	return spl != SPL_INVALID
171 	    && spl != SPL_TOWN
172 	    && spl != SPL_TELEPORT
173 	    && spelldata[spl].sTargeted
174 	    && !spelldata[spl].sTownSpell;
175 }
176 
CanTargetMonster(int mi)177 bool CanTargetMonster(int mi)
178 {
179 	const MonsterStruct &monst = monster[mi];
180 
181 	if (monst._mFlags & (MFLAG_HIDDEN | MFLAG_GOLEM))
182 		return false;
183 	if (monst._mhitpoints >> 6 <= 0) // dead
184 		return false;
185 
186 	const int mx = monst._mx;
187 	const int my = monst._my;
188 	if (!(dFlags[mx][my] & BFLAG_LIT)) // not visible
189 		return false;
190 	if (dMonster[mx][my] == 0)
191 		return false;
192 
193 	return true;
194 }
195 
FindRangedTarget()196 void FindRangedTarget()
197 {
198 	int rotations = 0;
199 	int distance = 0;
200 	bool canTalk = false;
201 
202 	// The first MAX_PLRS monsters are reserved for players' golems.
203 	for (int mi = MAX_PLRS; mi < MAXMONSTERS; mi++) {
204 		const MonsterStruct &monst = monster[mi];
205 		const int mx = monst._mfutx;
206 		const int my = monst._mfuty;
207 		if (!CanTargetMonster(mi))
208 			continue;
209 
210 		const bool newCanTalk = CanTalkToMonst(mi);
211 		if (pcursmonst != -1 && !canTalk && newCanTalk)
212 			continue;
213 		const int newDdistance = GetDistanceRanged(mx, my);
214 		const int newRotations = GetRotaryDistance(mx, my);
215 		if (pcursmonst != -1 && canTalk == newCanTalk) {
216 			if (distance < newDdistance)
217 				continue;
218 			if (distance == newDdistance && rotations < newRotations)
219 				continue;
220 		}
221 		distance = newDdistance;
222 		rotations = newRotations;
223 		canTalk = newCanTalk;
224 		pcursmonst = mi;
225 	}
226 }
227 
FindMeleeTarget()228 void FindMeleeTarget()
229 {
230 	bool visited[MAXDUNX][MAXDUNY] = { { 0 } };
231 	int maxSteps = 25; // Max steps for FindPath is 25
232 	int rotations = 0;
233 	bool canTalk = false;
234 
235 	struct SearchNode {
236 		int x, y;
237 		int steps;
238 	};
239 	std::list<SearchNode> queue;
240 
241 	{
242 		const int start_x = plr[myplr]._pfutx;
243 		const int start_y = plr[myplr]._pfuty;
244 		visited[start_x][start_y] = true;
245 		queue.push_back({ start_x, start_y, 0 });
246 	}
247 
248 	while (!queue.empty()) {
249 		SearchNode node = queue.front();
250 		queue.pop_front();
251 
252 		for (int i = 0; i < 8; i++) {
253 			const int dx = node.x + pathxdir[i];
254 			const int dy = node.y + pathydir[i];
255 
256 			if (visited[dx][dy])
257 				continue; // already visisted
258 
259 			if (node.steps > maxSteps) {
260 				visited[dx][dy] = true;
261 				continue;
262 			}
263 
264 			if (!PosOkPlayer(myplr, dx, dy)) {
265 				visited[dx][dy] = true;
266 
267 				if (dMonster[dx][dy] != 0) {
268 					const int mi = dMonster[dx][dy] > 0 ? dMonster[dx][dy] - 1 : -(dMonster[dx][dy] + 1);
269 					if (CanTargetMonster(mi)) {
270 						const bool newCanTalk = CanTalkToMonst(mi);
271 						if (pcursmonst != -1 && !canTalk && newCanTalk)
272 							continue;
273 						const int newRotations = GetRotaryDistance(dx, dy);
274 						if (pcursmonst != -1 && canTalk == newCanTalk && rotations < newRotations)
275 							continue;
276 						rotations = newRotations;
277 						canTalk = newCanTalk;
278 						pcursmonst = mi;
279 						if (!canTalk)
280 							maxSteps = node.steps; // Monsters found, cap search to current steps
281 					}
282 				}
283 
284 				continue;
285 			}
286 
287 			PATHNODE pPath;
288 			pPath.x = node.x;
289 			pPath.y = node.y;
290 
291 			if (path_solid_pieces(&pPath, dx, dy)) {
292 				queue.push_back({ dx, dy, node.steps + 1 });
293 				visited[dx][dy] = true;
294 			}
295 		}
296 	}
297 }
298 
CheckMonstersNearby()299 void CheckMonstersNearby()
300 {
301 	if (plr[myplr]._pwtype == WT_RANGED || HasRangedSpell()) {
302 		FindRangedTarget();
303 		return;
304 	}
305 
306 	FindMeleeTarget();
307 }
308 
CheckPlayerNearby()309 void CheckPlayerNearby()
310 {
311 	int newDdistance;
312 	int rotations = 0;
313 	int distance = 0;
314 
315 	if (pcursmonst != -1)
316 		return;
317 
318 	int spl = plr[myplr]._pRSpell;
319 	if (gbFriendlyMode && spl != SPL_RESURRECT && spl != SPL_HEALOTHER)
320 		return;
321 
322 	for (int i = 0; i < MAX_PLRS; i++) {
323 		if (i == myplr)
324 			continue;
325 		const int mx = plr[i]._pfutx;
326 		const int my = plr[i]._pfuty;
327 		if (dPlayer[mx][my] == 0
328 		    || !(dFlags[mx][my] & BFLAG_LIT)
329 		    || (plr[i]._pHitPoints == 0 && spl != SPL_RESURRECT))
330 			continue;
331 
332 		if (plr[myplr]._pwtype == WT_RANGED || HasRangedSpell() || spl == SPL_HEALOTHER) {
333 			newDdistance = GetDistanceRanged(mx, my);
334 		} else {
335 			newDdistance = GetDistance(mx, my, distance);
336 			if (newDdistance == 0)
337 				continue;
338 		}
339 
340 		if (pcursplr != -1 && distance < newDdistance)
341 			continue;
342 		const int newRotations = GetRotaryDistance(mx, my);
343 		if (pcursplr != -1 && distance == newDdistance && rotations < newRotations)
344 			continue;
345 
346 		distance = newDdistance;
347 		rotations = newRotations;
348 		pcursplr = i;
349 	}
350 }
351 
FindActor()352 void FindActor()
353 {
354 	if (leveltype != DTYPE_TOWN)
355 		CheckMonstersNearby();
356 	else
357 		CheckTownersNearby();
358 
359 	if (gbIsMultiplayer)
360 		CheckPlayerNearby();
361 }
362 
363 int pcursmissile;
364 int pcurstrig;
365 int pcursquest;
366 
FindTrigger()367 void FindTrigger()
368 {
369 	int rotations;
370 	int distance = 0;
371 
372 	if (pcursitem != -1 || pcursobj != -1)
373 		return; // Prefer showing items/objects over triggers (use of cursm* conflicts)
374 
375 	for (int i = 0; i < nummissiles; i++) {
376 		int mi = missileactive[i];
377 		if (missile[mi]._mitype == MIS_TOWN || missile[mi]._mitype == MIS_RPORTAL) {
378 			int mix = missile[mi]._mix;
379 			int miy = missile[mi]._miy;
380 			const int newDdistance = GetDistance(mix, miy, 2);
381 			if (newDdistance == 0)
382 				continue;
383 			if (pcursmissile != -1 && distance < newDdistance)
384 				continue;
385 			const int newRotations = GetRotaryDistance(mix, miy);
386 			if (pcursmissile != -1 && distance == newDdistance && rotations < newRotations)
387 				continue;
388 			cursmx = mix;
389 			cursmy = miy;
390 			pcursmissile = mi;
391 			distance = newDdistance;
392 			rotations = newRotations;
393 		}
394 	}
395 
396 	if (pcursmissile == -1) {
397 		for (int i = 0; i < numtrigs; i++) {
398 			int tx = trigs[i]._tx;
399 			int ty = trigs[i]._ty;
400 			if (trigs[i]._tlvl == 13)
401 				ty -= 1;
402 			const int newDdistance = GetDistance(tx, ty, 2);
403 			if (newDdistance == 0)
404 				continue;
405 			cursmx = tx;
406 			cursmy = ty;
407 			pcurstrig = i;
408 		}
409 
410 		if (pcurstrig == -1) {
411 			for (int i = 0; i < MAXQUESTS; i++) {
412 				if (i == Q_BETRAYER || currlevel != quests[i]._qlevel || quests[i]._qslvl == 0)
413 					continue;
414 				const int newDdistance = GetDistance(quests[i]._qtx, quests[i]._qty, 2);
415 				if (newDdistance == 0)
416 					continue;
417 				cursmx = quests[i]._qtx;
418 				cursmy = quests[i]._qty;
419 				pcursquest = i;
420 			}
421 		}
422 	}
423 
424 	if (pcursmonst != -1 || pcursplr != -1 || cursmx == -1 || cursmy == -1)
425 		return; // Prefer monster/player info text
426 
427 	CheckTrigForce();
428 	CheckTown();
429 	CheckRportal();
430 }
431 
Interact()432 void Interact()
433 {
434 	if (leveltype == DTYPE_TOWN && pcursmonst != -1) {
435 		NetSendCmdLocParam1(true, CMD_TALKXY, towner[pcursmonst]._tx, towner[pcursmonst]._ty, pcursmonst);
436 	} else if (pcursmonst != -1) {
437 		if (plr[myplr]._pwtype != WT_RANGED || CanTalkToMonst(pcursmonst)) {
438 			NetSendCmdParam1(true, CMD_ATTACKID, pcursmonst);
439 		} else {
440 			NetSendCmdParam1(true, CMD_RATTACKID, pcursmonst);
441 		}
442 	} else if (leveltype != DTYPE_TOWN && pcursplr != -1 && !gbFriendlyMode) {
443 		NetSendCmdParam1(true, plr[myplr]._pwtype == WT_RANGED ? CMD_RATTACKPID : CMD_ATTACKPID, pcursplr);
444 	}
445 }
446 
AttrIncBtnSnap(AxisDirection dir)447 void AttrIncBtnSnap(AxisDirection dir)
448 {
449 	static AxisDirectionRepeater repeater;
450 	dir = repeater.Get(dir);
451 	if (dir.y == AxisDirectionY_NONE)
452 		return;
453 
454 	if (chrbtnactive && plr[myplr]._pStatPts <= 0)
455 		return;
456 
457 	// first, find our cursor location
458 	int slot = 0;
459 	for (int i = 0; i < 4; i++) {
460 		if (MouseX >= ChrBtnsRect[i].x
461 		    && MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
462 		    && MouseY >= ChrBtnsRect[i].y
463 		    && MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
464 			slot = i;
465 			break;
466 		}
467 	}
468 
469 	if (dir.y == AxisDirectionY_UP) {
470 		if (slot > 0)
471 			--slot;
472 	} else if (dir.y == AxisDirectionY_DOWN) {
473 		if (slot < 3)
474 			++slot;
475 	}
476 
477 	// move cursor to our new location
478 	int x = ChrBtnsRect[slot].x + (ChrBtnsRect[slot].w / 2);
479 	int y = ChrBtnsRect[slot].y + (ChrBtnsRect[slot].h / 2);
480 	SetCursorPos(x, y);
481 }
482 
483 /**
484  * Move the cursor around in our inventory
485  * If mouse coords are at SLOTXY_CHEST_LAST, consider this center of equipment
486  * small inventory squares are 29x29 (roughly)
487  */
InvMove(AxisDirection dir)488 void InvMove(AxisDirection dir)
489 {
490 	static AxisDirectionRepeater repeater(/*min_interval_ms=*/100);
491 	dir = repeater.Get(dir);
492 	if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE)
493 		return;
494 
495 	int x = MouseX;
496 	int y = MouseY;
497 
498 	// check which inventory rectangle the mouse is in, if any
499 	for (int r = 0; (DWORD)r < NUM_XY_SLOTS; r++) {
500 		int xo = RIGHT_PANEL;
501 		int yo = 0;
502 		if (r >= SLOTXY_BELT_FIRST) {
503 			xo = PANEL_LEFT;
504 			yo = PANEL_TOP;
505 		}
506 
507 		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) {
508 			slot = r;
509 			break;
510 		}
511 	}
512 
513 	if (slot < 0)
514 		slot = 0;
515 	if (slot > SLOTXY_BELT_LAST)
516 		slot = SLOTXY_BELT_LAST;
517 
518 	// when item is on cursor, this is the real cursor XY
519 	if (dir.x == AxisDirectionX_LEFT) {
520 		if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) {
521 			x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
522 			y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
523 		} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
524 			x = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
525 			y = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
526 		} else if (slot == SLOTXY_AMULET) {
527 			x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
528 			y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
529 		} else if (slot == SLOTXY_RING_RIGHT) {
530 			x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
531 			y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
532 		} else if (slot == SLOTXY_BELT_FIRST) {
533 			// do nothing
534 		} else if (slot == SLOTXY_RING_LEFT) {                                        // left ring
535 			                                                                          // do nothing
536 		} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) { // left hand
537 			                                                                          // do nothing
538 		} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) {           // head
539 			                                                                          // do nothing
540 		} else if (slot > SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {              // general inventory
541 			if (slot != SLOTXY_INV_FIRST && slot != 35 && slot != 45 && slot != 55) { // left bounds
542 				slot -= 1;
543 				x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
544 				y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
545 			}
546 		} else if (slot > SLOTXY_BELT_FIRST && slot <= SLOTXY_BELT_LAST) { // belt
547 			slot -= 1;
548 			x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
549 			y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
550 		}
551 	} else if (dir.x == AxisDirectionX_RIGHT) {
552 		if (slot == SLOTXY_RING_LEFT) {
553 			x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
554 			y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
555 		} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) {
556 			x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
557 			y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
558 		} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
559 			x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
560 			y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
561 		} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) { // head to amulet
562 			x = InvRect[SLOTXY_AMULET].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
563 			y = InvRect[SLOTXY_AMULET].Y - (INV_SLOT_SIZE_PX / 2);
564 		} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) { // right hand
565 			                                                                            // do nothing
566 		} else if (slot == SLOTXY_AMULET) {
567 			// do nothing
568 		} else if (slot == SLOTXY_RING_RIGHT) {
569 			// do nothing
570 		} else if (slot >= SLOTXY_INV_FIRST && slot <= SLOTXY_INV_LAST) {            // general inventory
571 			if (slot != 34 && slot != 44 && slot != 54 && slot != SLOTXY_INV_LAST) { // right bounds
572 				slot += 1;
573 				x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
574 				y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
575 			}
576 		} else if (slot >= SLOTXY_BELT_FIRST && slot < SLOTXY_BELT_LAST) { // belt
577 			slot += 1;
578 			x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
579 			y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
580 		}
581 	}
582 	if (dir.y == AxisDirectionY_UP) {
583 		if (slot > 24 && slot <= 27) { // first 3 general slots
584 			x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
585 			y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
586 		} else if (slot >= 28 && slot <= 32) { // middle 4 general slots
587 			x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
588 			y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
589 		} else if (slot >= 33 && slot < 35) { // last 3 general slots
590 			x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
591 			y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
592 		} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) { // chest to head
593 			x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
594 			y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
595 		} else if (slot == SLOTXY_RING_LEFT) { // left ring to left hand
596 			x = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
597 			y = InvRect[SLOTXY_HAND_LEFT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
598 		} else if (slot == SLOTXY_RING_RIGHT) { // right ring to right hand
599 			x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
600 			y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
601 		} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) { // right hand to amulet
602 			x = InvRect[SLOTXY_AMULET].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
603 			y = InvRect[SLOTXY_AMULET].Y - (INV_SLOT_SIZE_PX / 2);
604 		} else if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) {
605 			// do nothing
606 		} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) { // left hand to head
607 			x = InvRect[SLOTXY_HEAD_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
608 			y = InvRect[SLOTXY_HEAD_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
609 		} else if (slot == SLOTXY_AMULET) {
610 			// do nothing
611 		} else if (slot >= (SLOTXY_INV_FIRST + 10)) { // general inventory
612 			slot -= 10;
613 			x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
614 			y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
615 		}
616 	} else if (dir.y == AxisDirectionY_DOWN) {
617 		if (slot >= SLOTXY_HEAD_FIRST && slot <= SLOTXY_HEAD_LAST) {
618 			x = InvRect[SLOTXY_CHEST_FIRST].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
619 			y = InvRect[SLOTXY_CHEST_FIRST].Y - (INV_SLOT_SIZE_PX / 2);
620 		} else if (slot >= SLOTXY_CHEST_FIRST && slot <= SLOTXY_CHEST_LAST) {
621 			x = InvRect[30].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
622 			y = InvRect[30].Y - (INV_SLOT_SIZE_PX / 2);
623 		} else if (slot >= SLOTXY_HAND_LEFT_FIRST && slot <= SLOTXY_HAND_LEFT_LAST) {
624 			x = InvRect[SLOTXY_RING_LEFT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
625 			y = InvRect[SLOTXY_RING_LEFT].Y - (INV_SLOT_SIZE_PX / 2);
626 		} else if (slot == SLOTXY_RING_LEFT) {
627 			x = InvRect[26].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
628 			y = InvRect[26].Y - (INV_SLOT_SIZE_PX / 2);
629 		} else if (slot == SLOTXY_RING_RIGHT) {
630 			x = InvRect[34].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
631 			y = InvRect[34].Y - (INV_SLOT_SIZE_PX / 2);
632 		} else if (slot == SLOTXY_AMULET) {
633 			x = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
634 			y = InvRect[SLOTXY_HAND_RIGHT_FIRST + 2].Y - (INV_SLOT_SIZE_PX / 2);
635 		} else if (slot >= SLOTXY_HAND_RIGHT_FIRST && slot <= SLOTXY_HAND_RIGHT_LAST) {
636 			x = InvRect[SLOTXY_RING_RIGHT].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
637 			y = InvRect[SLOTXY_RING_RIGHT].Y - (INV_SLOT_SIZE_PX / 2);
638 		} else if (slot <= (SLOTXY_INV_LAST - 10)) { // general inventory
639 			slot += 10;
640 			x = InvRect[slot].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2);
641 			y = InvRect[slot].Y - (INV_SLOT_SIZE_PX / 2);
642 		} else if (slot <= (SLOTXY_BELT_LAST - 10)) { // general inventory
643 			slot += 10;
644 			x = InvRect[slot].X + PANEL_LEFT + (INV_SLOT_SIZE_PX / 2);
645 			y = InvRect[slot].Y + PANEL_TOP - (INV_SLOT_SIZE_PX / 2);
646 		}
647 	}
648 
649 	if (x == MouseX && y == MouseY) {
650 		return; // Avoid wobeling when scalled
651 	}
652 
653 	if (pcurs > 1) {       // [3] Keep item in the same slot, don't jump it up
654 		if (x != MouseX) { // without this, the cursor keeps moving -10
655 			x -= 10;
656 			y -= 10;
657 		}
658 	}
659 	SetCursorPos(x, y);
660 }
661 
662 /**
663  * check if hot spell at X Y exists
664  */
HSExists(int x,int y)665 bool HSExists(int x, int y)
666 {
667 	for (int r = 0; r < speedspellcount; r++) {
668 		if (x >= speedspellscoords[r].x - SPLICONLENGTH / 2
669 		    && x < speedspellscoords[r].x + SPLICONLENGTH / 2
670 		    && y >= speedspellscoords[r].y - SPLICONLENGTH / 2
671 		    && y < speedspellscoords[r].y + SPLICONLENGTH / 2) {
672 			return true;
673 		}
674 	}
675 	return false;
676 }
677 
HotSpellMove(AxisDirection dir)678 void HotSpellMove(AxisDirection dir)
679 {
680 	static AxisDirectionRepeater repeater;
681 	dir = repeater.Get(dir);
682 	if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE)
683 		return;
684 
685 	int spbslot = plr[myplr]._pRSpell;
686 	for (int r = 0; r < speedspellcount; r++) {
687 		if (MouseX >= speedspellscoords[r].x - SPLICONLENGTH / 2
688 		    && MouseX < speedspellscoords[r].x + SPLICONLENGTH / 2
689 		    && MouseY >= speedspellscoords[r].y - SPLICONLENGTH / 2
690 		    && MouseY < speedspellscoords[r].y + SPLICONLENGTH / 2) {
691 			spbslot = r;
692 			break;
693 		}
694 	}
695 
696 	int x = speedspellscoords[spbslot].x;
697 	int y = speedspellscoords[spbslot].y;
698 
699 	if (dir.x == AxisDirectionX_LEFT) {
700 		if (spbslot < speedspellcount - 1) {
701 			x = speedspellscoords[spbslot + 1].x;
702 			y = speedspellscoords[spbslot + 1].y;
703 		}
704 	} else if (dir.x == AxisDirectionX_RIGHT) {
705 		if (spbslot > 0) {
706 			x = speedspellscoords[spbslot - 1].x;
707 			y = speedspellscoords[spbslot - 1].y;
708 		}
709 	}
710 
711 	if (dir.y == AxisDirectionY_UP) {
712 		if (HSExists(x, y - SPLICONLENGTH)) {
713 			y -= SPLICONLENGTH;
714 		}
715 	} else if (dir.y == AxisDirectionY_DOWN) {
716 		if (HSExists(x, y + SPLICONLENGTH)) {
717 			y += SPLICONLENGTH;
718 		}
719 	}
720 
721 	if (x != MouseX || y != MouseY) {
722 		SetCursorPos(x, y);
723 	}
724 }
725 
SpellBookMove(AxisDirection dir)726 void SpellBookMove(AxisDirection dir)
727 {
728 	static AxisDirectionRepeater repeater;
729 	dir = repeater.Get(dir);
730 
731 	if (dir.x == AxisDirectionX_LEFT) {
732 		if (sbooktab > 0)
733 			sbooktab--;
734 	} else if (dir.x == AxisDirectionX_RIGHT) {
735 		if ((gbIsHellfire && sbooktab < 4) || (!gbIsHellfire && sbooktab < 3))
736 			sbooktab++;
737 	}
738 }
739 
740 static const direction kFaceDir[3][3] = {
741 	// NONE      UP      DOWN
742 	{ DIR_OMNI, DIR_N, DIR_S }, // NONE
743 	{ DIR_W, DIR_NW, DIR_SW },  // LEFT
744 	{ DIR_E, DIR_NE, DIR_SE },  // RIGHT
745 };
746 static const int kOffsets[8][2] = {
747 	{ 1, 1 },   // DIR_S
748 	{ 0, 1 },   // DIR_SW
749 	{ -1, 1 },  // DIR_W
750 	{ -1, 0 },  // DIR_NW
751 	{ -1, -1 }, // DIR_N
752 	{ 0, -1 },  // DIR_NE
753 	{ 1, -1 },  // DIR_E
754 	{ 1, 0 },   // DIR_SE
755 };
756 
757 /**
758  * @brief check if stepping in direction (dir) from x, y is blocked.
759  *
760  * If you step from A to B, at leat one of the Xs need to be clear:
761  *
762  *  AX
763  *  XB
764  *
765  *  @return true if step is blocked
766  */
IsPathBlocked(int x,int y,int dir)767 bool IsPathBlocked(int x, int y, int dir)
768 {
769 	int d1, d2, d1x, d1y, d2x, d2y;
770 
771 	switch (dir) {
772 	case DIR_N:
773 		d1 = DIR_NW;
774 		d2 = DIR_NE;
775 		break;
776 	case DIR_E:
777 		d1 = DIR_NE;
778 		d2 = DIR_SE;
779 		break;
780 	case DIR_S:
781 		d1 = DIR_SE;
782 		d2 = DIR_SW;
783 		break;
784 	case DIR_W:
785 		d1 = DIR_SW;
786 		d2 = DIR_NW;
787 		break;
788 	default:
789 		return false;
790 	}
791 
792 	d1x = x + kOffsets[d1][0];
793 	d1y = y + kOffsets[d1][1];
794 	d2x = x + kOffsets[d2][0];
795 	d2y = y + kOffsets[d2][1];
796 
797 	if (!nSolidTable[dPiece[d1x][d1y]] && !nSolidTable[dPiece[d2x][d2y]])
798 		return false;
799 
800 	return !PosOkPlayer(myplr, d1x, d1y) && !PosOkPlayer(myplr, d2x, d2y);
801 }
802 
WalkInDir(AxisDirection dir)803 void WalkInDir(AxisDirection dir)
804 {
805 	const int x = plr[myplr]._pfutx;
806 	const int y = plr[myplr]._pfuty;
807 
808 	if (dir.x == AxisDirectionX_NONE && dir.y == AxisDirectionY_NONE) {
809 		if (sgbControllerActive && plr[myplr].walkpath[0] != WALK_NONE && plr[myplr].destAction == ACTION_NONE)
810 			NetSendCmdLoc(true, CMD_WALKXY, x, y); // Stop walking
811 		return;
812 	}
813 
814 	const direction pdir = kFaceDir[static_cast<std::size_t>(dir.x)][static_cast<std::size_t>(dir.y)];
815 	const int dx = x + kOffsets[pdir][0];
816 	const int dy = y + kOffsets[pdir][1];
817 	plr[myplr]._pdir = pdir;
818 
819 	if (PosOkPlayer(myplr, dx, dy) && IsPathBlocked(x, y, pdir))
820 		return; // Don't start backtrack around obstacles
821 
822 	NetSendCmdLoc(true, CMD_WALKXY, dx, dy);
823 }
824 
QuestLogMove(AxisDirection move_dir)825 void QuestLogMove(AxisDirection move_dir)
826 {
827 	static AxisDirectionRepeater repeater;
828 	move_dir = repeater.Get(move_dir);
829 	if (move_dir.y == AxisDirectionY_UP)
830 		QuestlogUp();
831 	else if (move_dir.y == AxisDirectionY_DOWN)
832 		QuestlogDown();
833 }
834 
StoreMove(AxisDirection move_dir)835 void StoreMove(AxisDirection move_dir)
836 {
837 	static AxisDirectionRepeater repeater;
838 	move_dir = repeater.Get(move_dir);
839 	if (move_dir.y == AxisDirectionY_UP)
840 		STextUp();
841 	else if (move_dir.y == AxisDirectionY_DOWN)
842 		STextDown();
843 }
844 
845 typedef void (*HandleLeftStickOrDPadFn)(dvl::AxisDirection);
846 
GetLeftStickOrDPadGameUIHandler()847 HandleLeftStickOrDPadFn GetLeftStickOrDPadGameUIHandler()
848 {
849 	if (invflag) {
850 		return &InvMove;
851 	} else if (chrflag && plr[myplr]._pStatPts > 0) {
852 		return &AttrIncBtnSnap;
853 	} else if (spselflag) {
854 		return &HotSpellMove;
855 	} else if (sbookflag) {
856 		return &SpellBookMove;
857 	} else if (questlog) {
858 		return &QuestLogMove;
859 	} else if (stextflag != STORE_NONE) {
860 		return &StoreMove;
861 	}
862 	return NULL;
863 }
864 
ProcessLeftStickOrDPadGameUI()865 void ProcessLeftStickOrDPadGameUI()
866 {
867 	HandleLeftStickOrDPadFn handler = GetLeftStickOrDPadGameUIHandler();
868 	if (handler != NULL)
869 		handler(GetLeftStickOrDpadDirection(true));
870 }
871 
Movement()872 void Movement()
873 {
874 	if (InGameMenu()
875 	    || IsControllerButtonPressed(ControllerButton_BUTTON_START)
876 	    || IsControllerButtonPressed(ControllerButton_BUTTON_BACK))
877 		return;
878 
879 	AxisDirection move_dir = GetMoveDirection();
880 	if (move_dir.x != AxisDirectionX_NONE || move_dir.y != AxisDirectionY_NONE) {
881 		sgbControllerActive = true;
882 	}
883 
884 	if (GetLeftStickOrDPadGameUIHandler() == NULL) {
885 		WalkInDir(move_dir);
886 	}
887 }
888 
889 struct RightStickAccumulator {
890 
RightStickAccumulatordvl::__anon7caacc790111::RightStickAccumulator891 	RightStickAccumulator()
892 	{
893 		lastTc = SDL_GetTicks();
894 		hiresDX = 0;
895 		hiresDY = 0;
896 	}
897 
pooldvl::__anon7caacc790111::RightStickAccumulator898 	void pool(int *x, int *y, int slowdown)
899 	{
900 		const Uint32 tc = SDL_GetTicks();
901 		const int dtc = tc - lastTc;
902 		hiresDX += rightStickX * dtc;
903 		hiresDY += rightStickY * dtc;
904 		const int dx = hiresDX / slowdown;
905 		const int dy = hiresDY / slowdown;
906 		*x += dx;
907 		*y -= dy;
908 		lastTc = tc;
909 		// keep track of remainder for sub-pixel motion
910 		hiresDX -= dx * slowdown;
911 		hiresDY -= dy * slowdown;
912 	}
913 
cleardvl::__anon7caacc790111::RightStickAccumulator914 	void clear()
915 	{
916 		lastTc = SDL_GetTicks();
917 	}
918 
919 	DWORD lastTc;
920 	float hiresDX;
921 	float hiresDY;
922 };
923 
924 } // namespace
925 
StoreSpellCoords()926 void StoreSpellCoords()
927 {
928 	const int START_X = PANEL_LEFT + 12 + SPLICONLENGTH / 2;
929 	const int END_X = START_X + SPLICONLENGTH * 10;
930 	const int END_Y = PANEL_TOP - 17 - SPLICONLENGTH / 2;
931 	speedspellcount = 0;
932 	int xo = END_X;
933 	int yo = END_Y;
934 	for (int i = 0; i < 4; i++) {
935 		std::uint64_t spells;
936 		switch (i) {
937 		case RSPLTYPE_SKILL:
938 			spells = plr[myplr]._pAblSpells;
939 			break;
940 		case RSPLTYPE_SPELL:
941 			spells = plr[myplr]._pMemSpells;
942 			break;
943 		case RSPLTYPE_SCROLL:
944 			spells = plr[myplr]._pScrlSpells;
945 			break;
946 		case RSPLTYPE_CHARGES:
947 			spells = plr[myplr]._pISpells;
948 			break;
949 		default:
950 			continue;
951 		}
952 		std::uint64_t spell = 1;
953 		for (int j = 1; j < MAX_SPELLS; j++) {
954 			if ((spell & spells)) {
955 				speedspellscoords[speedspellcount] = { xo, yo };
956 				++speedspellcount;
957 				xo -= SPLICONLENGTH;
958 				if (xo < START_X) {
959 					xo = END_X;
960 					yo -= SPLICONLENGTH;
961 				}
962 			}
963 			spell <<= 1;
964 		}
965 		if (spells && xo != END_X)
966 			xo -= SPLICONLENGTH;
967 		if (xo < START_X) {
968 			xo = END_X;
969 			yo -= SPLICONLENGTH;
970 		}
971 	}
972 }
973 
IsAutomapActive()974 bool IsAutomapActive()
975 {
976 	return automapflag && leveltype != DTYPE_TOWN;
977 }
978 
IsMovingMouseCursorWithController()979 bool IsMovingMouseCursorWithController()
980 {
981 	return rightStickX != 0 || rightStickY != 0;
982 }
983 
HandleRightStickMotion()984 void HandleRightStickMotion()
985 {
986 	static RightStickAccumulator acc;
987 	// deadzone is handled in ScaleJoystickAxes() already
988 	if (rightStickX == 0 && rightStickY == 0) {
989 		acc.clear();
990 		return;
991 	}
992 
993 	if (IsAutomapActive()) { // move map
994 		int dx = 0, dy = 0;
995 		acc.pool(&dx, &dy, 32);
996 		AutoMapXOfs += dy + dx;
997 		AutoMapYOfs += dy - dx;
998 		return;
999 	}
1000 
1001 	{ // move cursor
1002 		sgbControllerActive = false;
1003 		int x = MouseX;
1004 		int y = MouseY;
1005 		acc.pool(&x, &y, 2);
1006 		x = std::min(std::max(x, 0), gnScreenWidth - 1);
1007 		y = std::min(std::max(y, 0), gnScreenHeight - 1);
1008 
1009 		// We avoid calling `SetCursorPos` within the same SDL tick because
1010 		// that can cause all stick motion events to arrive before all
1011 		// cursor position events.
1012 		static int lastMouseSetTick = 0;
1013 		const int now = SDL_GetTicks();
1014 		if (now - lastMouseSetTick > 0) {
1015 			SetCursorPos(x, y);
1016 			lastMouseSetTick = now;
1017 		}
1018 	}
1019 }
1020 
1021 /**
1022  * @brief Moves the mouse to the first inventory slot.
1023  */
FocusOnInventory()1024 void FocusOnInventory()
1025 {
1026 	SetCursorPos(InvRect[25].X + RIGHT_PANEL + (INV_SLOT_SIZE_PX / 2), InvRect[25].Y - (INV_SLOT_SIZE_PX / 2));
1027 }
1028 
plrctrls_after_check_curs_move()1029 void plrctrls_after_check_curs_move()
1030 {
1031 	// check for monsters first, then items, then towners.
1032 	if (sgbControllerActive) {
1033 		// Clear focuse set by cursor
1034 		pcursplr = -1;
1035 		pcursmonst = -1;
1036 		pcursitem = -1;
1037 		pcursobj = -1;
1038 		pcursmissile = -1;
1039 		pcurstrig = -1;
1040 		pcursquest = -1;
1041 		cursmx = -1;
1042 		cursmy = -1;
1043 		if (!invflag) {
1044 			*infostr = '\0';
1045 			ClearPanel();
1046 			FindActor();
1047 			FindItemOrObject();
1048 			FindTrigger();
1049 		}
1050 	}
1051 }
1052 
plrctrls_every_frame()1053 void plrctrls_every_frame()
1054 {
1055 	ProcessLeftStickOrDPadGameUI();
1056 	HandleRightStickMotion();
1057 }
1058 
plrctrls_after_game_logic()1059 void plrctrls_after_game_logic()
1060 {
1061 	Movement();
1062 }
1063 
UseBeltItem(int type)1064 void UseBeltItem(int type)
1065 {
1066 	for (int i = 0; i < MAXBELTITEMS; i++) {
1067 		const int id = AllItemsList[plr[myplr].SpdList[i].IDidx].iMiscId;
1068 		const int spellId = AllItemsList[plr[myplr].SpdList[i].IDidx].iSpell;
1069 		if ((type == BLT_HEALING && (id == IMISC_HEAL || id == IMISC_FULLHEAL || (id == IMISC_SCROLL && spellId == SPL_HEAL)))
1070 		    || (type == BLT_MANA && (id == IMISC_MANA || id == IMISC_FULLMANA))
1071 		    || id == IMISC_REJUV || id == IMISC_FULLREJUV) {
1072 			if (plr[myplr].SpdList[i]._itype > -1) {
1073 				UseInvItem(myplr, INVITEM_BELT_FIRST + i);
1074 				break;
1075 			}
1076 		}
1077 	}
1078 }
1079 
PerformPrimaryAction()1080 void PerformPrimaryAction()
1081 {
1082 	if (invflag) { // inventory is open
1083 		if (pcurs > CURSOR_HAND && pcurs < CURSOR_FIRSTITEM) {
1084 			TryIconCurs();
1085 			SetCursor_(CURSOR_HAND);
1086 		} else {
1087 			CheckInvItem();
1088 		}
1089 		return;
1090 	}
1091 
1092 	if (spselflag) {
1093 		SetSpell();
1094 		return;
1095 	}
1096 
1097 	if (chrflag && !chrbtnactive && plr[myplr]._pStatPts > 0) {
1098 		CheckChrBtns();
1099 		for (int i = 0; i < 4; i++) {
1100 			if (MouseX >= ChrBtnsRect[i].x
1101 			    && MouseX <= ChrBtnsRect[i].x + ChrBtnsRect[i].w
1102 			    && MouseY >= ChrBtnsRect[i].y
1103 			    && MouseY <= ChrBtnsRect[i].h + ChrBtnsRect[i].y) {
1104 				chrbtn[i] = 1;
1105 				chrbtnactive = true;
1106 				ReleaseChrBtns(false);
1107 			}
1108 		}
1109 		return;
1110 	}
1111 
1112 	Interact();
1113 }
1114 
SpellHasActorTarget()1115 bool SpellHasActorTarget()
1116 {
1117 	int spl = plr[myplr]._pRSpell;
1118 	if (spl == SPL_TOWN || spl == SPL_TELEPORT)
1119 		return false;
1120 
1121 	if (spl == SPL_FIREWALL && pcursmonst != -1) {
1122 		cursmx = monster[pcursmonst]._mx;
1123 		cursmy = monster[pcursmonst]._my;
1124 	}
1125 
1126 	return pcursplr != -1 || pcursmonst != -1;
1127 }
1128 
UpdateSpellTarget()1129 void UpdateSpellTarget()
1130 {
1131 	if (SpellHasActorTarget())
1132 		return;
1133 
1134 	pcursplr = -1;
1135 	pcursmonst = -1;
1136 
1137 	const PlayerStruct &player = plr[myplr];
1138 
1139 	int range = 1;
1140 	if (plr[myplr]._pRSpell == SPL_TELEPORT)
1141 		range = 4;
1142 
1143 	cursmx = player._pfutx + kOffsets[player._pdir][0] * range;
1144 	cursmy = player._pfuty + kOffsets[player._pdir][1] * range;
1145 }
1146 
1147 /**
1148  * @brief Try dropping item in all 9 possible places
1149  */
TryDropItem()1150 bool TryDropItem()
1151 {
1152 	cursmx = plr[myplr]._pfutx + 1;
1153 	cursmy = plr[myplr]._pfuty;
1154 	if (!DropItemBeforeTrig()) {
1155 		// Try to drop on the other side
1156 		cursmx = plr[myplr]._pfutx;
1157 		cursmy = plr[myplr]._pfuty + 1;
1158 		DropItemBeforeTrig();
1159 	}
1160 
1161 	return pcurs == CURSOR_HAND;
1162 }
1163 
PerformSpellAction()1164 void PerformSpellAction()
1165 {
1166 	if (InGameMenu() || questlog || sbookflag)
1167 		return;
1168 
1169 	if (invflag) {
1170 		if (pcurs >= CURSOR_FIRSTITEM)
1171 			TryDropItem();
1172 		else if (pcurs > CURSOR_HAND) {
1173 			TryIconCurs();
1174 			SetCursor_(CURSOR_HAND);
1175 		}
1176 		return;
1177 	}
1178 
1179 	if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
1180 		return;
1181 	if (pcurs > CURSOR_HAND)
1182 		SetCursor_(CURSOR_HAND);
1183 
1184 	if (spselflag) {
1185 		SetSpell();
1186 		return;
1187 	}
1188 
1189 	int spl = plr[myplr]._pRSpell;
1190 	if ((pcursplr == -1 && (spl == SPL_RESURRECT || spl == SPL_HEALOTHER))
1191 	    || (pcursobj == -1 && spl == SPL_DISARM)) {
1192 		if (plr[myplr]._pClass == PC_WARRIOR) {
1193 			PlaySFX(PS_WARR27);
1194 		} else if (plr[myplr]._pClass == PC_ROGUE) {
1195 			PlaySFX(PS_ROGUE27);
1196 		} else if (plr[myplr]._pClass == PC_SORCERER) {
1197 			PlaySFX(PS_MAGE27);
1198 		}
1199 		return;
1200 	}
1201 
1202 	UpdateSpellTarget();
1203 	CheckPlrSpell();
1204 }
1205 
CtrlUseInvItem()1206 void CtrlUseInvItem()
1207 {
1208 	ItemStruct *Item;
1209 
1210 	if (pcursinvitem == -1)
1211 		return;
1212 
1213 	if (pcursinvitem <= INVITEM_INV_LAST)
1214 		Item = &plr[myplr].InvList[pcursinvitem - INVITEM_INV_FIRST];
1215 	else
1216 		Item = &plr[myplr].SpdList[pcursinvitem - INVITEM_BELT_FIRST];
1217 
1218 	if ((Item->_iMiscId == IMISC_SCROLLT || Item->_iMiscId == IMISC_SCROLL) && spelldata[Item->_iSpell].sTargeted) {
1219 		return;
1220 	}
1221 
1222 	UseInvItem(myplr, pcursinvitem);
1223 }
1224 
PerformSecondaryAction()1225 void PerformSecondaryAction()
1226 {
1227 	if (invflag) {
1228 		CtrlUseInvItem();
1229 		return;
1230 	}
1231 
1232 	if (pcurs >= CURSOR_FIRSTITEM && !TryDropItem())
1233 		return;
1234 	if (pcurs > CURSOR_HAND)
1235 		SetCursor_(CURSOR_HAND);
1236 
1237 	if (pcursitem != -1) {
1238 		NetSendCmdLocParam1(true, CMD_GOTOAGETITEM, cursmx, cursmy, pcursitem);
1239 	} else if (pcursobj != -1) {
1240 		NetSendCmdLocParam1(true, CMD_OPOBJXY, cursmx, cursmy, pcursobj);
1241 	} else if (pcursmissile != -1) {
1242 		MakePlrPath(myplr, missile[pcursmissile]._mix, missile[pcursmissile]._miy, true);
1243 		plr[myplr].destAction = ACTION_WALK;
1244 	} else if (pcurstrig != -1) {
1245 		MakePlrPath(myplr, trigs[pcurstrig]._tx, trigs[pcurstrig]._ty, true);
1246 		plr[myplr].destAction = ACTION_WALK;
1247 	} else if (pcursquest != -1) {
1248 		MakePlrPath(myplr, quests[pcursquest]._qtx, quests[pcursquest]._qty, true);
1249 		plr[myplr].destAction = ACTION_WALK;
1250 	}
1251 }
1252 
1253 } // namespace dvl
1254