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 }