1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: collision.cpp
5 Desc: contains all collision detection code
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include "main.hpp"
13 #include "game.hpp"
14 #include "stat.hpp"
15 #include "messages.hpp"
16 #include "entity.hpp"
17 #include "sound.hpp"
18 #include "items.hpp"
19 #include "interface/interface.hpp"
20 #include "magic/magic.hpp"
21 #include "net.hpp"
22 #include "paths.hpp"
23 #include "collision.hpp"
24 #include "player.hpp"
25 #ifdef __ARM_NEON__
26 #include <arm_neon.h>
27 #endif
28
29 /*-------------------------------------------------------------------------------
30
31 entityDist
32
33 returns the distance between the two given entities
34
35 -------------------------------------------------------------------------------*/
36
entityDist(Entity * my,Entity * your)37 real_t entityDist(Entity* my, Entity* your)
38 {
39 real_t dx, dy;
40 dx = my->x - your->x;
41 dy = my->y - your->y;
42 return sqrt(dx * dx + dy * dy);
43 }
44
45 /*-------------------------------------------------------------------------------
46
47 entityClicked
48
49 returns the entity that was last clicked on with the mouse
50
51 -------------------------------------------------------------------------------*/
52
entityClicked(bool * clickedOnGUI,bool clickCheckOverride,int player)53 Entity* entityClicked(bool* clickedOnGUI, bool clickCheckOverride, int player)
54 {
55 Uint32 uidnum;
56 GLubyte pixel[4];
57
58 if ( !clickCheckOverride && !(*inputPressed(impulses[IN_USE])) && !(*inputPressed(joyimpulses[INJOY_GAME_USE])) )
59 {
60 return NULL;
61 }
62 if ( !shootmode )
63 {
64 if ( itemMenuOpen )
65 {
66 if ( clickedOnGUI )
67 {
68 *clickedOnGUI = true;
69 }
70 return NULL;
71 }
72 if ( omousex < 0 || omousex >= 0 + xres || omousey < 0 || omousey >= 0 + yres )
73 {
74 if ( clickedOnGUI )
75 {
76 *clickedOnGUI = true;
77 }
78 return NULL;
79 }
80 if (openedChest[clientnum])
81 if (omousex > CHEST_INVENTORY_X && omousex < CHEST_INVENTORY_X + inventoryChest_bmp->w && omousey > CHEST_INVENTORY_Y && omousey < CHEST_INVENTORY_Y + inventoryChest_bmp->h)
82 {
83 if ( clickedOnGUI )
84 {
85 *clickedOnGUI = true;
86 }
87 return NULL; //Click falls inside the chest inventory GUI.
88 }
89 if (identifygui_active)
90 if (omousex > IDENTIFY_GUI_X && omousex < IDENTIFY_GUI_X + identifyGUI_img->w && omousey > IDENTIFY_GUI_Y && omousey < IDENTIFY_GUI_Y + identifyGUI_img->h)
91 {
92 if ( clickedOnGUI )
93 {
94 *clickedOnGUI = true;
95 }
96 return NULL; //Click falls inside the identify item gui.
97 }
98 if (book_open)
99 if (mouseInBounds(BOOK_GUI_X, BOOK_GUI_X + bookgui_img->w, BOOK_GUI_Y, BOOK_GUI_Y + bookgui_img->h))
100 {
101 if ( clickedOnGUI )
102 {
103 *clickedOnGUI = true;
104 }
105 return NULL; //Click falls inside the book GUI.
106 }
107 if (gui_mode == GUI_MODE_INVENTORY || gui_mode == GUI_MODE_SHOP)
108 {
109 if ( gui_mode == GUI_MODE_INVENTORY )
110 if (mouseInBounds(RIGHTSIDEBAR_X, RIGHTSIDEBAR_X + rightsidebar_titlebar_img->w, RIGHTSIDEBAR_Y, RIGHTSIDEBAR_Y + rightsidebar_height))
111 {
112 if ( clickedOnGUI )
113 {
114 *clickedOnGUI = true;
115 }
116 return NULL; //Click falls inside the right sidebar.
117 }
118 //int x = std::max(character_bmp->w, xres/2-inventory_bmp->w/2);
119 //if (mouseInBounds(x,x+inventory_bmp->w,0,inventory_bmp->h))
120 //return NULL;
121 if ( mouseInBounds(INVENTORY_STARTX, INVENTORY_STARTX + INVENTORY_SIZEX * INVENTORY_SLOTSIZE, INVENTORY_STARTY, INVENTORY_STARTY + INVENTORY_SIZEY * INVENTORY_SLOTSIZE) )
122 {
123 // clicked in inventory
124 if ( clickedOnGUI )
125 {
126 *clickedOnGUI = true;
127 }
128 return NULL;
129 }
130 if ( gui_mode == GUI_MODE_SHOP )
131 {
132 int x1 = xres / 2 - SHOPWINDOW_SIZEX / 2, x2 = xres / 2 + SHOPWINDOW_SIZEX / 2;
133 int y1 = yres / 2 - SHOPWINDOW_SIZEY / 2, y2 = yres / 2 + SHOPWINDOW_SIZEY / 2;
134 if (mouseInBounds(x1, x2, y1, y2))
135 {
136 if ( clickedOnGUI )
137 {
138 *clickedOnGUI = true;
139 }
140 return NULL;
141 }
142 }
143 }
144 else if (gui_mode == GUI_MODE_MAGIC)
145 {
146 if (magic_GUI_state == 0)
147 {
148 //Right, now calculate the spell list's height (the same way it calculates it for itself).
149 int height = spell_list_titlebar_bmp->h;
150 int numspells = 0;
151 node_t* node;
152 for (node = spellList.first; node != NULL; node = node->next)
153 {
154 numspells++;
155 }
156 int maxSpellsOnscreen = yres / spell_list_gui_slot_bmp->h;
157 numspells = std::min(numspells, maxSpellsOnscreen);
158 height += numspells * spell_list_gui_slot_bmp->h;
159 int spelllist_y = 0 + ((yres / 2) - (height / 2)) + magicspell_list_offset_x;
160
161 if (mouseInBounds(MAGICSPELL_LIST_X, MAGICSPELL_LIST_X + spell_list_titlebar_bmp->w, spelllist_y, spelllist_y + height))
162 {
163 if ( clickedOnGUI )
164 {
165 *clickedOnGUI = true;
166 }
167 return NULL;
168 }
169 }
170 }
171 if ( mouseInBounds(interfaceCharacterSheet.x, interfaceCharacterSheet.x + interfaceCharacterSheet.w,
172 interfaceCharacterSheet.y, interfaceCharacterSheet.y + interfaceCharacterSheet.h) ) // character sheet
173 {
174 if ( clickedOnGUI )
175 {
176 *clickedOnGUI = true;
177 }
178 return NULL;
179 }
180
181 if ( !hide_statusbar &&
182 mouseInBounds(interfaceMessageStatusBar.x, interfaceMessageStatusBar.x + interfaceMessageStatusBar.w,
183 interfaceMessageStatusBar.y, interfaceMessageStatusBar.y + interfaceMessageStatusBar.h) ) // bottom message log
184 {
185 if ( clickedOnGUI )
186 {
187 *clickedOnGUI = true;
188 }
189 return NULL;
190 }
191
192 // ui code taken from drawSkillsSheet() and drawPartySheet().
193 if ( proficienciesPage == 0 )
194 {
195 if ( mouseInBounds(interfaceSkillsSheet.x, interfaceSkillsSheet.x + interfaceSkillsSheet.w,
196 interfaceSkillsSheet.y, interfaceSkillsSheet.y + interfaceSkillsSheet.h) )
197 {
198 if ( clickedOnGUI )
199 {
200 *clickedOnGUI = true;
201 }
202 return NULL;
203 }
204 }
205 else
206 {
207 if ( mouseInBounds(interfacePartySheet.x, interfacePartySheet.x + interfacePartySheet.w,
208 interfacePartySheet.y, interfacePartySheet.y + interfacePartySheet.h) )
209 {
210 if ( clickedOnGUI )
211 {
212 *clickedOnGUI = true;
213 }
214 return NULL;
215 }
216 }
217
218 if ( mouseInsidePlayerInventory() || mouseInsidePlayerHotbar() )
219 {
220 if ( clickedOnGUI )
221 {
222 *clickedOnGUI = true;
223 }
224 return NULL;
225 }
226
227 if ( softwaremode )
228 {
229 return clickmap[omousey + omousex * yres];
230 }
231 else
232 {
233 uidnum = GO_GetPixelU32(omousex, yres - omousey, cameras[player]);
234 }
235 }
236 else
237 {
238 if ( softwaremode )
239 {
240 return clickmap[(yres / 2) + (xres / 2) * yres];
241 }
242 else
243 {
244 uidnum = GO_GetPixelU32(xres / 2, yres / 2, cameras[player]);
245 }
246 }
247
248 Entity* entity = uidToEntity(uidnum);
249
250 if ( !entity && !mute_player_monster_sounds && !clickCheckOverride )
251 {
252 if ( players[clientnum] && players[clientnum]->entity && monsterEmoteGimpTimer == 0 )
253 {
254 monsterEmoteGimpTimer = TICKS_PER_SECOND * 5;
255 int sfx = 0;
256 int line = 0;
257 switch ( stats[clientnum]->type )
258 {
259 case SKELETON:
260 sfx = 95;
261 monsterEmoteGimpTimer = TICKS_PER_SECOND;
262 break;
263 case SUCCUBUS:
264 sfx = 70;
265 break;
266 case VAMPIRE:
267 if ( rand() % 4 == 0 )
268 {
269 sfx = 329;
270 }
271 else
272 {
273 sfx = 322 + rand() % 3;
274 }
275 break;
276 case GOATMAN:
277 sfx = 332 + rand() % 2;
278 break;
279 case INSECTOID:
280 sfx = 291 + rand() % 4;
281 break;
282 case GOBLIN:
283 sfx = 60 + rand() % 3;
284 break;
285 case AUTOMATON:
286 sfx = 257 + rand() % 2;
287 break;
288 case INCUBUS:
289 sfx = 276 + rand() % 3;
290 break;
291 case RAT:
292 sfx = 29;
293 break;
294 case TROLL:
295 if ( rand() % 3 == 0 )
296 {
297 sfx = 79;
298 }
299 break;
300 case SPIDER:
301 if ( rand() % 3 == 2 )
302 {
303 sfx = 235;
304 }
305 else
306 {
307 sfx = 230 + rand() % 2;
308 }
309 break;
310 case CREATURE_IMP:
311 sfx = 198 + rand() % 3;
312 break;
313 default:
314 sfx = 0;
315 break;
316 }
317
318 //Tell the server we made a noise.
319 if ( sfx != 0 )
320 {
321 if ( multiplayer == CLIENT )
322 {
323 playSound(sfx, 92);
324 strcpy((char*)net_packet->data, "EMOT");
325 net_packet->data[4] = clientnum;
326 SDLNet_Write16(sfx, &net_packet->data[5]);
327 net_packet->address.host = net_server.host;
328 net_packet->address.port = net_server.port;
329 net_packet->len = 7;
330 sendPacketSafe(net_sock, -1, net_packet, 0);
331 }
332 else if ( multiplayer != CLIENT )
333 {
334 playSound(sfx, 92);
335 for ( int c = 1; c < MAXPLAYERS; ++c )
336 {
337 if ( !client_disconnected[c] )
338 {
339 strcpy((char*)net_packet->data, "SNEL");
340 SDLNet_Write16(sfx, &net_packet->data[4]);
341 SDLNet_Write32((Uint32)players[clientnum]->entity->getUID(), &net_packet->data[6]);
342 SDLNet_Write16(92, &net_packet->data[10]);
343 net_packet->address.host = net_clients[c - 1].host;
344 net_packet->address.port = net_clients[c - 1].port;
345 net_packet->len = 12;
346 sendPacketSafe(net_sock, -1, net_packet, c - 1);
347 }
348 }
349 }
350 }
351 }
352 }
353
354 // pixel processing (opengl only)
355 if ( softwaremode == false)
356 {
357 return entity;
358 }
359 else
360 {
361 return NULL;
362 }
363 }
364
365 /*-------------------------------------------------------------------------------
366
367 entityInsideTile
368
369 checks whether an entity is intersecting an impassible tile
370
371 -------------------------------------------------------------------------------*/
372
entityInsideTile(Entity * entity,int x,int y,int z,bool checkSafeTiles)373 bool entityInsideTile(Entity* entity, int x, int y, int z, bool checkSafeTiles)
374 {
375 if ( x < 0 || x >= map.width || y < 0 || y >= map.height || z < 0 || z >= MAPLAYERS )
376 {
377 return false;
378 }
379 if ( entity->x + entity->sizex >= x << 4 )
380 {
381 if ( entity->x - entity->sizex < (x + 1) << 4 )
382 {
383 if ( entity->y + entity->sizey >= y << 4 )
384 {
385 if ( entity->y - entity->sizey < (y + 1) << 4 )
386 {
387 if ( z == OBSTACLELAYER )
388 {
389 if ( map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] )
390 {
391 return true;
392 }
393 }
394 else if ( z == 0 )
395 {
396 if ( !checkSafeTiles && !map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] )
397 {
398 return true;
399 }
400 else if ( checkSafeTiles && map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height] )
401 {
402 return true;
403 }
404 bool isMonster = false;
405 if ( entity )
406 if ( entity->behavior == &actMonster )
407 {
408 isMonster = true;
409 }
410 if ( (swimmingtiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] || lavatiles[map.tiles[z + y * MAPLAYERS + x * MAPLAYERS * map.height]] )
411 && isMonster )
412 {
413 return true;
414 }
415 }
416 }
417 }
418 }
419 }
420 return false;
421 }
422
423 /*-------------------------------------------------------------------------------
424
425 entityInsideEntity
426
427 checks whether an entity is intersecting another entity
428
429 -------------------------------------------------------------------------------*/
430
entityInsideEntity(Entity * entity1,Entity * entity2)431 bool entityInsideEntity(Entity* entity1, Entity* entity2)
432 {
433 if ( entity1->x + entity1->sizex > entity2->x - entity2->sizex )
434 {
435 if ( entity1->x - entity1->sizex < entity2->x + entity2->sizex )
436 {
437 if ( entity1->y + entity1->sizey > entity2->y - entity2->sizey )
438 {
439 if ( entity1->y - entity1->sizey < entity2->y + entity2->sizey )
440 {
441 return true;
442 }
443 }
444 }
445 }
446 return false;
447 }
448
449 /*-------------------------------------------------------------------------------
450
451 entityInsideSomething
452
453 checks whether an entity is intersecting any obstacle
454
455 -------------------------------------------------------------------------------*/
456
entityInsideSomething(Entity * entity)457 bool entityInsideSomething(Entity* entity)
458 {
459 node_t* node;
460 int z;
461 int x, y;
462 #ifdef __ARM_NEON__
463 int32x2_t xy = vcvt_s32_f32(vmul_n_f32(vld1_f32(&entity->x), 1.0f/16.0f));
464 x = xy[0];
465 y = xy[1];
466 #else
467 x = (long)floor(entity->x / 16);
468 y = (long)floor(entity->y / 16);
469 #endif
470 // test against the map
471 for ( z = 0; z < MAPLAYERS; ++z )
472 {
473 if ( entityInsideTile(entity, x, y, z) )
474 {
475 return true;
476 }
477 }
478
479 // test against entities
480 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(entity, 2);
481 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
482 {
483 list_t* currentList = *it;
484 for ( node = currentList->first; node != nullptr; node = node->next )
485 {
486 Entity* testEntity = (Entity*)node->element;
487 if ( testEntity == entity || testEntity->flags[PASSABLE] )
488 {
489 continue;
490 }
491 if ( entityInsideEntity(entity, testEntity) )
492 {
493 return true;
494 }
495 }
496 }
497
498 return false;
499 }
500
501 /*-------------------------------------------------------------------------------
502
503 barony_clear
504
505 checks all blocks around tx/ty
506
507 -------------------------------------------------------------------------------*/
508
barony_clear(real_t tx,real_t ty,Entity * my)509 int barony_clear(real_t tx, real_t ty, Entity* my)
510 {
511 if (!my)
512 {
513 return 1;
514 }
515
516 long x, y;
517 real_t tx2, ty2;
518 node_t* node;
519 Entity* entity;
520 bool levitating = false;
521 // Reworked that function to break the loop in two part.
522 // A first fast one using integer only x/y
523 // And the second part that loop on entity and used a global BoundingBox collision detection
524 // Also, static stuff are out of the loop too
525
526 Stat* stats = my->getStats();
527 // moved static stuff outside of the loop
528 if ( stats )
529 {
530 levitating = isLevitating(stats);
531 }
532 bool isMonster = false;
533 if ( my )
534 {
535 if ( my->behavior == &actMonster )
536 {
537 isMonster = true;
538 }
539 }
540 if ( isMonster && multiplayer == CLIENT )
541 {
542 if ( my->sprite == 289 || my->sprite == 274 || my->sprite == 413 ) // imp and lich and cockatrice
543 {
544 levitating = true;
545 }
546 }
547 if ( my )
548 {
549 if ( my->behavior != &actPlayer && my->behavior != &actMonster )
550 {
551 levitating = true;
552 }
553 }
554
555 long ymin = floor((ty - my->sizey)/16), ymax = floor((ty + my->sizey)/16);
556 long xmin = floor((tx - my->sizex)/16), xmax = floor((tx + my->sizex)/16);
557 const real_t tymin = ty - my->sizey, tymax = ty + my->sizey;
558 const real_t txmin = tx - my->sizex, txmax = tx + my->sizex;
559 for ( y = ymin; y <= ymax; y++ )
560 {
561 for ( x = xmin; x <= xmax; x++ )
562 {
563 if ( x >= 0 && y >= 0 && x < map.width && y < map.height )
564 {
565 if (map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height])
566 {
567 // hit a wall
568 hit.x = x * 16 + 8;
569 hit.y = y * 16 + 8;
570 hit.mapx = x;
571 hit.mapy = y;
572 hit.entity = NULL;
573 return 0;
574 }
575
576 if ( !levitating && (!map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]
577 || ((swimmingtiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]] || lavatiles[map.tiles[y * MAPLAYERS + x * MAPLAYERS * map.height]])
578 && isMonster)) )
579 {
580 // no floor
581 hit.x = x * 16 + 8;
582 hit.y = y * 16 + 8;
583 hit.mapx = x;
584 hit.mapy = y;
585 hit.entity = NULL;
586 return 0;
587 }
588 }
589 }
590 }
591 std::vector<list_t*> entLists;
592 if ( multiplayer == CLIENT )
593 {
594 entLists.push_back(map.entities); // clients use old map.entities method
595 }
596 else
597 {
598 entLists = TileEntityList.getEntitiesWithinRadius(static_cast<int>(tx) >> 4, static_cast<int>(ty) >> 4, 2);
599 }
600 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
601 {
602 list_t* currentList = *it;
603 for ( node = currentList->first; node != nullptr; node = node->next )
604 {
605 entity = (Entity*)node->element;
606 if ( entity == my || my->parent == entity->getUID() )
607 {
608 continue;
609 }
610 if ( entity->flags[PASSABLE] )
611 {
612 if ( my->behavior == &actBoulder && entity->sprite == 886 )
613 {
614 // 886 is gyrobot, as they are passable, force collision here.
615 }
616 else
617 {
618 continue;
619 }
620 }
621 if ( entity->behavior == &actParticleTimer && static_cast<Uint32>(entity->particleTimerTarget) == my->getUID() )
622 {
623 continue;
624 }
625 if ( (my->behavior == &actMonster || my->behavior == &actBoulder) && entity->behavior == &actDoorFrame )
626 {
627 continue; // monsters don't have hard collision with door frames
628 }
629 Stat* myStats = stats; //my->getStats(); //SEB <<<
630 Stat* yourStats = entity->getStats();
631 if ( my->behavior == &actPlayer && entity->behavior == &actPlayer )
632 {
633 continue;
634 }
635 if ( myStats && yourStats )
636 {
637 if ( yourStats->leader_uid == my->getUID() )
638 {
639 continue;
640 }
641 if ( myStats->leader_uid == entity->getUID() )
642 {
643 continue;
644 }
645 if ( entity->behavior == &actMonster && yourStats->type == NOTHING && multiplayer == CLIENT )
646 {
647 // client doesn't know about the type of the monster.
648 yourStats->type = static_cast<Monster>(entity->getMonsterTypeFromSprite());
649 }
650 if ( monsterally[myStats->type][yourStats->type] )
651 {
652 if ( my->behavior == &actPlayer && myStats->type != HUMAN )
653 {
654 if ( my->checkFriend(entity) )
655 {
656 continue;
657 }
658 }
659 else if ( my->behavior == &actMonster && entity->behavior == &actPlayer )
660 {
661 if ( my->checkFriend(entity) )
662 {
663 continue;
664 }
665 }
666 else
667 {
668 if ( my->behavior == &actPlayer && yourStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY
669 || entity->behavior == &actPlayer && myStats->monsterForceAllegiance == Stat::MONSTER_FORCE_PLAYER_ENEMY )
670 {
671 // forced enemies.
672 }
673 else
674 {
675 continue;
676 }
677 }
678 }
679 else if ( my->behavior == &actPlayer )
680 {
681 if ( my->checkFriend(entity) )
682 {
683 continue;
684 }
685 }
686 if ( (myStats->type == HUMAN || my->flags[USERFLAG2]) && (yourStats->type == HUMAN || entity->flags[USERFLAG2]) )
687 {
688 continue;
689 }
690 }
691 if ( multiplayer == CLIENT )
692 {
693 // fixes bug where clients can't move through humans
694 if ( entity->isPlayerHeadSprite() ||
695 entity->sprite == 217 ) // human heads (217 is shopkeep)
696 {
697 continue;
698 }
699 else if ( my->behavior == &actPlayer && entity->flags[USERFLAG2] )
700 {
701 continue; // fix clients not being able to walk through friendly monsters
702 }
703 }
704 const real_t eymin = entity->y - entity->sizey, eymax = entity->y + entity->sizey;
705 const real_t exmin = entity->x - entity->sizex, exmax = entity->x + entity->sizex;
706 if ( (entity->sizex > 0) && ((txmin >= exmin && txmin < exmax) || (txmax >= exmin && txmax < exmax) || (txmin <= exmin && txmax > exmax)) )
707 {
708 if ( (entity->sizey > 0) && ((tymin >= eymin && tymin < eymax) || (tymax >= eymin && tymax < eymax) || (tymin <= eymin && tymax > eymax)) )
709 {
710 tx2 = std::max(txmin, exmin);
711 ty2 = std::max(tymin, eymin);
712 hit.x = tx2;
713 hit.y = ty2;
714 hit.mapx = entity->x / 16;
715 hit.mapy = entity->y / 16;
716 hit.entity = entity;
717 if ( multiplayer != CLIENT )
718 {
719 if ( my->flags[BURNING] && !hit.entity->flags[BURNING] && hit.entity->flags[BURNABLE] )
720 {
721 bool dyrnwyn = false;
722 Stat* stats = hit.entity->getStats();
723 if ( stats )
724 {
725 if ( stats->weapon )
726 {
727 if ( stats->weapon->type == ARTIFACT_SWORD )
728 {
729 dyrnwyn = true;
730 }
731 }
732 }
733 if ( !dyrnwyn )
734 {
735 bool previouslyOnFire = hit.entity->flags[BURNING];
736
737 // Attempt to set the Entity on fire
738 hit.entity->SetEntityOnFire();
739
740 // If the Entity is now on fire, tell them
741 if ( hit.entity->flags[BURNING] && !previouslyOnFire )
742 {
743 messagePlayer(hit.entity->skill[2], language[590]); // "You suddenly catch fire!"
744 }
745 }
746 }
747 else if ( hit.entity->flags[BURNING] && !my->flags[BURNING] && my->flags[BURNABLE] )
748 {
749 bool dyrnwyn = false;
750 Stat* stats = my->getStats();
751 if ( stats )
752 {
753 if ( stats->weapon )
754 {
755 if ( stats->weapon->type == ARTIFACT_SWORD )
756 {
757 dyrnwyn = true;
758 }
759 }
760 }
761 if ( !dyrnwyn )
762 {
763 bool previouslyOnFire = hit.entity->flags[BURNING];
764
765 // Attempt to set the Entity on fire
766 hit.entity->SetEntityOnFire();
767
768 // If the Entity is now on fire, tell them
769 if ( hit.entity->flags[BURNING] && !previouslyOnFire )
770 {
771 messagePlayer(hit.entity->skill[2], language[590]); // "You suddenly catch fire!"
772 }
773 }
774 }
775 }
776 return 0;
777 }
778 }
779 }
780 }
781
782 return 1;
783 }
784
785 /*-------------------------------------------------------------------------------
786
787 clipMove
788
789 clips velocity by checking which direction is clear. returns distance
790 covered.
791
792 -------------------------------------------------------------------------------*/
793
clipMove(real_t * x,real_t * y,real_t vx,real_t vy,Entity * my)794 real_t clipMove(real_t* x, real_t* y, real_t vx, real_t vy, Entity* my)
795 {
796 real_t tx, ty;
797 hit.entity = NULL;
798
799 // move x and y
800 tx = *x + vx;
801 ty = *y + vy;
802 if (barony_clear(tx, ty, my))
803 {
804 *x = tx;
805 *y = ty;
806 hit.side = 0;
807 return sqrt(vx * vx + vy * vy);
808 }
809
810 // only move x
811 tx = *x + vx;
812 ty = *y;
813 if (barony_clear(tx, ty, my))
814 {
815 *x = tx;
816 *y = ty;
817 hit.side = VERTICAL;
818 return fabs(vx);
819 }
820
821 // only move y
822 tx = *x;
823 ty = *y + vy;
824 if (barony_clear(tx, ty, my))
825 {
826 *x = tx;
827 *y = ty;
828 hit.side = HORIZONTAL;
829 return fabs(vy);
830 }
831 hit.side = 0;
832 return 0;
833 }
834
835 /*-------------------------------------------------------------------------------
836
837 findEntityInLine
838
839 returns the closest entity to intersect a ray starting from x1, y1 and
840 extending along the given angle. May return an improper result when
841 some entities overlap one another.
842
843 -------------------------------------------------------------------------------*/
844
findEntityInLine(Entity * my,real_t x1,real_t y1,real_t angle,int entities,Entity * target)845 Entity* findEntityInLine( Entity* my, real_t x1, real_t y1, real_t angle, int entities, Entity* target )
846 {
847 Entity* result = NULL;
848 node_t* node;
849 real_t lowestDist = 9999;
850 int quadrant = 0;
851
852 while ( angle >= PI * 2 )
853 {
854 angle -= PI * 2;
855 }
856 while ( angle < 0 )
857 {
858 angle += PI * 2;
859 }
860
861 int originx = static_cast<int>(my->x) >> 4;
862 int originy = static_cast<int>(my->y) >> 4;
863 std::vector<list_t*> entLists; // stores the possible entities to look through depending on the quadrant.
864 // start search from 1 tile behind facing direction in x/y position, extending to the edge of the map in the facing direction.
865
866 if ( multiplayer == CLIENT )
867 {
868 entLists.push_back(map.entities); // default to old map.entities if client (if they ever call this function...)
869 }
870
871 if ( angle >= PI / 2 && angle < PI ) // -x, +y
872 {
873 quadrant = 1;
874 /*messagePlayer(0, "drawing from x: %d - %d, y: %d- %d",
875 0, std::min(static_cast<int>(map.width) - 1, originx + 1),
876 std::max(0, originy - 1), map.height - 1);*/
877
878 if ( multiplayer != CLIENT )
879 {
880 for ( int ix = std::min(static_cast<int>(map.width) - 1, originx + 1); ix >= 0; --ix )
881 {
882 for ( int iy = std::max(0, originy - 1); iy < map.height; ++iy )
883 {
884 entLists.push_back(&TileEntityList.gridEntities[ix][iy]);
885 }
886 }
887 }
888 }
889 else if ( angle >= 0 && angle < PI / 2 ) // +x, +y
890 {
891 quadrant = 2;
892 /*messagePlayer(0, "drawing from x: %d - %d, y: %d- %d",
893 std::max(0, originx - 1), map.width - 1,
894 std::max(0, originy - 1), map.height - 1);*/
895
896 if ( multiplayer != CLIENT )
897 {
898 for ( int ix = std::max(0, originx - 1); ix < map.width; ++ix )
899 {
900 for ( int iy = std::max(0, originy - 1); iy < map.height; ++iy )
901 {
902 entLists.push_back(&TileEntityList.gridEntities[ix][iy]);
903 }
904 }
905 }
906 }
907 else if ( angle >= 3 * (PI / 2) && angle < PI * 2 ) // +x, -y
908 {
909 quadrant = 3;
910 /*messagePlayer(0, "drawing from x: %d - %d, y: %d- %d",
911 std::max(0, originx - 1), map.width - 1,
912 0, std::min(static_cast<int>(map.height) - 1, originy + 1));*/
913
914 if ( multiplayer != CLIENT )
915 {
916 for ( int ix = std::max(0, originx - 1); ix < map.width; ++ix )
917 {
918 for ( int iy = std::min(static_cast<int>(map.height) - 1, originy + 1); iy >= 0; --iy )
919 {
920 entLists.push_back(&TileEntityList.gridEntities[ix][iy]);
921 }
922 }
923 }
924 }
925 else // -x, -y
926 {
927 quadrant = 4;
928 /*messagePlayer(0, "drawing from x: %d - %d, y: %d- %d",
929 0, std::min(static_cast<int>(map.width) - 1, originx + 1),
930 0, std::min(static_cast<int>(map.height) - 1, originy + 1));*/
931
932 if ( multiplayer != CLIENT )
933 {
934 for ( int ix = std::min(static_cast<int>(map.width) - 1, originx + 1); ix >= 0; --ix )
935 {
936 for ( int iy = std::min(static_cast<int>(map.height) - 1, originy + 1); iy >= 0; --iy )
937 {
938 entLists.push_back(&TileEntityList.gridEntities[ix][iy]);
939 }
940 }
941 }
942 }
943
944 bool adjust = false;
945 if ( angle >= PI / 2 && angle < 3 * (PI / 2) )
946 {
947 adjust = true;
948 }
949 else
950 {
951 while ( angle >= PI )
952 {
953 angle -= PI * 2;
954 }
955 while ( angle < -PI )
956 {
957 angle += PI * 2;
958 }
959 }
960
961 //std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();
962
963 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
964 {
965 list_t* currentList = *it;
966 for ( node = currentList->first; node != nullptr; node = node->next )
967 {
968 Entity* entity = (Entity*)node->element;
969 if ( (entity != target && target != nullptr) || entity->flags[PASSABLE] || entity == my
970 || (entities &&
971 ( (!entity->flags[BLOCKSIGHT] && entity->behavior != &actMonster)
972 || (entity->behavior == &actMonster && (entity->flags[INVISIBLE] && entity->sprite != 889) )
973 )
974 )
975 )
976 {
977 // if entities == 1, then ignore entities that block sight.
978 // 16/11/19 - added exception to monsters. if monster, use the INVISIBLE flag to skip checking.
979 // 889 is dummybot "invisible" AI entity. so it's invisible, need to make it shown here.
980 continue;
981 }
982 if ( entity->behavior == &actParticleTimer )
983 {
984 continue;
985 }
986
987 if ( quadrant == 2 || quadrant == 4 )
988 {
989 // upper right and lower left
990 real_t upperX = entity->x + entity->sizex;
991 real_t upperY = entity->y - entity->sizey;
992 real_t lowerX = entity->x - entity->sizex;
993 real_t lowerY = entity->y + entity->sizey;
994 real_t upperTan = atan2(upperY - y1, upperX - x1);
995 real_t lowerTan = atan2(lowerY - y1, lowerX - x1);
996 if ( adjust )
997 {
998 if ( upperTan < 0 )
999 {
1000 upperTan += PI * 2;
1001 }
1002 if ( lowerTan < 0 )
1003 {
1004 lowerTan += PI * 2;
1005 }
1006 }
1007
1008 // determine whether line intersects entity
1009 if ( quadrant == 2 )
1010 {
1011 if ( angle >= upperTan && angle <= lowerTan )
1012 {
1013 real_t dist = sqrt(pow(x1 - entity->x, 2) + pow(y1 - entity->y, 2));
1014 if ( dist < lowestDist )
1015 {
1016 lowestDist = dist;
1017 result = entity;
1018 }
1019 }
1020 }
1021 else
1022 {
1023 if ( angle <= upperTan && angle >= lowerTan )
1024 {
1025 real_t dist = sqrt(pow(x1 - entity->x, 2) + pow(y1 - entity->y, 2));
1026 if ( dist < lowestDist )
1027 {
1028 lowestDist = dist;
1029 result = entity;
1030 }
1031 }
1032 }
1033 }
1034 else
1035 {
1036 // upper left and lower right
1037 real_t upperX = entity->x - entity->sizex;
1038 real_t upperY = entity->y - entity->sizey;
1039 real_t lowerX = entity->x + entity->sizex;
1040 real_t lowerY = entity->y + entity->sizey;
1041 real_t upperTan = atan2(upperY - y1, upperX - x1);
1042 real_t lowerTan = atan2(lowerY - y1, lowerX - x1);
1043 if ( adjust )
1044 {
1045 if ( upperTan < 0 )
1046 {
1047 upperTan += PI * 2;
1048 }
1049 if ( lowerTan < 0 )
1050 {
1051 lowerTan += PI * 2;
1052 }
1053 }
1054
1055 // determine whether line intersects entity
1056 if ( quadrant == 3 )
1057 {
1058 if ( angle >= upperTan && angle <= lowerTan )
1059 {
1060 real_t dist = sqrt(pow(x1 - entity->x, 2) + pow(y1 - entity->y, 2));
1061 if ( dist < lowestDist )
1062 {
1063 lowestDist = dist;
1064 result = entity;
1065 }
1066 }
1067 }
1068 else
1069 {
1070 if ( angle <= upperTan && angle >= lowerTan )
1071 {
1072 real_t dist = sqrt(pow(x1 - entity->x, 2) + pow(y1 - entity->y, 2));
1073 if ( dist < lowestDist )
1074 {
1075 lowestDist = dist;
1076 result = entity;
1077 }
1078 }
1079 }
1080 }
1081 }
1082 }
1083 //std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
1084 //messagePlayer(0, "%lld", std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1));
1085 return result;
1086 }
1087
1088 /*-------------------------------------------------------------------------------
1089
1090 lineTrace
1091
1092 Trace a line from x1, y1 along the provided heading, place information of
1093 the first hit obstacle into the "hit" struct, and report distance to
1094 next obstacle. Uses entity coordinates
1095
1096 -------------------------------------------------------------------------------*/
1097
lineTrace(Entity * my,real_t x1,real_t y1,real_t angle,real_t range,int entities,bool ground)1098 real_t lineTrace( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground )
1099 {
1100 int posx, posy;
1101 real_t fracx, fracy;
1102 real_t rx, ry;
1103 real_t ix, iy;
1104 int inx, iny;
1105 real_t arx, ary;
1106 real_t dincx, dval0, dincy, dval1;
1107 real_t d;
1108
1109 posx = floor(x1);
1110 posy = floor(y1); // integer coordinates
1111 fracx = x1 - posx;
1112 fracy = y1 - posy; // fraction coordinates
1113 rx = cos(angle);
1114 ry = sin(angle);
1115 ix = 0;
1116 iy = 0;
1117
1118 inx = posx;
1119 iny = posy;
1120 arx = 0;
1121 if (rx)
1122 {
1123 arx = 1.0 / fabs(rx);
1124 }
1125 ary = 0;
1126 if (ry)
1127 {
1128 ary = 1.0 / fabs(ry);
1129 }
1130 dincx = 0;
1131 dval0 = 1e32;
1132 dincy = 0;
1133 dval1 = 1e32;
1134 if (rx < 0)
1135 {
1136 dincx = -1;
1137 dval0 = fracx * arx;
1138 }
1139 else if (rx > 0)
1140 {
1141 dincx = 1;
1142 dval0 = (1.0 - fracx) * arx;
1143 }
1144 if (ry < 0)
1145 {
1146 dincy = -1;
1147 dval1 = fracy * ary;
1148 }
1149 else if (ry > 0)
1150 {
1151 dincy = 1;
1152 dval1 = (1.0 - fracy) * ary;
1153 }
1154 d = 0;
1155
1156 if ( my )
1157 {
1158 Stat* stats = my->getStats();
1159 if ( stats )
1160 {
1161 if ( stats->type == DEVIL )
1162 {
1163 ground = false;
1164 }
1165 else if ( stats->type == SENTRYBOT || stats->type == SPELLBOT )
1166 {
1167 ground = false;
1168 }
1169 }
1170 }
1171
1172 Entity* entity = findEntityInLine(my, x1, y1, angle, entities, NULL);
1173
1174 // trace the line
1175 while ( d < range )
1176 {
1177 if ( dval1 > dval0 )
1178 {
1179 inx += dincx;
1180 d = dval0;
1181 dval0 += arx;
1182 hit.side = HORIZONTAL;
1183 }
1184 else
1185 {
1186 iny += dincy;
1187 d = dval1;
1188 dval1 += ary;
1189 hit.side = VERTICAL;
1190 }
1191 if ( inx < 0 || iny < 0 || (inx >> 4) >= map.width || (iny >> 4) >= map.height )
1192 {
1193 break;
1194 }
1195
1196 ix = x1 + rx * d;
1197 iy = y1 + ry * d;
1198
1199 // check against the map
1200 int index = (iny >> 4) * MAPLAYERS + (inx >> 4) * MAPLAYERS * map.height;
1201 if ( map.tiles[OBSTACLELAYER + index] )
1202 {
1203 hit.x = ix;
1204 hit.y = iy;
1205 hit.mapx = inx >> 4;
1206 hit.mapy = iny >> 4;
1207 hit.entity = NULL;
1208 return d;
1209 }
1210 if ( ground )
1211 {
1212 bool isMonster = false;
1213 if ( my )
1214 if ( my->behavior == &actMonster )
1215 {
1216 isMonster = true;
1217 }
1218 if ( !map.tiles[index]
1219 || ((swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) && isMonster) )
1220 {
1221 hit.x = ix;
1222 hit.y = iy;
1223 hit.mapx = inx >> 4;
1224 hit.mapy = iny >> 4;
1225 hit.entity = NULL;
1226 return d;
1227 }
1228 }
1229
1230 // check against entity
1231 if ( entity )
1232 {
1233 // debug particles.
1234 //if ( entity->behavior == &actMonster && entities != 0 )
1235 //{
1236 // Entity* particle = spawnMagicParticle(my);
1237 // particle->sprite = 576;
1238 // particle->x = ix;
1239 // particle->y = iy;
1240 // particle->z = 0;
1241
1242 // particle = spawnMagicParticle(my);
1243 // particle->sprite = 942;
1244 // particle->x = entity->x + entity->sizex;
1245 // particle->y = entity->y + entity->sizey;
1246 // particle->z = 0;
1247
1248 // particle = spawnMagicParticle(my);
1249 // particle->sprite = 942;
1250 // particle->x = entity->x - entity->sizex;
1251 // particle->y = entity->y + entity->sizey;
1252 // particle->z = 0;
1253
1254 // particle = spawnMagicParticle(my);
1255 // particle->sprite = 942;
1256 // particle->x = entity->x + entity->sizex;
1257 // particle->y = entity->y - entity->sizey;
1258 // particle->z = 0;
1259
1260 // particle = spawnMagicParticle(my);
1261 // particle->sprite = 942;
1262 // particle->x = entity->x - entity->sizex;
1263 // particle->y = entity->y - entity->sizey;
1264 // particle->z = 0;
1265 //}
1266
1267 if ( ix >= entity->x - entity->sizex && ix <= entity->x + entity->sizex )
1268 {
1269 if ( iy >= entity->y - entity->sizey && iy <= entity->y + entity->sizey )
1270 {
1271 hit.x = ix;
1272 hit.y = iy;
1273 hit.mapx = entity->x / 16;
1274 hit.mapy = entity->y / 16;
1275 hit.entity = entity;
1276 return d;
1277 }
1278 }
1279 }
1280 }
1281 hit.x = ix;
1282 hit.y = iy;
1283 hit.mapx = inx >> 4;
1284 hit.mapy = iny >> 4;
1285 hit.entity = NULL;
1286 hit.side = 0;
1287 return range;
1288 }
1289
lineTraceTarget(Entity * my,real_t x1,real_t y1,real_t angle,real_t range,int entities,bool ground,Entity * target)1290 real_t lineTraceTarget( Entity* my, real_t x1, real_t y1, real_t angle, real_t range, int entities, bool ground, Entity* target )
1291 {
1292 int posx, posy;
1293 real_t fracx, fracy;
1294 real_t rx, ry;
1295 real_t ix, iy;
1296 int inx, iny;
1297 real_t arx, ary;
1298 real_t dincx, dval0, dincy, dval1;
1299 real_t d;
1300
1301 posx = floor(x1);
1302 posy = floor(y1); // integer coordinates
1303 fracx = x1 - posx;
1304 fracy = y1 - posy; // fraction coordinates
1305 rx = cos(angle);
1306 ry = sin(angle);
1307 ix = 0;
1308 iy = 0;
1309
1310 inx = posx;
1311 iny = posy;
1312 arx = 0;
1313 if (rx)
1314 {
1315 arx = 1.0 / fabs(rx);
1316 }
1317 ary = 0;
1318 if (ry)
1319 {
1320 ary = 1.0 / fabs(ry);
1321 }
1322 dincx = 0;
1323 dval0 = 1e32;
1324 dincy = 0;
1325 dval1 = 1e32;
1326 if (rx < 0)
1327 {
1328 dincx = -1;
1329 dval0 = fracx * arx;
1330 }
1331 else if (rx > 0)
1332 {
1333 dincx = 1;
1334 dval0 = (1.0 - fracx) * arx;
1335 }
1336 if (ry < 0)
1337 {
1338 dincy = -1;
1339 dval1 = fracy * ary;
1340 }
1341 else if (ry > 0)
1342 {
1343 dincy = 1;
1344 dval1 = (1.0 - fracy) * ary;
1345 }
1346 d = 0;
1347
1348 Entity* entity = findEntityInLine(my, x1, y1, angle, entities, target);
1349
1350 // trace the line
1351 while ( d < range )
1352 {
1353 if ( dval1 > dval0 )
1354 {
1355 inx += dincx;
1356 d = dval0;
1357 dval0 += arx;
1358 hit.side = HORIZONTAL;
1359 }
1360 else
1361 {
1362 iny += dincy;
1363 d = dval1;
1364 dval1 += ary;
1365 hit.side = VERTICAL;
1366 }
1367 if ( inx < 0 || iny < 0 || (inx >> 4) >= map.width || (iny >> 4) >= map.height )
1368 {
1369 break;
1370 }
1371
1372 ix = x1 + rx * d;
1373 iy = y1 + ry * d;
1374
1375 // check against the map
1376 int index = (iny >> 4) * MAPLAYERS + (inx >> 4) * MAPLAYERS * map.height;
1377 if ( map.tiles[OBSTACLELAYER + index] )
1378 {
1379 hit.x = ix;
1380 hit.y = iy;
1381 hit.mapx = inx >> 4;
1382 hit.mapy = iny >> 4;
1383 hit.entity = NULL;
1384 return d;
1385 }
1386 if ( ground )
1387 {
1388 bool isMonster = false;
1389 if ( my )
1390 if ( my->behavior == &actMonster )
1391 {
1392 isMonster = true;
1393 }
1394 if ( !map.tiles[index]
1395 || ((swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]]) && isMonster) )
1396 {
1397 hit.x = ix;
1398 hit.y = iy;
1399 hit.mapx = inx >> 4;
1400 hit.mapy = iny >> 4;
1401 hit.entity = NULL;
1402 return d;
1403 }
1404 }
1405
1406 // check against entity
1407 if ( entity )
1408 {
1409 if ( ix >= entity->x - entity->sizex && ix <= entity->x + entity->sizex )
1410 {
1411 if ( iy >= entity->y - entity->sizey && iy <= entity->y + entity->sizey )
1412 {
1413 hit.x = ix;
1414 hit.y = iy;
1415 hit.mapx = entity->x / 16;
1416 hit.mapy = entity->y / 16;
1417 hit.entity = entity;
1418 return d;
1419 }
1420 }
1421 }
1422 }
1423 hit.x = ix;
1424 hit.y = iy;
1425 hit.mapx = inx >> 4;
1426 hit.mapy = iny >> 4;
1427 hit.entity = NULL;
1428 hit.side = 0;
1429 return range;
1430 }
1431
1432 /*-------------------------------------------------------------------------------
1433
1434 checkObstacle
1435
1436 Checks the environment at the given ENTITY coordinates for obstacles,
1437 performing boundary check
1438
1439 -------------------------------------------------------------------------------*/
1440
checkObstacle(long x,long y,Entity * my,Entity * target)1441 int checkObstacle(long x, long y, Entity* my, Entity* target)
1442 {
1443 node_t* node;
1444 Entity* entity;
1445 Stat* stats;
1446 bool levitating = false;
1447
1448 // get levitation status
1449 if ( my != NULL && (stats = my->getStats()) != NULL )
1450 {
1451 levitating = isLevitating(stats);
1452 }
1453 if ( my )
1454 {
1455 if ( my->behavior != &actPlayer && my->behavior != &actMonster && my->behavior != &actLadder && my->behavior != &actPortal )
1456 {
1457 levitating = true;
1458 }
1459 }
1460
1461 // collision detection
1462 if ( x >= 0 && x < map.width << 4 )
1463 {
1464 if ( y >= 0 && y < map.height << 4 )
1465 {
1466 int index = (y >> 4) * MAPLAYERS + (x >> 4) * MAPLAYERS * map.height;
1467 if (map.tiles[OBSTACLELAYER + index]) // wall
1468 {
1469 return 1;
1470 }
1471 bool isMonster = false;
1472 if ( my )
1473 {
1474 if ( my->behavior == &actMonster )
1475 {
1476 isMonster = true;
1477 }
1478 }
1479 if ( !levitating
1480 && (!map.tiles[index]
1481 || ( (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]])
1482 && isMonster) ) ) // no floor
1483 {
1484 return 1; // if there's no floor, or either water/lava then a non-levitating monster sees obstacle.
1485 }
1486
1487 std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadius(static_cast<int>(x) >> 4, static_cast<int>(y) >> 4, 2);
1488 for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1489 {
1490 list_t* currentList = *it;
1491 for ( node = currentList->first; node != nullptr; node = node->next )
1492 {
1493 entity = (Entity*)node->element;
1494 if ( entity->flags[PASSABLE] || entity == my || entity == target || entity->behavior == &actDoor )
1495 {
1496 continue;
1497 }
1498 if ( entity->behavior == &actParticleTimer && static_cast<Uint32>(entity->particleTimerTarget) == my->getUID() )
1499 {
1500 continue;
1501 }
1502 if ( x >= (int)(entity->x - entity->sizex) && x <= (int)(entity->x + entity->sizex) )
1503 {
1504 if ( y >= (int)(entity->y - entity->sizey) && y <= (int)(entity->y + entity->sizey) )
1505 {
1506 return 1;
1507 }
1508 }
1509 }
1510 }
1511 }
1512 }
1513
1514 if ( logCheckObstacle )
1515 {
1516 ++logCheckObstacleCount;
1517 }
1518
1519 return 0;
1520 }
1521