1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: item_tool.cpp
5 	Desc: implementation functions for the tool category items
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 "sound.hpp"
15 #include "net.hpp"
16 #include "player.hpp"
17 #include "stat.hpp"
18 #include "colors.hpp"
19 #include "items.hpp"
20 #include "magic/magic.hpp"
21 #include "scores.hpp"
22 #include "shops.hpp"
23 
applySkeletonKey(int player,Entity & entity)24 void Item::applySkeletonKey(int player, Entity& entity)
25 {
26 	if ( entity.behavior == &actChest )
27 	{
28 		playSoundEntity(&entity, 91, 64);
29 		if ( entity.skill[4] )
30 		{
31 			messagePlayer(player, language[1097]);
32 			entity.unlockChest();
33 		}
34 		else
35 		{
36 			messagePlayer(player, language[1098]);
37 			entity.lockChest();
38 		}
39 	}
40 	else if ( entity.behavior == &actDoor )
41 	{
42 		playSoundEntity(&entity, 91, 64);
43 		if ( entity.doorLocked )
44 		{
45 			if ( entity.doorDisableLockpicks == 1 )
46 			{
47 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
48 				messagePlayerColor(player, color, language[3101]); // disabled.
49 			}
50 			else
51 			{
52 				messagePlayer(player, language[1099]);
53 				entity.doorLocked = 0;
54 			}
55 		}
56 		else
57 		{
58 			messagePlayer(player, language[1100]);
59 			entity.doorLocked = 1;
60 		}
61 	}
62 	else
63 	{
64 		messagePlayer(player, language[1101], getName());
65 	}
66 }
67 
68 
applyLockpick(int player,Entity & entity)69 void Item::applyLockpick(int player, Entity& entity)
70 {
71 	bool capstoneUnlocked = (stats[player]->PROFICIENCIES[PRO_LOCKPICKING] >= CAPSTONE_LOCKPICKING_UNLOCK);
72 	if ( entity.behavior == &actBomb )
73 	{
74 		Entity* gyrobotUsing = nullptr;
75 		if ( entity.isInteractWithMonster() )
76 		{
77 			Entity* monsterInteracting = uidToEntity(entity.interactedByMonster);
78 			if ( monsterInteracting && monsterInteracting->getMonsterTypeFromSprite() == GYROBOT )
79 			{
80 				gyrobotUsing = monsterInteracting;
81 			}
82 		}
83 
84 		if ( entity.skill[21] == TOOL_TELEPORT_BOMB )
85 		{
86 			++entity.skill[22];
87 			if ( entity.skill[22] > BOMB_TRIGGER_ALL )
88 			{
89 				entity.skill[22] = BOMB_TRIGGER_ENEMIES;
90 			}
91 		}
92 		else
93 		{
94 			entity.skill[22] = (entity.skill[22] == BOMB_TRIGGER_ENEMIES) ? BOMB_TRIGGER_ALL : BOMB_TRIGGER_ENEMIES;
95 		}
96 		if ( entity.skill[22] == BOMB_TRIGGER_ENEMIES )
97 		{
98 			if ( gyrobotUsing )
99 			{
100 				messagePlayer(player, language[3865]);
101 			}
102 			else
103 			{
104 				messagePlayer(player, language[3605]);
105 			}
106 			messagePlayerColor(player, uint32ColorGreen(*mainsurface), language[3606]);
107 		}
108 		else if ( entity.skill[22] == BOMB_TRIGGER_ALL )
109 		{
110 			if ( gyrobotUsing )
111 			{
112 				messagePlayer(player, language[3866]);
113 			}
114 			else
115 			{
116 				messagePlayer(player, language[3607]);
117 			}
118 			messagePlayerColor(player, uint32ColorRed(*mainsurface), language[3608]);
119 		}
120 		else if ( entity.skill[22] == BOMB_TELEPORT_RECEIVER )
121 		{
122 			if ( gyrobotUsing )
123 			{
124 				messagePlayer(player, language[3867]);
125 			}
126 			else
127 			{
128 				messagePlayer(player, language[3609]);
129 			}
130 			messagePlayer(player, language[3610]);
131 
132 			playSoundEntity(&entity, 166, 128); // invisible.ogg
133 			createParticleDropRising(&entity, 576, 1.0);
134 			serverSpawnMiscParticles(&entity, PARTICLE_EFFECT_RISING_DROP, 576);
135 		}
136 		serverUpdateEntitySkill(&entity, 22);
137 		playSoundEntity(&entity, 253, 64);
138 	}
139 	else if ( entity.behavior == &actChest )
140 	{
141 		if ( entity.chestLocked )
142 		{
143 			// 3-17 damage on lockpick depending on skill
144 			// 0 skill is 3 damage
145 			// 20 skill is 4-5 damage
146 			// 60 skill is 6-11 damage
147 			// 100 skill is 8-17 damage
148 			int lockpickDamageToChest = 3 + stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 20
149 				+ rand() % std::max(1, stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 10);
150 			entity.chestLockpickHealth = std::max(0, entity.chestLockpickHealth - lockpickDamageToChest);
151 			bool unlockedFromLockpickHealth = (entity.chestLockpickHealth == 0);
152 
153 			if ( capstoneUnlocked || stats[player]->PROFICIENCIES[PRO_LOCKPICKING] > rand() % 200
154 				|| unlockedFromLockpickHealth )
155 			{
156 				//Unlock chest.
157 				playSoundEntity(&entity, 91, 64);
158 				messagePlayer(player, language[1097]);
159 				if ( capstoneUnlocked && !entity.chestPreventLockpickCapstoneExploit )
160 				{
161 					if ( rand() % 2 == 0 )
162 					{
163 						Item* generated = newItem(itemTypeWithinGoldValue(-1, 80, 600), static_cast<Status>(SERVICABLE + rand() % 2), 0 + rand() % 2, 1, rand(), false, nullptr);
164 						entity.addItemToChest(generated);
165 						messagePlayer(player, language[3897]);
166 					}
167 					else
168 					{
169 						int goldAmount = CAPSTONE_LOCKPICKING_CHEST_GOLD_AMOUNT;
170 						stats[player]->GOLD += goldAmount;
171 						messagePlayerColor(player, uint32ColorGreen(*mainsurface), "You found %d gold pieces in the chest!", goldAmount);
172 					}
173 				}
174 				if ( !entity.chestPreventLockpickCapstoneExploit )
175 				{
176 					if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_EXPERT )
177 					{
178 						players[player]->entity->increaseSkill(PRO_LOCKPICKING);
179 					}
180 					else
181 					{
182 						if ( rand() % 20 == 0 )
183 						{
184 							messagePlayer(player, language[3689], language[675]);
185 						}
186 					}
187 
188 					// based on tinkering skill, add some bonus scrap materials inside chest. (50-150%)
189 					if ( (50 + 10 * (stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 10)) > rand() % 100 )
190 					{
191 						int metalscrap = 5 + rand() % 6;
192 						int magicscrap = 5 + rand() % 11;
193 						if ( entity.children.first )
194 						{
195 							list_t* inventory = static_cast<list_t* >(entity.children.first->element);
196 							if ( inventory )
197 							{
198 								newItem(TOOL_METAL_SCRAP, DECREPIT, 0, metalscrap, 0, true, inventory);
199 								newItem(TOOL_MAGIC_SCRAP, DECREPIT, 0, magicscrap, 0, true, inventory);
200 							}
201 						}
202 					}
203 				}
204 				entity.unlockChest();
205 			}
206 			else
207 			{
208 				//Failed to unlock chest.
209 				playSoundEntity(&entity, 92, 64);
210 				messagePlayer(player, language[1102]);
211 				bool tryDegradeLockpick = true;
212 				if ( !entity.chestPreventLockpickCapstoneExploit )
213 				{
214 					if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_EXPERT )
215 					{
216 						if ( rand() % 10 == 0 )
217 						{
218 							players[player]->entity->increaseSkill(PRO_LOCKPICKING);
219 							tryDegradeLockpick = false;
220 						}
221 					}
222 					else
223 					{
224 						if ( rand() % 20 == 0 )
225 						{
226 							messagePlayer(player, language[3689], language[675]);
227 							tryDegradeLockpick = false;
228 						}
229 					}
230 				}
231 
232 				if ( tryDegradeLockpick )
233 				{
234 					if ( rand() % 5 == 0 )
235 					{
236 						if ( player == clientnum )
237 						{
238 							if ( count > 1 )
239 							{
240 								newItem(type, status, beatitude, count - 1, appearance, identified, &stats[player]->inventory);
241 							}
242 						}
243 						stats[player]->weapon->count = 1;
244 						stats[player]->weapon->status = static_cast<Status>(stats[player]->weapon->status - 1);
245 						if ( status != BROKEN )
246 						{
247 							messagePlayer(player, language[1103]);
248 						}
249 						else
250 						{
251 							messagePlayer(player, language[1104]);
252 						}
253 						if ( player > 0 && multiplayer == SERVER )
254 						{
255 							strcpy((char*) (net_packet->data), "ARMR");
256 							net_packet->data[4] = 5;
257 							net_packet->data[5] = stats[player]->weapon->status;
258 							net_packet->address.host = net_clients[player - 1].host;
259 							net_packet->address.port = net_clients[player - 1].port;
260 							net_packet->len = 6;
261 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
262 						}
263 					}
264 				}
265 			}
266 		}
267 		else
268 		{
269 			messagePlayer(player, language[1105]);
270 		}
271 	}
272 	else if ( entity.behavior == &actDoor )
273 	{
274 		if ( entity.doorLocked )
275 		{
276 			// 3-17 damage on lockpick depending on skill
277 			// 0 skill is 3 damage
278 			// 20 skill is 4-5 damage
279 			// 60 skill is 6-11 damage
280 			// 100 skill is 8-17 damage
281 			int lockpickDamageToDoor = 3 + stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 20
282 				+ rand() % std::max(1, stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 10);
283 			entity.doorLockpickHealth = std::max(0, entity.doorLockpickHealth - lockpickDamageToDoor);
284 			bool unlockedFromLockpickHealth = (entity.doorLockpickHealth == 0);
285 
286 			if ( entity.doorDisableLockpicks == 1 )
287 			{
288 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
289 				messagePlayerColor(player, color, language[3101]); // disabled.
290 			}
291 			else if ( capstoneUnlocked
292 				|| stats[player]->PROFICIENCIES[PRO_LOCKPICKING] > rand() % 200
293 				|| unlockedFromLockpickHealth )
294 			{
295 				//Unlock door.
296 				playSoundEntity(&entity, 91, 64);
297 				messagePlayer(player, language[1099]);
298 				entity.doorLocked = 0;
299 				if ( !entity.doorPreventLockpickExploit )
300 				{
301 					if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_SKILLED )
302 					{
303 						players[player]->entity->increaseSkill(PRO_LOCKPICKING);
304 					}
305 					else
306 					{
307 						if ( rand() % 20 == 0 )
308 						{
309 							messagePlayer(player, language[3689], language[674]);
310 						}
311 					}
312 				}
313 				entity.doorPreventLockpickExploit = 1;
314 			}
315 			else
316 			{
317 				//Failed to unlock door.
318 				playSoundEntity(&entity, 92, 64);
319 				messagePlayer(player, language[1106]);
320 				bool tryDegradeLockpick = true;
321 				if ( !entity.doorPreventLockpickExploit )
322 				{
323 					if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] < SKILL_LEVEL_SKILLED )
324 					{
325 						if ( rand() % 10 == 0 )
326 						{
327 							players[player]->entity->increaseSkill(PRO_LOCKPICKING);
328 							tryDegradeLockpick = false;
329 						}
330 					}
331 					else
332 					{
333 						if ( rand() % 20 == 0 )
334 						{
335 							messagePlayer(player, language[3689], language[674]);
336 							tryDegradeLockpick = false;
337 						}
338 					}
339 				}
340 
341 				if ( tryDegradeLockpick )
342 				{
343 					if ( rand() % 5 == 0 )
344 					{
345 						if ( player == clientnum )
346 						{
347 							if ( count > 1 )
348 							{
349 								newItem(type, status, beatitude, count - 1, appearance, identified, &stats[player]->inventory);
350 							}
351 						}
352 						stats[player]->weapon->count = 1;
353 						stats[player]->weapon->status = static_cast<Status>(stats[player]->weapon->status - 1);
354 						if ( status != BROKEN )
355 						{
356 							messagePlayer(player, language[1103]);
357 						}
358 						else
359 						{
360 							messagePlayer(player, language[1104]);
361 						}
362 						if ( player > 0 && multiplayer == SERVER )
363 						{
364 							strcpy((char*) (net_packet->data), "ARMR");
365 							net_packet->data[4] = 5;
366 							net_packet->data[5] = stats[player]->weapon->status;
367 							net_packet->address.host = net_clients[player - 1].host;
368 							net_packet->address.port = net_clients[player - 1].port;
369 							net_packet->len = 6;
370 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
371 						}
372 					}
373 				}
374 			}
375 		}
376 		else
377 		{
378 			messagePlayer(player, language[1107]);
379 		}
380 	}
381 	else if ( entity.behavior == &actMonster )
382 	{
383 		Stat* myStats = entity.getStats();
384 		if ( myStats && myStats->type == AUTOMATON
385 			&& entity.monsterSpecialState == 0
386 			&& !myStats->EFFECTS[EFF_CONFUSED] )
387 		{
388 			if ( players[player] && players[player]->entity )
389 			{
390 				// calculate facing direction from player, < PI is facing away from player
391 				real_t yawDiff = entity.yawDifferenceFromPlayer(player);
392 				if ( yawDiff < PI )
393 				{
394 					messagePlayer(player, language[2524], getName(), entity.getMonsterLangEntry());
395 					int chance = stats[player]->PROFICIENCIES[PRO_LOCKPICKING] / 20 + 1;
396 					if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] >= 60 || (rand() % chance > 0) )
397 					{
398 						// 100% >= 60 lockpicking. 40 = 66%, 20 = 50%, 0 = 0%
399 						entity.monsterSpecialState = AUTOMATON_MALFUNCTION_START;
400 						entity.monsterSpecialTimer = MONSTER_SPECIAL_COOLDOWN_AUTOMATON_MALFUNCTION;
401 						serverUpdateEntitySkill(&entity, 33);
402 
403 						myStats->EFFECTS[EFF_PARALYZED] = true;
404 						myStats->EFFECTS_TIMERS[EFF_PARALYZED] = -1;
405 						playSoundEntity(&entity, 76, 128);
406 						messagePlayer(player, language[2527], entity.getMonsterLangEntry());
407 
408 						if ( rand() % 3 == 0 )
409 						{
410 							players[player]->entity->increaseSkill(PRO_LOCKPICKING);
411 						}
412 
413 						int qtyMetalScrap = 5 + rand() % 6;
414 						int qtyMagicScrap = 8 + rand() % 6;
415 						if ( stats[player] )
416 						{
417 							if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] >= SKILL_LEVEL_MASTER )
418 							{
419 								qtyMetalScrap += 5 + rand() % 6; // 10-20 total
420 								qtyMagicScrap += 8 + rand() % 11; // 16-31 total
421 							}
422 							else if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] >= SKILL_LEVEL_EXPERT )
423 							{
424 								qtyMetalScrap += 3 + rand() % 4; // 8-16 total
425 								qtyMagicScrap += 5 + rand() % 8; // 13-25 total
426 							}
427 							else if ( stats[player]->PROFICIENCIES[PRO_LOCKPICKING] >= SKILL_LEVEL_SKILLED )
428 							{
429 								qtyMetalScrap += 1 + rand() % 4; // 6-14 total
430 								qtyMagicScrap += 3 + rand() % 4; // 11-19 total
431 							}
432 						}
433 						Item* item = newItem(TOOL_METAL_SCRAP, DECREPIT, 0, qtyMetalScrap, 0, true, &myStats->inventory);
434 						item = newItem(TOOL_MAGIC_SCRAP, DECREPIT, 0, qtyMagicScrap, 0, true, &myStats->inventory);
435 						serverUpdatePlayerGameplayStats(player, STATISTICS_BOMB_SQUAD, 1);
436 						players[player]->entity->awardXP(&entity, true, true);
437 					}
438 					else
439 					{
440 						messagePlayer(player, language[2526], entity.getMonsterLangEntry());
441 						myStats->EFFECTS[EFF_CONFUSED] = true;
442 						myStats->EFFECTS_TIMERS[EFF_CONFUSED] = -1;
443 						myStats->EFFECTS[EFF_PARALYZED] = true;
444 						myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 25;
445 						playSoundEntity(&entity, 263, 128);
446 						spawnMagicEffectParticles(entity.x, entity.y, entity.z, 170);
447 						entity.monsterAcquireAttackTarget(*players[player]->entity, MONSTER_STATE_PATH, true);
448 
449 						if ( rand() % 5 == 0 )
450 						{
451 							players[player]->entity->increaseSkill(PRO_LOCKPICKING);
452 						}
453 					}
454 					if ( rand() % 2 == 0 )
455 					{
456 						if ( player == clientnum )
457 						{
458 							if ( count > 1 )
459 							{
460 								newItem(type, status, beatitude, count - 1, appearance, identified, &stats[player]->inventory);
461 							}
462 						}
463 						stats[player]->weapon->count = 1;
464 						stats[player]->weapon->status = static_cast<Status>(stats[player]->weapon->status - 1);
465 						if ( status != BROKEN )
466 						{
467 							messagePlayer(player, language[1103]);
468 						}
469 						else
470 						{
471 							messagePlayer(player, language[1104]);
472 						}
473 						if ( player > 0 && multiplayer == SERVER )
474 						{
475 							strcpy((char*)(net_packet->data), "ARMR");
476 							net_packet->data[4] = 5;
477 							net_packet->data[5] = stats[player]->weapon->status;
478 							net_packet->address.host = net_clients[player - 1].host;
479 							net_packet->address.port = net_clients[player - 1].port;
480 							net_packet->len = 6;
481 							sendPacketSafe(net_sock, -1, net_packet, player - 1);
482 						}
483 					}
484 				}
485 				else
486 				{
487 					messagePlayer(player, language[2525], entity.getMonsterLangEntry());
488 				}
489 			}
490 		}
491 		else
492 		{
493 			messagePlayer(player, language[2528], getName());
494 		}
495 	}
496 	else
497 	{
498 		messagePlayer(player, language[1101], getName());
499 	}
500 }
501 
applyOrb(int player,ItemType type,Entity & entity)502 void Item::applyOrb(int player, ItemType type, Entity& entity)
503 {
504 	if ( entity.behavior == &actPedestalBase && entity.pedestalHasOrb == 0 )
505 	{
506 		if ( multiplayer == CLIENT )
507 		{
508 			Item* item = stats[player]->weapon;
509 			stats[player]->weapon = nullptr;
510 			consumeItem(item, player);
511 			return;
512 		}
513 		messagePlayer(player, language[2368]);
514 		bool playSound = true;
515 
516 		if ( type == ARTIFACT_ORB_BLUE && entity.pedestalOrbType == 1 )
517 		{
518 			messagePlayer(player, language[2370]);
519 		}
520 		else if ( type == ARTIFACT_ORB_RED && entity.pedestalOrbType == 2 )
521 		{
522 			messagePlayer(player, language[2370]);
523 		}
524 		else if ( type == ARTIFACT_ORB_PURPLE && entity.pedestalOrbType == 3 )
525 		{
526 			messagePlayer(player, language[2370]);
527 		}
528 		else if ( type == ARTIFACT_ORB_GREEN && entity.pedestalOrbType == 4 )
529 		{
530 			messagePlayer(player, language[2370]);
531 		}
532 		else
533 		{
534 			// incorrect orb.
535 			messagePlayer(player, language[2369]);
536 			playSound = false;
537 		}
538 
539 		if ( multiplayer != CLIENT )
540 		{
541 			if ( playSound )
542 			{
543 				playSoundEntity(&entity, 166, 128); // invisible.ogg
544 				createParticleDropRising(&entity, entity.pedestalOrbType + 605, 1.0);
545 				serverSpawnMiscParticles(&entity, PARTICLE_EFFECT_RISING_DROP, entity.pedestalOrbType + 605);
546 			}
547 			entity.pedestalHasOrb = type - ARTIFACT_ORB_BLUE + 1;
548 			serverUpdateEntitySkill(&entity, 0); // update orb status.
549 			Item* item = stats[player]->weapon;
550 			consumeItem(item, player);
551 			stats[player]->weapon = nullptr;
552 		}
553 	}
554 	else if ( entity.behavior == &actMonster || entity.behavior == &actPlayer )
555 	{
556 		if ( entity.getMonsterTypeFromSprite() == SHOPKEEPER && shopIsMysteriousShopkeeper(&entity) && this->type != ARTIFACT_ORB_PURPLE )
557 		{
558 			if ( multiplayer == CLIENT )
559 			{
560 				Item* item = stats[player]->weapon;
561 				stats[player]->weapon = nullptr;
562 				consumeItem(item, player);
563 				return;
564 			}
565 
566 			switch ( this->type )
567 			{
568 				case ARTIFACT_ORB_BLUE:
569 					messagePlayer(player, language[3889], entity.getStats()->name);
570 					break;
571 				case ARTIFACT_ORB_RED:
572 					messagePlayer(player, language[3890], entity.getStats()->name);
573 					break;
574 				case ARTIFACT_ORB_GREEN:
575 					messagePlayer(player, language[3888], entity.getStats()->name);
576 					break;
577 				default:
578 					break;
579 			}
580 
581 			playSoundEntity(&entity, 35 + rand() % 3, 64);
582 
583 			Item* item = stats[player]->weapon;
584 			entity.addItemToMonsterInventory(newItem(item->type, item->status, item->beatitude, 1, item->appearance, item->identified, nullptr));
585 			consumeItem(item, player);
586 			stats[player]->weapon = nullptr;
587 		}
588 		else
589 		{
590 			if ( multiplayer != CLIENT )
591 			{
592 				messagePlayerMonsterEvent(player, uint32ColorWhite(*mainsurface), *entity.getStats(), language[3892], language[3891], MSG_COMBAT);
593 			}
594 			return;
595 		}
596 	}
597 	else
598 	{
599 		messagePlayer(player, language[2371]);
600 	}
601 }
602 
applyEmptyPotion(int player,Entity & entity)603 void Item::applyEmptyPotion(int player, Entity& entity)
604 {
605 	if ( entity.behavior == &actFountain || entity.behavior == &actSink )
606 	{
607 		if ( entity.skill[0] <= 0 )
608 		{
609 			// fountain is dry, no bueno.
610 			if ( player == clientnum )
611 			{
612 				if ( entity.behavior == &actFountain )
613 				{
614 					messagePlayer(player, language[467]);
615 				}
616 				else
617 				{
618 					messagePlayer(player, language[580]);
619 				}
620 			}
621 			return;
622 		}
623 		if ( multiplayer == CLIENT )
624 		{
625 			Item* item = stats[player]->weapon;
626 			consumeItem(item, player);
627 			return;
628 		}
629 
630 		Item* item = stats[player]->weapon;
631 		consumeItem(item, player);
632 
633 		int skillLVL = 2; // 0 to 5
634 		if ( stats[player] )
635 		{
636 			int skillLVL = stats[player]->PROFICIENCIES[PRO_ALCHEMY] / 20;
637 		}
638 
639 		std::vector<int> potionChances =
640 		{
641 			20,	//POTION_WATER,
642 			20,	//POTION_BOOZE,
643 			20,	//POTION_JUICE,
644 			20,	//POTION_SICKNESS,
645 			10,	//POTION_CONFUSION,
646 			0,	//POTION_EXTRAHEALING,
647 			4,	//POTION_HEALING,
648 			4,	//POTION_CUREAILMENT,
649 			10,	//POTION_BLINDNESS,
650 			4,	//POTION_RESTOREMAGIC,
651 			1,	//POTION_INVISIBILITY,
652 			1,	//POTION_LEVITATION,
653 			4,	//POTION_SPEED,
654 			10,	//POTION_ACID,
655 			1,	//POTION_PARALYSIS,
656 			1,	//POTION_POLYMORPH
657 		};
658 		if ( skillLVL == 2 ) // 40 skill
659 		{
660 			potionChances =
661 			{
662 				4,	//POTION_WATER,
663 				5,	//POTION_BOOZE,
664 				5,	//POTION_JUICE,
665 				5,	//POTION_SICKNESS,
666 				5,	//POTION_CONFUSION,
667 				1,	//POTION_EXTRAHEALING,
668 				2,	//POTION_HEALING,
669 				2,	//POTION_CUREAILMENT,
670 				5,	//POTION_BLINDNESS,
671 				2,	//POTION_RESTOREMAGIC,
672 				1,	//POTION_INVISIBILITY,
673 				1,	//POTION_LEVITATION,
674 				5,	//POTION_SPEED,
675 				5,	//POTION_ACID,
676 				1,	//POTION_PARALYSIS,
677 				1,	//POTION_POLYMORPH
678 			};
679 		}
680 		else if ( skillLVL == 3 ) // 60 skill
681 		{
682 			potionChances =
683 			{
684 				2,	//POTION_WATER,
685 				4,	//POTION_BOOZE,
686 				4,	//POTION_JUICE,
687 				4,	//POTION_SICKNESS,
688 				4,	//POTION_CONFUSION,
689 				1,	//POTION_EXTRAHEALING,
690 				2,	//POTION_HEALING,
691 				2,	//POTION_CUREAILMENT,
692 				4,	//POTION_BLINDNESS,
693 				2,	//POTION_RESTOREMAGIC,
694 				1,	//POTION_INVISIBILITY,
695 				1,	//POTION_LEVITATION,
696 				4,	//POTION_SPEED,
697 				4,	//POTION_ACID,
698 				1,	//POTION_PARALYSIS,
699 				1,	//POTION_POLYMORPH
700 			};
701 		}
702 		else if ( skillLVL == 4 ) // 80 skill
703 		{
704 			potionChances =
705 			{
706 				0,	//POTION_WATER,
707 				2,	//POTION_BOOZE,
708 				2,	//POTION_JUICE,
709 				2,	//POTION_SICKNESS,
710 				3,	//POTION_CONFUSION,
711 				1,	//POTION_EXTRAHEALING,
712 				2,	//POTION_HEALING,
713 				2,	//POTION_CUREAILMENT,
714 				3,	//POTION_BLINDNESS,
715 				2,	//POTION_RESTOREMAGIC,
716 				1,	//POTION_INVISIBILITY,
717 				1,	//POTION_LEVITATION,
718 				3,	//POTION_SPEED,
719 				3,	//POTION_ACID,
720 				1,	//POTION_PARALYSIS,
721 				1,	//POTION_POLYMORPH
722 			};
723 		}
724 		else if ( skillLVL == 5 ) // 100 skill
725 		{
726 			potionChances =
727 			{
728 				0,	//POTION_WATER,
729 				1,	//POTION_BOOZE,
730 				1,	//POTION_JUICE,
731 				1,	//POTION_SICKNESS,
732 				1,	//POTION_CONFUSION,
733 				1,	//POTION_EXTRAHEALING,
734 				2,	//POTION_HEALING,
735 				2,	//POTION_CUREAILMENT,
736 				2,	//POTION_BLINDNESS,
737 				2,	//POTION_RESTOREMAGIC,
738 				1,	//POTION_INVISIBILITY,
739 				1,	//POTION_LEVITATION,
740 				2,	//POTION_SPEED,
741 				2,	//POTION_ACID,
742 				1,	//POTION_PARALYSIS,
743 				1,	//POTION_POLYMORPH
744 			};
745 		}
746 
747 
748 		if ( entity.behavior == &actFountain )
749 		{
750 			std::discrete_distribution<> potionDistribution(potionChances.begin(), potionChances.end());
751 			auto generatedPotion = potionStandardAppearanceMap.at(potionDistribution(fountainSeed));
752 			item = newItem(static_cast<ItemType>(generatedPotion.first), SERVICABLE, 0, 1, generatedPotion.second, false, NULL);
753 		}
754 		else
755 		{
756 			if ( entity.skill[3] == 1 ) // slime
757 			{
758 				item = newItem(POTION_ACID, SERVICABLE, 0, 1, 0, false, NULL);
759 			}
760 			else
761 			{
762 				item = newItem(POTION_WATER, SERVICABLE, 0, 1, 0, false, NULL);
763 			}
764 		}
765 		if ( item )
766 		{
767 			itemPickup(player, item);
768 			messagePlayer(player, language[3353], item->description());
769 			if ( players[player] && players[player]->entity )
770 			{
771 				playSoundEntity(players[player]->entity, 401, 64);
772 			}
773 			free(item);
774 			steamStatisticUpdateClient(player, STEAM_STAT_FREE_REFILLS, STEAM_STAT_INT, 1);
775 		}
776 
777 		if ( entity.behavior == &actSink )
778 		{
779 			if ( entity.skill[3] == 1 || entity.skill[3] == 0 ) // ring or a slime
780 			{
781 				if ( player > 0 )
782 				{
783 					client_selected[player] = &entity;
784 					entity.skill[8] = 1; // disables polymorph being washed away.
785 					actSink(&entity);
786 					entity.skill[8] = 0;
787 				}
788 				else if ( player == 0 )
789 				{
790 					selectedEntity = &entity;
791 					entity.skill[8] = 1; // disables polymorph being washed away.
792 					actSink(&entity);
793 					entity.skill[8] = 0;
794 				}
795 			}
796 			else if ( entity.skill[0] > 1 )
797 			{
798 				--entity.skill[0];
799 				// Randomly choose second usage stats.
800 				int effect = rand() % 10; //4 possible effects.
801 				switch ( effect )
802 				{
803 					case 0:
804 						//10% chance.
805 						entity.skill[3] = 0; //Player will find a ring.
806 					case 1:
807 						//10% chance.
808 						entity.skill[3] = 1; //Will spawn a slime.
809 						break;
810 					case 2:
811 					case 3:
812 					case 4:
813 					case 5:
814 					case 6:
815 					case 7:
816 						//60% chance.
817 						entity.skill[3] = 2; //Will raise nutrition.
818 						break;
819 					case 8:
820 					case 9:
821 						//20% chance.
822 						entity.skill[3] = 3; //Player will lose 1 HP.
823 						break;
824 					default:
825 						break; //Should never happen.
826 				}
827 			}
828 			else
829 			{
830 				--entity.skill[0];
831 				entity.skill[0] = std::max(entity.skill[0], 0);
832 				serverUpdateEntitySkill(&entity, 0);
833 			}
834 		}
835 		else if ( entity.skill[1] == 2 || entity.skill[1] == 1 ) // fountain would spawn potions
836 		{
837 			//messagePlayer(player, language[474]);
838 			entity.skill[0] = 0; //Dry up fountain.
839 			serverUpdateEntitySkill(&entity, 0);
840 		}
841 		else if ( entity.skill[1] == 3 || entity.skill[1] == 4 )
842 		{
843 			// fountain would bless equipment.
844 			entity.skill[0] = 0; //Dry up fountain.
845 			serverUpdateEntitySkill(&entity, 0);
846 		}
847 		else if ( skillLVL < 2 || (skillLVL >= 2 && rand() % (skillLVL) == 0 ) )
848 		{
849 			if ( player > 0 )
850 			{
851 				client_selected[player] = &entity;
852 				actFountain(&entity);
853 			}
854 			else if ( player == 0 )
855 			{
856 				selectedEntity = &entity;
857 				actFountain(&entity);
858 			}
859 		}
860 	}
861 	else
862 	{
863 		messagePlayer(player, language[2371]);
864 	}
865 }
866 
applyBomb(Entity * parent,ItemType type,ItemBombPlacement placement,ItemBombFacingDirection dir,Entity * thrown,Entity * onEntity)867 void Item::applyBomb(Entity* parent, ItemType type, ItemBombPlacement placement, ItemBombFacingDirection dir, Entity* thrown, Entity* onEntity)
868 {
869 	if ( multiplayer == CLIENT )
870 	{
871 		return;
872 	}
873 
874 	int sprite = items[TOOL_BOMB].index + 1;
875 	if ( type >= TOOL_BOMB && type <= TOOL_TELEPORT_BOMB )
876 	{
877 		sprite = items[type].index + 1; // unpacked bomb model.
878 	}
879 
880 	if ( placement == BOMB_FLOOR )
881 	{
882 		if ( thrown )
883 		{
884 			Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Beartrap entity.
885 			entity->behavior = &actBomb;
886 			entity->flags[PASSABLE] = true;
887 			entity->flags[UPDATENEEDED] = true;
888 			entity->x = thrown->x;
889 			entity->y = thrown->y;
890 			entity->z = 6.5;
891 			entity->yaw = thrown->yaw;
892 			entity->roll = -PI / 2; // flip the model
893 			if ( parent )
894 			{
895 				entity->parent = parent->getUID();
896 				if ( parent->behavior == &actPlayer )
897 				{
898 					entity->skill[17] = parent->skill[2];
899 				}
900 				else
901 				{
902 					entity->skill[17] = -1;
903 				}
904 			}
905 			entity->sizex = 4;
906 			entity->sizey = 4;
907 			entity->skill[11] = this->status;
908 			entity->skill[12] = this->beatitude;
909 			entity->skill[14] = this->appearance;
910 			entity->skill[15] = this->identified;
911 			entity->skill[16] = placement;
912 			entity->skill[20] = dir;
913 			entity->skill[21] = type;
914 		}
915 	}
916 	else if ( placement == BOMB_WALL )
917 	{
918 		if ( thrown )
919 		{
920 			Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Beartrap entity.
921 			entity->behavior = &actBomb;
922 			entity->flags[PASSABLE] = true;
923 			entity->flags[UPDATENEEDED] = true;
924 			entity->x = thrown->x;
925 			entity->y = thrown->y;
926 			entity->z = std::min(4.0, thrown->z);
927 			int height = 1;
928 			switch ( dir )
929 			{
930 				case BOMB_EAST:
931 					entity->yaw = 3 * PI / 2;
932 					entity->x = (static_cast<int>(std::floor(entity->x + 8)) >> 4) * 16;
933 					entity->x += height;
934 					break;
935 				case BOMB_SOUTH:
936 					entity->yaw = 0;
937 					entity->y = (static_cast<int>(std::floor(entity->y + 8)) >> 4) * 16;
938 					entity->y += height;
939 					break;
940 				case BOMB_WEST:
941 					entity->yaw = PI / 2;
942 					entity->x = (static_cast<int>(std::floor(entity->x + 8)) >> 4) * 16;
943 					entity->x -= height;
944 					break;
945 				case BOMB_NORTH:
946 					entity->yaw = PI;
947 					entity->y = (static_cast<int>(std::floor(entity->y + 8)) >> 4) * 16;
948 					entity->y -= height;
949 					break;
950 				default:
951 					break;
952 			}
953 
954 			// check the wall because there is an edge case where the model clips a wall edge and there's technically no wall behind it
955 			// = boom for player.
956 			int checkx = entity->x;
957 			int checky = entity->y;
958 			switch ( dir )
959 			{
960 				case BOMB_EAST:
961 					checkx = static_cast<int>(entity->x - 8) >> 4;
962 					checky = static_cast<int>(entity->y) >> 4;
963 					break;
964 				case BOMB_WEST:
965 					checkx = static_cast<int>(entity->x + 8) >> 4;
966 					checky = static_cast<int>(entity->y) >> 4;
967 					break;
968 				case BOMB_SOUTH:
969 					checky = static_cast<int>(entity->y - 8) >> 4;
970 					checkx = static_cast<int>(entity->x) >> 4;
971 					break;
972 				case BOMB_NORTH:
973 					checky = static_cast<int>(entity->y + 8) >> 4;
974 					checkx = static_cast<int>(entity->x) >> 4;
975 					break;
976 				default:
977 					break;
978 			}
979 			if ( !map.tiles[OBSTACLELAYER + checky * MAPLAYERS + checkx * MAPLAYERS * map.height] )
980 			{
981 				// no wall.
982 				switch ( dir )
983 				{
984 					case BOMB_EAST:
985 					case BOMB_WEST:
986 						if ( map.tiles[OBSTACLELAYER + (static_cast<int>(entity->y + 4) >> 4) * MAPLAYERS + checkx * MAPLAYERS * map.height] )
987 						{
988 							// try 4 units away.
989 							entity->y += 4; // coordinates good.
990 						}
991 						else if ( map.tiles[OBSTACLELAYER + (static_cast<int>(entity->y - 4) >> 4) * MAPLAYERS + checkx * MAPLAYERS * map.height] )
992 						{
993 							// try 4 units away other direction.
994 							entity->y -= 4; // coordinates good.
995 						}
996 						break;
997 					case BOMB_NORTH:
998 					case BOMB_SOUTH:
999 						if ( map.tiles[OBSTACLELAYER + checky * MAPLAYERS + (static_cast<int>(entity->x + 4) >> 4)  * MAPLAYERS * map.height] )
1000 						{
1001 							// try 4 units away.
1002 							entity->x += 4; // coordinates good.
1003 						}
1004 						else if ( map.tiles[OBSTACLELAYER + checky * MAPLAYERS + (static_cast<int>(entity->x - 4) >> 4)  * MAPLAYERS * map.height] )
1005 						{
1006 							// try 4 units away other direction.
1007 							entity->x -= 4; // coordinates good.
1008 						}
1009 						break;
1010 					default:
1011 						break;
1012 				}
1013 			}
1014 
1015 			entity->roll = 0; // flip the model
1016 			if ( parent )
1017 			{
1018 				entity->parent = parent->getUID();
1019 				if ( parent->behavior == &actPlayer )
1020 				{
1021 					entity->skill[17] = parent->skill[2];
1022 				}
1023 				else
1024 				{
1025 					entity->skill[17] = -1;
1026 				}
1027 			}
1028 			entity->sizex = 4;
1029 			entity->sizey = 4;
1030 			entity->skill[11] = this->status;
1031 			entity->skill[12] = this->beatitude;
1032 			entity->skill[14] = this->appearance;
1033 			entity->skill[15] = this->identified;
1034 			entity->skill[16] = placement;
1035 			entity->skill[20] = dir;
1036 			entity->skill[21] = type;
1037 		}
1038 	}
1039 	else if ( placement == BOMB_CHEST || placement == BOMB_DOOR )
1040 	{
1041 		if ( thrown && onEntity && (hit.entity == onEntity) )
1042 		{
1043 			Entity* entity = newEntity(sprite, 1, map.entities, nullptr); //Beartrap entity.
1044 			entity->behavior = &actBomb;
1045 			entity->flags[PASSABLE] = true;
1046 			entity->flags[UPDATENEEDED] = true;
1047 			entity->x = thrown->x;
1048 			entity->y = thrown->y;
1049 			entity->z = std::min(4.0, thrown->z);
1050 			if ( placement == BOMB_CHEST )
1051 			{
1052 				entity->z = std::max(2.0, entity->z);
1053 			}
1054 
1055 			if ( hit.side == 0 )
1056 			{
1057 				// pick a random side to be on.
1058 				if ( rand() % 2 == 0 )
1059 				{
1060 					hit.side = HORIZONTAL;
1061 				}
1062 				else
1063 				{
1064 					hit.side = VERTICAL;
1065 				}
1066 			}
1067 
1068 			if ( hit.side == HORIZONTAL )
1069 			{
1070 				if ( thrown->vel_x > 0 )
1071 				{
1072 					dir = BOMB_WEST;
1073 				}
1074 				else
1075 				{
1076 					dir = BOMB_EAST;
1077 				}
1078 			}
1079 			else if ( hit.side == VERTICAL )
1080 			{
1081 				if ( thrown->vel_y > 0 )
1082 				{
1083 					dir = BOMB_NORTH;
1084 				}
1085 				else
1086 				{
1087 					dir = BOMB_SOUTH;
1088 				}
1089 			}
1090 			real_t height = 0;
1091 			if ( placement == BOMB_CHEST )
1092 			{
1093 				if ( onEntity->yaw == 0 || onEntity->yaw == PI ) //EAST/WEST FACING
1094 				{
1095 					if ( hit.side == HORIZONTAL )
1096 					{
1097 						height = 5.25;
1098 					}
1099 					else
1100 					{
1101 						height = 4.25;
1102 					}
1103 				}
1104 				else if ( onEntity->yaw == PI / 2 || onEntity->yaw == 3 * PI / 2 ) //SOUTH/NORTH FACING
1105 				{
1106 					if ( hit.side == VERTICAL )
1107 					{
1108 						height = 4.25;
1109 					}
1110 					else
1111 					{
1112 						height = 5.25;
1113 					}
1114 				}
1115 			}
1116 			else if ( placement == BOMB_DOOR )
1117 			{
1118 				if ( onEntity->yaw == 0 || onEntity->yaw == PI ) //EAST/WEST FACING
1119 				{
1120 					if ( hit.side == HORIZONTAL )
1121 					{
1122 						height = 2;
1123 					}
1124 				}
1125 				else if ( onEntity->yaw == -PI / 2 ) //SOUTH/NORTH FACING
1126 				{
1127 					if ( hit.side == VERTICAL )
1128 					{
1129 						height = 2;
1130 					}
1131 				}
1132 			}
1133 
1134 			switch ( dir )
1135 			{
1136 				case BOMB_EAST:
1137 					entity->yaw = 3 * PI / 2;
1138 					entity->x = onEntity->x;
1139 					if ( onEntity->yaw == 0 && placement == BOMB_CHEST )
1140 					{
1141 						height += 0.5;
1142 					}
1143 					entity->x += height;
1144 					break;
1145 				case BOMB_SOUTH:
1146 					entity->yaw = 0;
1147 					entity->y = onEntity->y;
1148 					if ( onEntity->yaw == PI / 2 && placement == BOMB_CHEST )
1149 					{
1150 						height -= 0.5;
1151 					}
1152 					entity->y += height;
1153 					break;
1154 				case BOMB_WEST:
1155 					entity->yaw = PI / 2;
1156 					entity->x = onEntity->x;
1157 					if ( onEntity->yaw == PI && placement == BOMB_CHEST )
1158 					{
1159 						height -= 0.5;
1160 					}
1161 					entity->x -= height;
1162 					break;
1163 				case BOMB_NORTH:
1164 					entity->yaw = PI;
1165 					entity->y = onEntity->y;
1166 					if ( onEntity->yaw == 3 * PI / 2 && placement == BOMB_CHEST )
1167 					{
1168 						height += 0.5;
1169 					}
1170 					entity->y -= height;
1171 					break;
1172 				default:
1173 					break;
1174 			}
1175 			entity->roll = 0; // flip the model
1176 			if ( parent )
1177 			{
1178 				entity->parent = parent->getUID();
1179 				if ( parent->behavior == &actPlayer )
1180 				{
1181 					entity->skill[17] = parent->skill[2];
1182 				}
1183 				else
1184 				{
1185 					entity->skill[17] = -1;
1186 				}
1187 			}
1188 			entity->sizex = 4;
1189 			entity->sizey = 4;
1190 			entity->skill[11] = this->status;
1191 			entity->skill[12] = this->beatitude;
1192 			entity->skill[14] = this->appearance;
1193 			entity->skill[15] = this->identified;
1194 			entity->skill[16] = placement;
1195 			entity->skill[18] = static_cast<Sint32>(onEntity->getUID());
1196 			if ( placement == BOMB_DOOR )
1197 			{
1198 				entity->skill[19] = onEntity->doorHealth;
1199 			}
1200 			else if ( placement == BOMB_CHEST )
1201 			{
1202 				entity->skill[19] = onEntity->skill[3]; //chestHealth
1203 				entity->skill[22] = onEntity->skill[1];
1204 			}
1205 			entity->skill[20] = dir;
1206 			entity->skill[21] = type;
1207 		}
1208 	}
1209 }
1210 
applyTinkeringCreation(Entity * parent,Entity * thrown)1211 void Item::applyTinkeringCreation(Entity* parent, Entity* thrown)
1212 {
1213 	if ( !thrown )
1214 	{
1215 		return;
1216 	}
1217 	if ( type == TOOL_DECOY )
1218 	{
1219 		Entity* entity = newEntity(894, 1, map.entities, nullptr); //Decoy box.
1220 		entity->behavior = &actDecoyBox;
1221 		entity->flags[PASSABLE] = true;
1222 		entity->flags[UPDATENEEDED] = true;
1223 		entity->x = thrown->x;
1224 		entity->y = thrown->y;
1225 		entity->z = limbs[DUMMYBOT][9][2] - 0.25;
1226 		entity->yaw = thrown->yaw;
1227 		entity->skill[2] = -14;
1228 		if ( parent )
1229 		{
1230 			entity->parent = parent->getUID();
1231 		}
1232 		entity->sizex = 4;
1233 		entity->sizey = 4;
1234 		entity->skill[10] = this->type;
1235 		entity->skill[11] = this->status;
1236 		entity->skill[12] = this->beatitude;
1237 		entity->skill[13] = 1;
1238 		entity->skill[14] = this->appearance;
1239 		entity->skill[15] = 1;
1240 	}
1241 	else if ( type == TOOL_DUMMYBOT || type == TOOL_GYROBOT || type == TOOL_SENTRYBOT || type == TOOL_SPELLBOT )
1242 	{
1243 		Monster monsterType = DUMMYBOT;
1244 		if ( type == TOOL_GYROBOT )
1245 		{
1246 			monsterType = GYROBOT;
1247 		}
1248 		else if ( type == TOOL_SENTRYBOT )
1249 		{
1250 			monsterType = SENTRYBOT;
1251 		}
1252 		else if ( type == TOOL_SPELLBOT )
1253 		{
1254 			monsterType = SPELLBOT;
1255 		}
1256 
1257 		bool exactLocation = true;
1258 		Entity* summon = summonMonster(monsterType, thrown->x, thrown->y, true);
1259 		if ( !summon )
1260 		{
1261 			exactLocation = false;
1262 			summon = summonMonster(monsterType, floor(thrown->x / 16) * 16 + 8, floor(thrown->y / 16) * 16 + 8, false);
1263 		}
1264 		if ( summon )
1265 		{
1266 			Stat* summonedStats = summon->getStats();
1267 			if ( parent && parent->behavior == &actPlayer && summonedStats )
1268 			{
1269 				if ( summonedStats->type == GYROBOT )
1270 				{
1271 					summon->yaw = thrown->yaw;
1272 					summon->monsterSpecialState = GYRO_START_FLYING;
1273 					serverUpdateEntitySkill(summon, 33);
1274 					playSoundPos(summon->x, summon->y, 415, 128);
1275 				}
1276 				else if ( summonedStats->type == SENTRYBOT || summonedStats->type == SPELLBOT )
1277 				{
1278 					summon->yaw = thrown->yaw;
1279 					if ( exactLocation )
1280 					{
1281 						summon->x = thrown->x;
1282 						summon->y = thrown->y;
1283 					}
1284 					summonedStats->EFFECTS[EFF_STUNNED] = true;
1285 					summonedStats->EFFECTS_TIMERS[EFF_STUNNED] = 30;
1286 					playSoundEntity(summon, 453 + rand() % 2, 192);
1287 				}
1288 				else
1289 				{
1290 					summon->yaw = thrown->yaw + ((PI / 2) * (rand() % 4));
1291 					if ( summonedStats->type == DUMMYBOT )
1292 					{
1293 						playSoundEntity(summon, 417 + rand() % 3, 128);
1294 					}
1295 				}
1296 				summonedStats->monsterTinkeringStatus = static_cast<Sint32>(this->status); // store the type of item that was used to summon me.
1297 				summon->tinkerBotSetStats(summonedStats, this->status);
1298 				if ( !this->tinkeringBotIsMaxHealth() )
1299 				{
1300 					summon->setHP(monsterTinkeringConvertAppearanceToHP(summonedStats, this->appearance));
1301 				}
1302 				if ( forceFollower(*parent, *summon) )
1303 				{
1304 					if ( parent->behavior == &actPlayer )
1305 					{
1306 						summon->monsterAllyIndex = parent->skill[2];
1307 						if ( multiplayer == SERVER )
1308 						{
1309 							serverUpdateEntitySkill(summon, 42); // update monsterAllyIndex for clients.
1310 						}
1311 					}
1312 					// change the color of the hit entity.
1313 					summon->flags[USERFLAG2] = true;
1314 					serverUpdateEntityFlag(summon, USERFLAG2);
1315 				}
1316 			}
1317 		}
1318 	}
1319 }
1320 
itemIsThrowableTinkerTool(const Item * item)1321 bool itemIsThrowableTinkerTool(const Item* item)
1322 {
1323 	if ( !item )
1324 	{
1325 		return false;
1326 	}
1327 	if ( (item->type >= TOOL_BOMB && item->type <= TOOL_TELEPORT_BOMB)
1328 		|| item->type == TOOL_DECOY
1329 		|| item->type == TOOL_SENTRYBOT
1330 		|| item->type == TOOL_SPELLBOT
1331 		|| item->type == TOOL_GYROBOT
1332 		|| item->type == TOOL_DUMMYBOT )
1333 	{
1334 		return true;
1335 	}
1336 	return false;
1337 }