1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: actbeartrap.cpp
5 	Desc: behavior function for set beartraps
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 "sound.hpp"
17 #include "entity.hpp"
18 #include "items.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23 #include "scores.hpp"
24 #include "monster.hpp"
25 
26 /*-------------------------------------------------------------------------------
27 
28 	act*
29 
30 	The following function describes an entity behavior. The function
31 	takes a pointer to the entity that uses it as an argument.
32 
33 -------------------------------------------------------------------------------*/
34 
35 #define BEARTRAP_CAUGHT my->skill[0]
36 #define BEARTRAP_STATUS my->skill[11]
37 #define BEARTRAP_BEATITUDE my->skill[12]
38 #define BEARTRAP_APPEARANCE my->skill[14]
39 #define BEARTRAP_IDENTIFIED my->skill[15]
40 #define BEARTRAP_OWNER my->skill[17]
41 
actBeartrap(Entity * my)42 void actBeartrap(Entity* my)
43 {
44 	int i;
45 	if ( my->sprite == 667 )
46 	{
47 		my->roll = 0;
48 		my->z = 6.75;
49 	}
50 
51 	if ( multiplayer == CLIENT )
52 	{
53 		return;
54 	}
55 
56 	// undo beartrap
57 	for (i = 0; i < MAXPLAYERS; i++)
58 	{
59 		if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
60 		{
61 			if (inrange[i])
62 			{
63 				Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
64 				entity->flags[INVISIBLE] = true;
65 				entity->flags[UPDATENEEDED] = true;
66 				entity->flags[PASSABLE] = true;
67 				entity->x = my->x;
68 				entity->y = my->y;
69 				entity->z = my->z;
70 				entity->sizex = my->sizex;
71 				entity->sizey = my->sizey;
72 				entity->yaw = my->yaw;
73 				entity->pitch = my->pitch;
74 				entity->roll = PI / 2;
75 				entity->behavior = &actItem;
76 				entity->skill[10] = TOOL_BEARTRAP;
77 				entity->skill[11] = BEARTRAP_STATUS;
78 				entity->skill[12] = BEARTRAP_BEATITUDE;
79 				entity->skill[13] = 1;
80 				entity->skill[14] = BEARTRAP_APPEARANCE;
81 				entity->skill[15] = BEARTRAP_IDENTIFIED;
82 				entity->itemNotMoving = 1;
83 				entity->itemNotMovingClient = 1;
84 				messagePlayer(i, language[1300]);
85 				list_RemoveNode(my->mynode);
86 				return;
87 			}
88 		}
89 	}
90 
91 	if ( BEARTRAP_CAUGHT == 1 )
92 	{
93 		return;
94 	}
95 
96 	// launch beartrap
97 	node_t* node;
98 	for ( node = map.creatures->first; node != nullptr; node = node->next )
99 	{
100 		Entity* entity = (Entity*)node->element;
101 		if ( my->parent == entity->getUID() )
102 		{
103 			continue;
104 		}
105 		if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
106 		{
107 			Stat* stat = entity->getStats();
108 			if ( stat )
109 			{
110 				Entity* parent = uidToEntity(my->parent);
111 				if ( (parent && parent->checkFriend(entity)) )
112 				{
113 					continue;
114 				}
115 				if ( !parent && BEARTRAP_OWNER >= 0 )
116 				{
117 					if ( entity->behavior == &actPlayer )
118 					{
119 						continue; // players won't trigger if owner dead.
120 					}
121 					else if ( entity->monsterAllyGetPlayerLeader() )
122 					{
123 						continue; // player followers won't trigger if owner dead.
124 					}
125 				}
126 				if ( entityDist(my, entity) < 6.5 )
127 				{
128 					entity->setEffect(EFF_PARALYZED, true, 200, false);
129 					entity->setEffect(EFF_BLEEDING, true, 300, false);
130 					int damage = 10 + 3 * (BEARTRAP_STATUS + BEARTRAP_BEATITUDE);
131 					if ( parent )
132 					{
133 						stat->bleedInflictedBy = static_cast<Sint32>(parent->getUID());
134 						//damage += trapperStat->PROFICIENCIES[PRO_LOCKPICKING] / 20;
135 					}
136 					int oldHP = stat->HP;
137 					//messagePlayer(0, "dmg: %d", damage);
138 					entity->modHP(-damage);
139 					//// alert the monster! DOES NOT WORK DURING PARALYZE.
140 					//if ( entity->behavior == &actMonster && entity->monsterState != MONSTER_STATE_ATTACK && (stat->type < LICH || stat->type >= SHOPKEEPER) )
141 					//{
142 					//	Entity* attackTarget = uidToEntity(my->parent);
143 					//	if ( attackTarget )
144 					//	{
145 					//		entity->monsterAcquireAttackTarget(*attackTarget, MONSTER_STATE_PATH);
146 					//	}
147 					//}
148 					// set obituary
149 					entity->setObituary(language[1504]);
150 
151 					if ( stat->HP <= 0 && oldHP > 0 )
152 					{
153 						if ( parent )
154 						{
155 							parent->awardXP( entity, true, true );
156 						}
157 					}
158 					if ( entity->behavior == &actPlayer )
159 					{
160 						int player = entity->skill[2];
161 						Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
162 						messagePlayerColor(player, color, language[454]);
163 						if ( player > 0 )
164 						{
165 							serverUpdateEffects(player);
166 						}
167 						if ( player == clientnum || splitscreen )
168 						{
169 							cameravars[entity->skill[2]].shakex += .1;
170 							cameravars[entity->skill[2]].shakey += 10;
171 						}
172 						else if ( player > 0 && multiplayer == SERVER )
173 						{
174 							strcpy((char*)net_packet->data, "SHAK");
175 							net_packet->data[4] = 10; // turns into .1
176 							net_packet->data[5] = 10;
177 							net_packet->address.host = net_clients[player - 1].host;
178 							net_packet->address.port = net_clients[player - 1].port;
179 							net_packet->len = 6;
180 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
181 						}
182 					}
183 					else if ( parent && parent->behavior == &actPlayer )
184 					{
185 						int player = parent->skill[2];
186 						if ( player >= 0 )
187 						{
188 							if ( oldHP > 0 )
189 							{
190 								if ( entityDist(my, parent) >= 64 && entityDist(my, parent) < 128 )
191 								{
192 									messagePlayer(player, language[2521]);
193 								}
194 								else
195 								{
196 									messagePlayer(player, language[2522]);
197 								}
198 								if ( rand() % 10 == 0 )
199 								{
200 									parent->increaseSkill(PRO_LOCKPICKING);
201 								}
202 								if ( rand() % 5 == 0 )
203 								{
204 									parent->increaseSkill(PRO_RANGED);
205 								}
206 							}
207 							// update enemy bar for attacker
208 							if ( !strcmp(stat->name, "") )
209 							{
210 								if ( stat->type < KOBOLD ) //Original monster count
211 								{
212 									updateEnemyBar(parent, entity, language[90 + stat->type], stat->HP, stat->MAXHP);
213 								}
214 								else if ( stat->type >= KOBOLD ) //New monsters
215 								{
216 									updateEnemyBar(parent, entity, language[2000 + (stat->type - KOBOLD)], stat->HP, stat->MAXHP);
217 								}
218 							}
219 							else
220 							{
221 								updateEnemyBar(parent, entity, stat->name, stat->HP, stat->MAXHP);
222 							}
223 						}
224 					}
225 					playSoundEntity(my, 76, 64);
226 					playSoundEntity(entity, 28, 64);
227 					Entity* gib = spawnGib(entity);
228 					serverSpawnGibForClient(gib);
229 
230 					--BEARTRAP_STATUS;
231 					if ( BEARTRAP_STATUS < DECREPIT )
232 					{
233 						// make first arm
234 						entity = newEntity(668, 1, map.entities, nullptr); //Special effect entity.
235 						entity->behavior = &actBeartrapLaunched;
236 						entity->flags[PASSABLE] = true;
237 						entity->flags[UPDATENEEDED] = true;
238 						entity->x = my->x;
239 						entity->y = my->y;
240 						entity->z = my->z + 1;
241 						entity->yaw = my->yaw;
242 						entity->pitch = my->pitch;
243 						entity->roll = 0;
244 
245 						// and then the second
246 						entity = newEntity(668, 1, map.entities, nullptr); //Special effect entity.
247 						entity->behavior = &actBeartrapLaunched;
248 						entity->flags[PASSABLE] = true;
249 						entity->flags[UPDATENEEDED] = true;
250 						entity->x = my->x;
251 						entity->y = my->y;
252 						entity->z = my->z + 1;
253 						entity->yaw = my->yaw;
254 						entity->pitch = my->pitch;
255 						entity->roll = PI;
256 						list_RemoveNode(my->mynode);
257 					}
258 					else
259 					{
260 						BEARTRAP_CAUGHT = 1;
261 						my->sprite = 667;
262 						serverUpdateEntitySkill(my, 0);
263 					}
264 					return;
265 				}
266 			}
267 		}
268 	}
269 }
270 
actBeartrapLaunched(Entity * my)271 void actBeartrapLaunched(Entity* my)
272 {
273 	if ( my->ticks >= 200 )
274 	{
275 		list_RemoveNode(my->mynode);
276 		return;
277 	}
278 }
279 
280 #define BOMB_PLACEMENT my->skill[16]
281 #define BOMB_PLAYER_OWNER my->skill[17]
282 #define BOMB_ENTITY_ATTACHED_TO my->skill[18]
283 #define BOMB_ENTITY_ATTACHED_START_HP my->skill[19]
284 #define BOMB_DIRECTION my->skill[20]
285 #define BOMB_ITEMTYPE my->skill[21]
286 #define BOMB_TRIGGER_TYPE my->skill[22]
287 #define BOMB_CHEST_STATUS my->skill[23]
288 #define BOMB_HIT_BY_PROJECTILE my->skill[24]
289 
bombDoEffect(Entity * my,Entity * triggered,real_t entityDistance,bool spawnMagicOnTriggeredMonster,bool hitByAOE)290 void bombDoEffect(Entity* my, Entity* triggered, real_t entityDistance, bool spawnMagicOnTriggeredMonster, bool hitByAOE )
291 {
292 	if ( !triggered || !my )
293 	{
294 		return;
295 	}
296 	Entity* parent = uidToEntity(my->parent);
297 	Stat* stat = triggered->getStats();
298 	Stat* parentStats = nullptr;
299 	if ( parent )
300 	{
301 		parentStats = parent->getStats();
302 	}
303 	int damage = 0;
304 	//messagePlayer(0, "dmg: %d", damage);
305 	int doSpell = SPELL_NONE;
306 	bool doVertical = false;
307 	switch ( BOMB_ITEMTYPE )
308 	{
309 		case TOOL_BOMB:
310 			doSpell = SPELL_FIREBALL;
311 			damage = 5;
312 			if ( parentStats )
313 			{
314 				damage += std::max(0, parent->getPER() / 2);
315 			}
316 			break;
317 		case TOOL_SLEEP_BOMB:
318 			doSpell = SPELL_SLEEP;
319 			damage = 0;
320 			break;
321 		case TOOL_FREEZE_BOMB:
322 			doSpell = SPELL_COLD;
323 			damage = 5;
324 			if ( parentStats )
325 			{
326 				damage += std::max(0, parent->getPER() / 4);
327 			}
328 			break;
329 		case TOOL_TELEPORT_BOMB:
330 			doSpell = SPELL_TELEPORTATION;
331 			damage = 0;
332 			break;
333 		default:
334 			break;
335 	}
336 	if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
337 	{
338 		doVertical = true;
339 	}
340 
341 	// stumbled into the trap!
342 	Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
343 	if ( parent && parent->behavior == &actPlayer && triggered != parent )
344 	{
345 		if ( !hitByAOE )
346 		{
347 			messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3498], language[3499], MSG_TOOL_BOMB, my);
348 		}
349 		else
350 		{
351 			messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3613], language[3614], MSG_TOOL_BOMB, my);
352 		}
353 	}
354 	if ( triggered->behavior == &actPlayer )
355 	{
356 		int player = triggered->skill[2];
357 		Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
358 		// you stumbled into the trap!
359 		if ( !hitByAOE )
360 		{
361 			messagePlayerColor(player, color, language[3497], items[BOMB_ITEMTYPE].name_identified);
362 		}
363 		else
364 		{
365 			messagePlayerColor(player, color, language[3612], items[BOMB_ITEMTYPE].name_identified);
366 		}
367 	}
368 
369 	if ( doSpell == SPELL_TELEPORTATION )
370 	{
371 		if ( triggered->isBossMonster() )
372 		{
373 			// no effect.
374 			if ( parent && parent->behavior == &actPlayer )
375 			{
376 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
377 				messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3603], language[3604], MSG_COMBAT);
378 			}
379 			return;
380 		}
381 		std::vector<Entity*> goodspots;
382 		bool teleported = false;
383 		for ( node_t* node = map.entities->first; node != NULL; node = node->next )
384 		{
385 			Entity* entity = (Entity*)node->element;
386 			if ( entity && entity != my && entity->behavior == &actBomb )
387 			{
388 				if ( entity->skill[21] == TOOL_TELEPORT_BOMB && entity->skill[22] == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
389 				{
390 					// receiver location.
391 					goodspots.push_back(entity);
392 				}
393 			}
394 		}
395 
396 		std::vector<Entity*> parentgoodspots; // prioritize traps from the player owner, instead of other player's traps.
397 		for ( auto it = goodspots.begin(); it != goodspots.end(); ++it )
398 		{
399 			Entity* entry = *it;
400 			if ( entry && (entry->parent == my->parent) )
401 			{
402 				parentgoodspots.push_back(entry);
403 			}
404 		}
405 
406 		if ( !parentgoodspots.empty() )
407 		{
408 			Entity* targetLocation = parentgoodspots[rand() % parentgoodspots.size()];
409 			teleported = triggered->teleportAroundEntity(targetLocation, 2);
410 		}
411 		else if ( !goodspots.empty() )
412 		{
413 			Entity* targetLocation = goodspots[rand() % goodspots.size()];
414 			teleported = triggered->teleportAroundEntity(targetLocation, 2);
415 		}
416 		else
417 		{
418 			teleported = triggered->teleportRandom(); // woosh!
419 		}
420 
421 		if ( teleported )
422 		{
423 			createParticleErupt(my, 593);
424 			serverSpawnMiscParticles(my, PARTICLE_EFFECT_ERUPT, 593);
425 			if ( parent && parent->behavior == &actPlayer )
426 			{
427 				// whisked away!
428 				if ( triggered != parent )
429 				{
430 					Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
431 					messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3601], language[3602], MSG_COMBAT);
432 				}
433 			}
434 			if ( triggered->behavior == &actPlayer )
435 			{
436 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
437 				messagePlayerColor(triggered->skill[2], color, language[3611]);
438 				achievementObserver.playerAchievements[triggered->skill[2]].checkPathBetweenObjects(triggered, my, AchievementObserver::BARONY_ACH_WONDERFUL_TOYS);
439 			}
440 
441 			if ( triggered->behavior == &actMonster )
442 			{
443 				triggered->monsterReleaseAttackTarget();
444 			}
445 
446 			createParticleErupt(triggered, 593);
447 			serverSpawnMiscParticlesAtLocation(triggered->x / 16, triggered->y / 16, 0, PARTICLE_EFFECT_ERUPT, 593);
448 		}
449 		else
450 		{
451 			if ( parent && parent->behavior == &actPlayer && triggered != parent )
452 			{
453 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
454 				messagePlayerMonsterEvent(parent->skill[2], color, *triggered->getStats(), language[3615], language[3616], MSG_COMBAT);
455 			}
456 		}
457 		return;
458 	}
459 	else if ( doSpell != SPELL_NONE )
460 	{
461 		Entity* spell = castSpell(my->getUID(), getSpellFromID(doSpell), false, true);
462 		spell->parent = my->parent;
463 		spell->x = triggered->x;
464 		spell->y = triggered->y;
465 		if ( !doVertical )
466 		{
467 			real_t speed = 1.f;
468 			real_t ticksToHit = (entityDistance / speed);
469 			/*real_t predictx = triggered->x + (triggered->vel_x * ticksToHit);
470 			real_t predicty = triggered->y + (triggered->vel_y * ticksToHit);
471 			double tangent = atan2(predicty - my->y, predictx - my->x);*/
472 			double tangent = atan2(triggered->y - my->y, triggered->x - my->x);
473 			spell->yaw = tangent;
474 			spell->vel_x = speed * cos(spell->yaw);
475 			spell->vel_y = speed * sin(spell->yaw);
476 		}
477 		else
478 		{
479 			spell->x = my->x;
480 			spell->y = my->y;
481 			real_t speed = 3.f;
482 			real_t ticksToHit = (entityDistance / speed);
483 			real_t predictx = triggered->x + (triggered->vel_x * ticksToHit);
484 			real_t predicty = triggered->y + (triggered->vel_y * ticksToHit);
485 			double tangent = atan2(predicty - my->y, predictx - my->x);
486 			spell->yaw = tangent;
487 			spell->vel_z = -2.f;
488 			spell->vel_x = speed * cos(spell->yaw);
489 			spell->vel_y = speed * sin(spell->yaw);
490 			spell->pitch = atan2(spell->vel_z, speed);
491 			spell->actmagicIsVertical = MAGIC_ISVERTICAL_XYZ;
492 		}
493 		spell->actmagicCastByTinkerTrap = 1;
494 		if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL )
495 		{
496 			spell->actmagicTinkerTrapFriendlyFire = 1;
497 			if ( triggered == parent )
498 			{
499 				spell->parent = 0;
500 			}
501 		}
502 		spell->skill[5] = 10; // travel time
503 	}
504 	// set obituary
505 	int oldHP = stat->HP;
506 	if ( stat )
507 	{
508 		damage *= triggered->getDamageTableMultiplier(*stat, DAMAGE_TABLE_MAGIC); // reduce/increase by magic table.
509 	}
510 	bool wasAsleep = stat->EFFECTS[EFF_ASLEEP];
511 	triggered->modHP(-damage);
512 	triggered->setObituary(language[3496]);
513 
514 	if ( stat->HP <= 0 && oldHP > 0 )
515 	{
516 		if ( parent )
517 		{
518 			parent->awardXP(triggered, true, true);
519 			if ( stat->type == MINOTAUR )
520 			{
521 				if ( parent->behavior == &actPlayer )
522 				{
523 					steamAchievementClient(parent->skill[2], "BARONY_ACH_TIME_TO_PLAN");
524 				}
525 			}
526 		}
527 		else
528 		{
529 			if ( achievementObserver.checkUidIsFromPlayer(my->parent) >= 0 )
530 			{
531 				steamAchievementClient(achievementObserver.checkUidIsFromPlayer(my->parent), "BARONY_ACH_TAKING_WITH");
532 			}
533 		}
534 	}
535 
536 	if ( triggered->behavior == &actPlayer )
537 	{
538 		int player = triggered->skill[2];
539 
540 		if ( player == clientnum || splitscreen )
541 		{
542 			cameravars[triggered->skill[2]].shakex += .1;
543 			cameravars[triggered->skill[2]].shakey += 10;
544 		}
545 		else if ( player > 0 && multiplayer == SERVER )
546 		{
547 			strcpy((char*)net_packet->data, "SHAK");
548 			net_packet->data[4] = 10; // turns into .1
549 			net_packet->data[5] = 10;
550 			net_packet->address.host = net_clients[player - 1].host;
551 			net_packet->address.port = net_clients[player - 1].port;
552 			net_packet->len = 6;
553 			sendPacketSafe(net_sock, -1, net_packet, player - 1);
554 		}
555 	}
556 	if ( parent && parent != triggered && parent->behavior == &actPlayer )
557 	{
558 		int player = parent->skill[2];
559 		if ( player >= 0 )
560 		{
561 			double tangent = atan2(parent->y - my->y, parent->x - my->x);
562 			lineTraceTarget(my, my->x, my->y, tangent, 128, 0, false, parent);
563 			if ( hit.entity != parent )
564 			{
565 				if ( entityDist(my, parent) >= 64 && entityDist(my, parent) < 128 )
566 				{
567 					messagePlayer(player, language[3494]);
568 				}
569 				else
570 				{
571 					messagePlayer(player, language[3495]);
572 				}
573 			}
574 			if ( triggered->behavior == &actMonster )
575 			{
576 				if ( oldHP > 0 && stat->HP == 0 ) // got a kill
577 				{
578 					if ( rand() % 5 == 0 )
579 					{
580 						parent->increaseSkill(PRO_LOCKPICKING);
581 					}
582 				}
583 				else if ( oldHP > stat->HP )
584 				{
585 					if ( rand() % 20 == 0 ) // wounded
586 					{
587 						parent->increaseSkill(PRO_LOCKPICKING);
588 					}
589 				}
590 				else if( rand() % 20 == 0) // any other effect
591 				{
592 					parent->increaseSkill(PRO_LOCKPICKING);
593 				}
594 
595 				if ( !achievementObserver.playerAchievements[player].bombTrack )
596 				{
597 					achievementObserver.addEntityAchievementTimer(triggered, AchievementObserver::BARONY_ACH_BOMBTRACK, 250, false, 1);
598 					achievementObserver.awardAchievementIfActive(player, triggered, AchievementObserver::BARONY_ACH_BOMBTRACK);
599 				}
600 				if ( !achievementObserver.playerAchievements[player].calmLikeABomb )
601 				{
602 					if ( BOMB_ITEMTYPE == TOOL_SLEEP_BOMB )
603 					{
604 						achievementObserver.addEntityAchievementTimer(triggered, AchievementObserver::BARONY_ACH_CALM_LIKE_A_BOMB, 50 * 15, true, 0);
605 					}
606 					else if ( wasAsleep && damage > 0 )
607 					{
608 						achievementObserver.awardAchievementIfActive(player, triggered, AchievementObserver::BARONY_ACH_CALM_LIKE_A_BOMB);
609 					}
610 				}
611 			}
612 			// update enemy bar for attacker
613 			if ( damage > 0 )
614 			{
615 				if ( !strcmp(stat->name, "") )
616 				{
617 					if ( stat->type < KOBOLD ) //Original monster count
618 					{
619 						updateEnemyBar(parent, triggered, language[90 + stat->type], stat->HP, stat->MAXHP);
620 					}
621 					else if ( stat->type >= KOBOLD ) //New monsters
622 					{
623 						updateEnemyBar(parent, triggered, language[2000 + (stat->type - KOBOLD)], stat->HP, stat->MAXHP);
624 					}
625 				}
626 				else
627 				{
628 					updateEnemyBar(parent, triggered, stat->name, stat->HP, stat->MAXHP);
629 				}
630 				Entity* gib = spawnGib(triggered);
631 				serverSpawnGibForClient(gib);
632 			}
633 		}
634 	}
635 }
636 
actBomb(Entity * my)637 void actBomb(Entity* my)
638 {
639 	my->removeLightField();
640 	if ( multiplayer == CLIENT )
641 	{
642 		if ( BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
643 		{
644 			my->spawnAmbientParticles(25, 576, 10 + rand() % 40, 1.0, false);
645 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 92);
646 		}
647 		return;
648 	}
649 	else
650 	{
651 		my->skill[2] = -15;
652 	}
653 
654 	// undo bomb
655 	for ( int i = 0; i < MAXPLAYERS; i++ )
656 	{
657 		if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
658 		{
659 			if ( inrange[i] )
660 			{
661 				Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
662 				entity->flags[INVISIBLE] = true;
663 				entity->flags[UPDATENEEDED] = true;
664 				entity->flags[PASSABLE] = true;
665 				entity->x = my->x;
666 				entity->y = my->y;
667 				entity->z = my->z;
668 				entity->sizex = my->sizex;
669 				entity->sizey = my->sizey;
670 				entity->yaw = my->yaw;
671 				entity->pitch = my->pitch;
672 				entity->roll = 3 * PI / 2;
673 				entity->behavior = &actItem;
674 				entity->skill[10] = BOMB_ITEMTYPE;
675 				entity->skill[11] = BEARTRAP_STATUS;
676 				entity->skill[12] = BEARTRAP_BEATITUDE;
677 				entity->skill[13] = 1;
678 				entity->skill[14] = BEARTRAP_APPEARANCE;
679 				entity->skill[15] = BEARTRAP_IDENTIFIED;
680 				if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
681 				{
682 					// don't fall down
683 					entity->itemNotMoving = 1;
684 					entity->itemNotMovingClient = 1;
685 					serverUpdateEntitySkill(entity, 18); //update both the above flags.
686 					serverUpdateEntitySkill(entity, 19);
687 				}
688 				else
689 				{
690 					entity->itemNotMoving = 0;
691 					entity->itemNotMovingClient = 0;
692 				}
693 				messagePlayer(i, language[3600], items[BOMB_ITEMTYPE].name_identified);
694 				list_RemoveNode(my->mynode);
695 				return;
696 			}
697 		}
698 	}
699 
700 	if ( my->isInteractWithMonster() )
701 	{
702 		Entity* monsterInteracting = uidToEntity(my->interactedByMonster);
703 		if ( monsterInteracting && monsterInteracting->getMonsterTypeFromSprite() == GYROBOT )
704 		{
705 			if ( monsterInteracting->monsterAllyGetPlayerLeader() )
706 			{
707 				Item* tmp = newItemFromEntity(my);
708 				if ( tmp )
709 				{
710 					tmp->applyLockpick(monsterInteracting->monsterAllyIndex, *my);
711 					free(tmp);
712 				}
713 			}
714 		}
715 		my->clearMonsterInteract();
716 	}
717 
718 	if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB && BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TELEPORT_RECEIVER )
719 	{
720 		my->spawnAmbientParticles(25, 576, 10 + rand() % 40, 1.0, false);
721 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 92);
722 		my->sprite = 899;
723 		return;
724 	}
725 	else if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB )
726 	{
727 		my->sprite = 884;
728 	}
729 
730 	// launch bomb
731 	std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 1);
732 	Entity* triggered = nullptr;
733 	real_t entityDistance = 0.f;
734 	bool bombExplodeAOETargets = false;
735 
736 
737 	int explosionSprite = 49;
738 	if ( BOMB_ITEMTYPE == TOOL_TELEPORT_BOMB )
739 	{
740 		explosionSprite = 145;
741 	}
742 	else if ( BOMB_ITEMTYPE == TOOL_FREEZE_BOMB )
743 	{
744 		explosionSprite = 135;
745 	}
746 	else if ( BOMB_ITEMTYPE == TOOL_SLEEP_BOMB )
747 	{
748 		explosionSprite = 0;
749 	}
750 
751 	if ( BOMB_ENTITY_ATTACHED_TO != 0 || BOMB_HIT_BY_PROJECTILE == 1 || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL )
752 	{
753 		Entity* onEntity = uidToEntity(static_cast<Uint32>(BOMB_ENTITY_ATTACHED_TO));
754 		bool shouldExplode = false;
755 		if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_WALL )
756 		{
757 			int checkx = my->x;
758 			int checky = my->y;
759 			switch ( BOMB_DIRECTION )
760 			{
761 				case Item::ItemBombFacingDirection::BOMB_EAST:
762 					checkx -= 8;
763 					break;
764 				case Item::ItemBombFacingDirection::BOMB_WEST:
765 					checkx += 8;
766 					break;
767 				case Item::ItemBombFacingDirection::BOMB_SOUTH:
768 					checky -= 8;
769 					break;
770 				case Item::ItemBombFacingDirection::BOMB_NORTH:
771 					checky += 8;
772 					break;
773 				default:
774 					break;
775 			}
776 			checkx = checkx >> 4;
777 			checky = checky >> 4;
778 			if ( !map.tiles[OBSTACLELAYER + checky * MAPLAYERS + checkx * MAPLAYERS * map.height] )   // wall
779 			{
780 				shouldExplode = true;
781 			}
782 			else if ( BOMB_HIT_BY_PROJECTILE == 1 )
783 			{
784 				shouldExplode = true;
785 			}
786 		}
787 		else if ( onEntity )
788 		{
789 			if ( onEntity->behavior == &actDoor )
790 			{
791 				if ( onEntity->doorHealth < BOMB_ENTITY_ATTACHED_START_HP || onEntity->flags[PASSABLE]
792 					|| BOMB_HIT_BY_PROJECTILE == 1 )
793 				{
794 					if ( onEntity->doorHealth > 0 )
795 					{
796 						onEntity->doorHandleDamageMagic(50, *my, uidToEntity(my->parent));
797 					}
798 					shouldExplode = true;
799 				}
800 			}
801 			else if ( onEntity->behavior == &actChest )
802 			{
803 				if ( onEntity->skill[3] < BOMB_ENTITY_ATTACHED_START_HP || BOMB_CHEST_STATUS != onEntity->skill[1]
804 					|| BOMB_HIT_BY_PROJECTILE == 1 )
805 				{
806 					if ( onEntity->skill[3] > 0 )
807 					{
808 						if ( BOMB_ITEMTYPE == TOOL_BOMB ) // fire bomb do more.
809 						{
810 							onEntity->chestHandleDamageMagic(50, *my, uidToEntity(my->parent));
811 						}
812 						else
813 						{
814 							onEntity->chestHandleDamageMagic(20, *my, uidToEntity(my->parent));
815 						}
816 					}
817 					shouldExplode = true;
818 				}
819 			}
820 		}
821 		else
822 		{
823 			shouldExplode = true; // my attached entity died.
824 		}
825 
826 		if ( shouldExplode )
827 		{
828 			if ( onEntity )
829 			{
830 				spawnExplosionFromSprite(explosionSprite, onEntity->x, onEntity->y, onEntity->z);
831 			}
832 			else
833 			{
834 				spawnExplosionFromSprite(explosionSprite, my->x, my->y, my->z);
835 			}
836 			bombExplodeAOETargets = true;
837 			BOMB_TRIGGER_TYPE = Item::ItemBombTriggerType::BOMB_TRIGGER_ALL;
838 		}
839 	}
840 
841 	for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end() && !triggered; ++it )
842 	{
843 		list_t* currentList = *it;
844 		node_t* node;
845 		for ( node = currentList->first; node != nullptr && !triggered; node = node->next )
846 		{
847 			Entity* entity = (Entity*)node->element;
848 			if ( !entity )
849 			{
850 				continue;
851 			}
852 			if ( my->parent == entity->getUID() && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
853 			{
854 				continue;
855 			}
856 			if ( entity->behavior == &actMonster || entity->behavior == &actPlayer )
857 			{
858 				Stat* stat = entity->getStats();
859 				if ( stat )
860 				{
861 					Entity* parent = uidToEntity(my->parent);
862 					if ( parent && parent->checkFriend(entity) && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
863 					{
864 						continue;
865 					}
866 					if ( stat->type == GYROBOT )
867 					{
868 						continue;
869 					}
870 					if ( !parent && BOMB_PLAYER_OWNER >= 0 && !(BOMB_TRIGGER_TYPE == Item::ItemBombTriggerType::BOMB_TRIGGER_ALL) )
871 					{
872 						if ( entity->behavior == &actPlayer )
873 						{
874 							continue; // players won't trigger if owner dead.
875 						}
876 						else if ( entity->monsterAllyGetPlayerLeader() )
877 						{
878 							continue; // player followers won't trigger if owner dead.
879 						}
880 					}
881 					if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
882 					{
883 						entityDistance = entityDist(my, entity);
884 						if ( entityDistance < 6.5 )
885 						{
886 							spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y + rand() % 9, my->z - 2);
887 							triggered = entity;
888 						}
889 					}
890 					else if ( bombExplodeAOETargets )
891 					{
892 						Entity* onEntity = uidToEntity(static_cast<Uint32>(BOMB_ENTITY_ATTACHED_TO));
893 						if ( onEntity )
894 						{
895 							entityDistance = entityDist(onEntity, entity);
896 							if ( entityDistance < STRIKERANGE )
897 							{
898 								spawnExplosionFromSprite(explosionSprite, entity->x, entity->y, entity->z);
899 								bombDoEffect(my, entity, entityDistance, true, true);
900 							}
901 						}
902 						else
903 						{
904 							entityDistance = entityDist(my, entity);
905 							if ( entityDistance < STRIKERANGE )
906 							{
907 								spawnExplosionFromSprite(explosionSprite, entity->x, entity->y, entity->z);
908 								bombDoEffect(my, entity, entityDistance, true, true);
909 							}
910 						}
911 					}
912 					else
913 					{
914 						real_t oldx = my->x;
915 						real_t oldy = my->y;
916 						// pretend the bomb is in the center of the tile it's facing.
917 						switch ( BOMB_DIRECTION )
918 						{
919 							case Item::ItemBombFacingDirection::BOMB_EAST:
920 								my->x += 8;
921 								entityDistance = entityDist(my, entity);
922 								if ( entityDistance < 12 )
923 								{
924 									triggered = entity;
925 									spawnExplosionFromSprite(explosionSprite, my->x - rand() % 9, my->y - 4 + rand() % 9, my->z);
926 								}
927 								break;
928 							case Item::ItemBombFacingDirection::BOMB_WEST:
929 								my->x -= 8;
930 								entityDistance = entityDist(my, entity);
931 								if ( entityDistance < 12 )
932 								{
933 									triggered = entity;
934 									spawnExplosionFromSprite(explosionSprite, my->x + rand() % 9, my->y - 4 + rand() % 9, my->z);
935 								}
936 								break;
937 							case Item::ItemBombFacingDirection::BOMB_SOUTH:
938 								my->y += 8;
939 								entityDistance = entityDist(my, entity);
940 								if ( entityDistance < 12 )
941 								{
942 									triggered = entity;
943 									spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y - rand() % 9, my->z);
944 								}
945 								break;
946 							case Item::ItemBombFacingDirection::BOMB_NORTH:
947 								my->y -= 8;
948 								entityDistance = entityDist(my, entity);
949 								if ( entityDistance < 12 )
950 								{
951 									triggered = entity;
952 									spawnExplosionFromSprite(explosionSprite, my->x - 4 + rand() % 9, my->y + rand() % 9, my->z);
953 								}
954 								break;
955 							default:
956 								break;
957 						}
958 						my->x = oldx;
959 						my->y = oldy;
960 					}
961 				}
962 			}
963 		}
964 	}
965 
966 	if ( !bombExplodeAOETargets && triggered
967 		&& (BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_DOOR || BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_CHEST) )
968 	{
969 		// found enemy, do AoE effect.
970 		BOMB_HIT_BY_PROJECTILE = 1;
971 	}
972 	else if ( bombExplodeAOETargets || triggered )
973 	{
974 		//Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Item entity.
975 		//entity->flags[INVISIBLE] = true;
976 		//entity->flags[UPDATENEEDED] = true;
977 		//entity->flags[PASSABLE] = true;
978 		//entity->x = my->x;
979 		//entity->y = my->y;
980 		//entity->z = my->z;
981 		//entity->sizex = my->sizex;
982 		//entity->sizey = my->sizey;
983 		//entity->yaw = my->yaw;
984 		//entity->pitch = my->pitch;
985 		//entity->roll = 3 * PI / 2;
986 		//entity->behavior = &actItem;
987 		//entity->skill[10] = TOOL_DETONATOR_CHARGE;
988 		//entity->skill[11] = BROKEN;
989 		//entity->skill[12] = 0;
990 		//entity->skill[13] = 1;
991 		//entity->skill[14] = ITEM_TINKERING_APPEARANCE;
992 		//entity->skill[15] = 1;
993 		//if ( BOMB_PLACEMENT == Item::ItemBombPlacement::BOMB_FLOOR )
994 		//{
995 		//	// don't fall down
996 		//	entity->itemNotMoving = 1;
997 		//	entity->itemNotMovingClient = 1;
998 		//	serverUpdateEntitySkill(entity, 18); //update both the above flags.
999 		//	serverUpdateEntitySkill(entity, 19);
1000 		//}
1001 		//else
1002 		//{
1003 		//	entity->itemNotMoving = 0;
1004 		//	entity->itemNotMovingClient = 0;
1005 		//}
1006 		Item* charge = newItem(TOOL_DETONATOR_CHARGE, BROKEN, 0, 1, ITEM_TINKERING_APPEARANCE, true, nullptr);
1007 		Entity* dropped = dropItemMonster(charge, my, nullptr);
1008 		if ( dropped )
1009 		{
1010 			dropped->flags[USERFLAG1] = true;
1011 		}
1012 		my->removeLightField();
1013 		if ( triggered )
1014 		{
1015 			bombDoEffect(my, triggered, entityDistance, false, false);
1016 		}
1017 		playSoundEntity(my, 76, 64);
1018 		list_RemoveNode(my->mynode);
1019 		return;
1020 	}
1021 }
1022 
entityCheckIfTriggeredBomb(bool triggerBomb)1023 bool Entity::entityCheckIfTriggeredBomb(bool triggerBomb)
1024 {
1025 	if ( multiplayer == CLIENT )
1026 	{
1027 		return false;
1028 	}
1029 	if ( this->behavior != &actThrown && this->behavior != &actArrow )
1030 	{
1031 		return false;
1032 	}
1033 	bool foundBomb = false;
1034 	std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(this, 2);
1035 	for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1036 	{
1037 		list_t* currentList = *it;
1038 		node_t* node;
1039 		for ( node = currentList->first; node != nullptr; node = node->next )
1040 		{
1041 			Entity* entity = (Entity*)node->element;
1042 			if ( entity && entity->behavior == &actBomb && entity->skill[24] == 0 )
1043 			{
1044 				if ( entityInsideEntity(this, entity) )
1045 				{
1046 					if ( triggerBomb )
1047 					{
1048 						entity->skill[24] = 1;
1049 					}
1050 					foundBomb = true;
1051 				}
1052 			}
1053 		}
1054 	}
1055 	return foundBomb;
1056 }
1057 
actDecoyBox(Entity * my)1058 void actDecoyBox(Entity* my)
1059 {
1060 	my->flags[PASSABLE] = true;
1061 	if ( my->skill[0] == 0 )
1062 	{
1063 		my->skill[0] = 1;
1064 		//spawn a crank.
1065 		Entity* entity = newEntity(895, 1, map.entities, nullptr); //Decoy crank.
1066 		entity->behavior = &actDecoyBoxCrank;
1067 		entity->parent = my->getUID();
1068 		entity->x = my->x;
1069 		entity->y = my->y;
1070 		entity->z = my->z;
1071 		entity->yaw = my->yaw;
1072 		entity->flags[PASSABLE] = true;
1073 		entity->flags[NOUPDATE] = true;
1074 		entity->flags[UNCLICKABLE] = true;
1075 		if ( multiplayer != CLIENT )
1076 		{
1077 			entity_uids--;
1078 		}
1079 		entity->setUID(-3);
1080 		playSoundEntityLocal(my, 455, 64);
1081 	}
1082 	else
1083 	{
1084 		// let's make some noise.
1085 		if ( my->ticks % 5 == 0 && rand() % 3 == 0 )
1086 		{
1087 			playSoundEntityLocal(my, 472 + rand() % 13, 192);
1088 		}
1089 		if ( my->ticks % 20 == 0 && rand() % 3 > 0 )
1090 		{
1091 			playSoundEntityLocal(my, 475 + rand() % 10, 192);
1092 		}
1093 	}
1094 	if ( multiplayer == CLIENT )
1095 	{
1096 		return;
1097 	}
1098 
1099 	if ( my->ticks % TICKS_PER_SECOND == 0 )
1100 	{
1101 		Entity* parent = uidToEntity(my->parent);
1102 		std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange * 2 + 1);
1103 		std::vector<Entity*> listOfOtherDecoys;
1104 		// find other decoys (so monsters don't wiggle back and forth.)
1105 		for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1106 		{
1107 			list_t* currentList = *it;
1108 			node_t* node;
1109 			for ( node = currentList->first; node != nullptr; node = node->next )
1110 			{
1111 				Entity* entity = (Entity*)node->element;
1112 				if ( entity && entity->behavior == &actDecoyBox && entity != my )
1113 				{
1114 					listOfOtherDecoys.push_back(entity);
1115 				}
1116 			}
1117 		}
1118 		entLists.clear();
1119 
1120 		entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, decoyBoxRange);
1121 		bool message = false;
1122 		bool detected = false;
1123 		for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1124 		{
1125 			list_t* currentList = *it;
1126 			node_t* node;
1127 			for ( node = currentList->first; node != nullptr; node = node->next )
1128 			{
1129 				Entity* entity = (Entity*)node->element;
1130 				if ( parent && entity && entity->behavior == &actMonster
1131 					&& parent->checkEnemy(entity) && entity->isMobile() )
1132 				{
1133 					if ( (entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0)
1134 						|| (entityDist(entity,my) < 2 * TOUCHRANGE && (Uint32)(entity->monsterLastDistractedByNoisemaker) != my->getUID()) )
1135 					{
1136 						Stat* myStats = entity->getStats();
1137 						if ( !entity->isBossMonster() && !entity->monsterIsTinkeringCreation()
1138 							&& myStats && !uidToEntity(myStats->leader_uid) )
1139 						{
1140 							// found an eligible monster (far enough away, non-boss)
1141 							// now look through other decoys, if others are in range of our target,
1142 							// then the most recent decoy will pull them (this decoy won't do anything)
1143 							bool foundMoreRecentDecoy = false;
1144 							if ( !listOfOtherDecoys.empty() )
1145 							{
1146 								for ( std::vector<Entity*>::iterator decoyIt = listOfOtherDecoys.begin(); decoyIt != listOfOtherDecoys.end(); ++decoyIt )
1147 								{
1148 									Entity* decoy = *decoyIt;
1149 									if ( entityDist(decoy, entity) < (decoyBoxRange * 16) ) // less than x tiles from our monster
1150 									{
1151 										if ( decoy->ticks < my->ticks )
1152 										{
1153 											// this decoy is newer (less game ticks alive)
1154 											// defer to this decoy.
1155 											foundMoreRecentDecoy = true;
1156 											break;
1157 										}
1158 									}
1159 								}
1160 							}
1161 							if ( (Uint32)(entity->monsterLastDistractedByNoisemaker) == my->getUID() )
1162 							{
1163 								// ignore pathing to this noisemaker as we're already distracted by it.
1164 								if ( entityDist(entity, my) < TOUCHRANGE
1165 									&& !myStats->EFFECTS[EFF_DISORIENTED]
1166 									&& !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
1167 								{
1168 									// if we pathed within range
1169 									detected = false; // skip the message.
1170 
1171 									// can I see the noisemaker next to me?
1172 									real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
1173 									lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
1174 									if ( hit.entity == entity )
1175 									{
1176 										// set disoriented and start a cooldown on being distracted.
1177 										if ( entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0 )
1178 										{
1179 											// not attacking, duration longer.
1180 											entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 3, false);
1181 											entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1182 										}
1183 										else
1184 										{
1185 											entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 1, false);
1186 											entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1187 										}
1188 										spawnFloatingSpriteMisc(134, entity->x + (-4 + rand() % 9) + cos(entity->yaw) * 2,
1189 											entity->y + (-4 + rand() % 9) + sin(entity->yaw) * 2, entity->z + rand() % 4);
1190 									}
1191 								}
1192 								break;
1193 							}
1194 							if ( foundMoreRecentDecoy )
1195 							{
1196 								break;
1197 							}
1198 							if ( !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN]
1199 								&& entity->monsterSetPathToLocation(my->x / 16, my->y / 16, 2) && entity->children.first )
1200 							{
1201 								// path only if we're not on cooldown
1202 								entity->monsterLastDistractedByNoisemaker = my->getUID();
1203 								entity->monsterTarget = my->getUID();
1204 								entity->monsterState = MONSTER_STATE_HUNT; // hunt state
1205 								serverUpdateEntitySkill(entity, 0);
1206 								detected = true;
1207 
1208 								if ( entityDist(entity, my) < TOUCHRANGE
1209 									&& !myStats->EFFECTS[EFF_DISORIENTED]
1210 									&& !myStats->EFFECTS[EFF_DISTRACTED_COOLDOWN] )
1211 								{
1212 									detected = false; // skip the message.
1213 
1214 									// can I see the noisemaker next to me?
1215 									real_t tangent = atan2(entity->y - my->y, entity->x - my->x);
1216 									lineTraceTarget(my, my->x, my->y, tangent, 32.0, 0, false, entity);
1217 									if ( hit.entity == entity )
1218 									{
1219 										// set disoriented and start a cooldown on being distracted.
1220 										if ( entity->monsterState == MONSTER_STATE_WAIT || entity->monsterTarget == 0 )
1221 										{
1222 											// not attacking, duration longer.
1223 											entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 3, false);
1224 											entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1225 										}
1226 										else
1227 										{
1228 											entity->setEffect(EFF_DISORIENTED, true, TICKS_PER_SECOND * 1, false);
1229 											entity->setEffect(EFF_DISTRACTED_COOLDOWN, true, TICKS_PER_SECOND * 5, false);
1230 										}
1231 										spawnFloatingSpriteMisc(134, entity->x + (-4 + rand() % 9) + cos(entity->yaw) * 2,
1232 											entity->y + (-4 + rand() % 9) + sin(entity->yaw) * 2, entity->z + rand() % 4);
1233 									}
1234 								}
1235 
1236 								if ( parent->behavior == &actPlayer && stats[parent->skill[2]] )
1237 								{
1238 									// see if we have a gyrobot follower to tell us what's goin on
1239 									for ( node_t* tmpNode = stats[parent->skill[2]]->FOLLOWERS.first; tmpNode != nullptr; tmpNode = tmpNode->next )
1240 									{
1241 										Uint32* c = (Uint32*)tmpNode->element;
1242 										Entity* gyrobot = uidToEntity(*c);
1243 										if ( gyrobot && gyrobot->getRace() == GYROBOT )
1244 										{
1245 											if ( entity->entityShowOnMap < 250 )
1246 											{
1247 												entity->entityShowOnMap = TICKS_PER_SECOND * 5;
1248 												if ( parent->skill[2] != 0 )
1249 												{
1250 													serverUpdateEntitySkill(entity, 59);
1251 												}
1252 											}
1253 											if ( !message )
1254 											{
1255 												messagePlayer(parent->skill[2], language[3671]);
1256 												message = true;
1257 											}
1258 											break;
1259 										}
1260 									}
1261 								}
1262 							}
1263 						}
1264 					}
1265 				}
1266 			}
1267 		}
1268 		if ( !message && detected )
1269 		{
1270 			if ( parent && parent->behavior == &actPlayer )
1271 			{
1272 				messagePlayer(parent->skill[2], language[3882]);
1273 			}
1274 		}
1275 	}
1276 
1277 	if ( my->ticks > TICKS_PER_SECOND * 7 )
1278 	{
1279 		// stop working.
1280 		bool decoyBreak = (rand() % 5 == 0);
1281 		Entity* parent = uidToEntity(my->parent);
1282 		playSoundEntity(my, 485 + rand() % 3, 192);
1283 		if ( !decoyBreak )
1284 		{
1285 			playSoundEntity(my, 176, 128);
1286 			Item* item = newItem(TOOL_DECOY, static_cast<Status>(BEARTRAP_STATUS), BEARTRAP_BEATITUDE, 1, BEARTRAP_APPEARANCE, true, nullptr);
1287 			Entity* entity = dropItemMonster(item, my, nullptr);
1288 			if ( entity )
1289 			{
1290 				entity->flags[USERFLAG1] = true;    // makes items passable, improves performance
1291 			}
1292 			/*if ( parent && parent->behavior == &actPlayer )
1293 			{
1294 				messagePlayer(parent->skill[2], language[3769]);
1295 			}*/
1296 			list_RemoveNode(my->mynode);
1297 			return;
1298 		}
1299 		else
1300 		{
1301 			playSoundEntity(my, 132, 16);
1302 			for ( int c = 0; c < 3; c++ )
1303 			{
1304 				Entity* entity = spawnGib(my);
1305 				if ( entity )
1306 				{
1307 					switch ( c )
1308 					{
1309 						case 0:
1310 							entity->sprite = 895;
1311 							break;
1312 						case 1:
1313 							entity->sprite = 894;
1314 							break;
1315 						case 2:
1316 							entity->sprite = 874;
1317 							break;
1318 						default:
1319 							break;
1320 					}
1321 					serverSpawnGibForClient(entity);
1322 				}
1323 			}
1324 			if ( parent && parent->behavior == &actPlayer )
1325 			{
1326 				messagePlayer(parent->skill[2], language[3770]);
1327 			}
1328 			list_RemoveNode(my->mynode);
1329 			return;
1330 		}
1331 	}
1332 }
1333 
actDecoyBoxCrank(Entity * my)1334 void actDecoyBoxCrank(Entity* my)
1335 {
1336 	Entity* parent = uidToEntity(my->parent);
1337 	if ( !parent )
1338 	{
1339 		list_RemoveNode(my->mynode);
1340 		return;
1341 	}
1342 	my->x = parent->x;
1343 	my->y = parent->y;
1344 	my->z = parent->z;
1345 	my->yaw = parent->yaw;
1346 
1347 	my->x += limbs[DUMMYBOT][12][0] * cos(parent->yaw) + limbs[DUMMYBOT][12][1] * cos(parent->yaw + PI / 2);
1348 	my->y += limbs[DUMMYBOT][12][0] * sin(parent->yaw) + limbs[DUMMYBOT][12][1] * sin(parent->yaw + PI / 2);
1349 	my->z = limbs[DUMMYBOT][12][2];
1350 	my->focalx = limbs[DUMMYBOT][11][0];
1351 	my->focaly = limbs[DUMMYBOT][11][1];
1352 	my->focalz = limbs[DUMMYBOT][11][2];
1353 
1354 	my->pitch += 0.1;
1355 	if ( my->pitch > 2 * PI )
1356 	{
1357 		my->pitch -= 2 * PI;
1358 	}
1359 }