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