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