1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: magic.cpp
5 Desc: contains magic definitions
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 "../entity.hpp"
16 #include "../interface/interface.hpp"
17 #include "../sound.hpp"
18 #include "../items.hpp"
19 #include "../net.hpp"
20 #include "../player.hpp"
21 #include "magic.hpp"
22 #include "../collision.hpp"
23 #include "../classdescriptions.hpp"
24 #include "../scores.hpp"
25
freeSpells()26 void freeSpells()
27 {
28 list_FreeAll(&spell_forcebolt.elements);
29 list_FreeAll(&spell_magicmissile.elements);
30 list_FreeAll(&spell_cold.elements);
31 list_FreeAll(&spell_fireball.elements);
32 list_FreeAll(&spell_lightning.elements);
33 list_FreeAll(&spell_removecurse.elements);
34 list_FreeAll(&spell_light.elements);
35 list_FreeAll(&spell_identify.elements);
36 list_FreeAll(&spell_magicmapping.elements);
37 list_FreeAll(&spell_sleep.elements);
38 list_FreeAll(&spell_confuse.elements);
39 list_FreeAll(&spell_slow.elements);
40 list_FreeAll(&spell_opening.elements);
41 list_FreeAll(&spell_locking.elements);
42 list_FreeAll(&spell_levitation.elements);
43 list_FreeAll(&spell_invisibility.elements);
44 list_FreeAll(&spell_teleportation.elements);
45 list_FreeAll(&spell_healing.elements);
46 list_FreeAll(&spell_extrahealing.elements);
47 list_FreeAll(&spell_cureailment.elements);
48 list_FreeAll(&spell_dig.elements);
49 list_FreeAll(&spell_summon.elements);
50 list_FreeAll(&spell_stoneblood.elements);
51 list_FreeAll(&spell_bleed.elements);
52 list_FreeAll(&spell_dominate.elements);
53 list_FreeAll(&spell_reflectMagic.elements);
54 list_FreeAll(&spell_acidSpray.elements);
55 list_FreeAll(&spell_stealWeapon.elements);
56 list_FreeAll(&spell_drainSoul.elements);
57 list_FreeAll(&spell_vampiricAura.elements);
58 list_FreeAll(&spell_charmMonster.elements);
59 list_FreeAll(&spell_revertForm.elements);
60 list_FreeAll(&spell_ratForm.elements);
61 list_FreeAll(&spell_spiderForm.elements);
62 list_FreeAll(&spell_trollForm.elements);
63 list_FreeAll(&spell_impForm.elements);
64 list_FreeAll(&spell_sprayWeb.elements);
65 list_FreeAll(&spell_poison.elements);
66 list_FreeAll(&spell_speed.elements);
67 list_FreeAll(&spell_fear.elements);
68 list_FreeAll(&spell_strike.elements);
69 list_FreeAll(&spell_detectFood.elements);
70 list_FreeAll(&spell_weakness.elements);
71 list_FreeAll(&spell_amplifyMagic.elements);
72 list_FreeAll(&spell_shadowTag.elements);
73 list_FreeAll(&spell_telePull.elements);
74 list_FreeAll(&spell_demonIllusion.elements);
75 list_FreeAll(&spell_trollsBlood.elements);
76 list_FreeAll(&spell_salvageItem.elements);
77 list_FreeAll(&spell_flutter.elements);
78 list_FreeAll(&spell_dash.elements);
79 list_FreeAll(&spell_polymorph.elements);
80 }
81
spell_magicMap(int player)82 void spell_magicMap(int player)
83 {
84 if (players[player] == nullptr || players[player]->entity == nullptr)
85 {
86 return;
87 }
88
89 if ( multiplayer == SERVER && player > 0 )
90 {
91 //Tell the client to map the magic.
92 strcpy((char*)net_packet->data, "MMAP");
93 net_packet->address.host = net_clients[player - 1].host;
94 net_packet->address.port = net_clients[player - 1].port;
95 net_packet->len = 4;
96 sendPacketSafe(net_sock, -1, net_packet, player - 1);
97 return;
98 }
99
100 messagePlayer(player, language[412]);
101 mapLevel(player);
102 }
103
spell_detectFoodEffectOnMap(int player)104 void spell_detectFoodEffectOnMap(int player)
105 {
106 if ( players[player] == nullptr || players[player]->entity == nullptr )
107 {
108 return;
109 }
110
111 if ( multiplayer == SERVER && player > 0 )
112 {
113 //Tell the client to map the food.
114 strcpy((char*)net_packet->data, "MFOD");
115 net_packet->address.host = net_clients[player - 1].host;
116 net_packet->address.port = net_clients[player - 1].port;
117 net_packet->len = 4;
118 sendPacketSafe(net_sock, -1, net_packet, player - 1);
119 return;
120 }
121
122 mapFoodOnLevel(player);
123 }
124
spell_summonFamiliar(int player)125 void spell_summonFamiliar(int player)
126 {
127 // server only function
128 if ( players[player] == nullptr || players[player]->entity == nullptr )
129 {
130 return;
131 }
132
133 Uint32 numCreatures = 1;
134 Monster creature = RAT;
135
136 // spawn something really nasty
137 /*numCreatures = 1;
138 switch ( rand() % 4 )
139 {
140 case 0:
141 creature = MINOTAUR;
142 break;
143 case 1:
144 creature = DEMON;
145 break;
146 case 2:
147 creature = CREATURE_IMP;
148 break;
149 case 3:
150 creature = TROLL;
151 break;
152 }*/
153 // spawn moderately nasty things
154 //switch ( rand() % 6 )
155 //{
156 // case 0:
157 // creature = GNOME;
158 // numCreatures = rand() % 3 + 1;
159 // break;
160 // case 1:
161 // creature = SPIDER;
162 // numCreatures = rand() % 2 + 1;
163 // break;
164 // case 2:
165 // creature = SUCCUBUS;
166 // numCreatures = rand() % 2 + 1;
167 // break;
168 // case 3:
169 // creature = SCORPION;
170 // numCreatures = rand() % 2 + 1;
171 // break;
172 // case 4:
173 // creature = GHOUL;
174 // numCreatures = rand() % 2 + 1;
175 // break;
176 // case 5:
177 // creature = GOBLIN;
178 // numCreatures = rand() % 2 + 1;
179 // break;
180 //}
181
182 // spawn weak monster ally
183 switch ( rand() % 3 )
184 {
185 case 0:
186 creature = RAT;
187 numCreatures = rand() % 3 + 1;
188 break;
189 case 1:
190 creature = GHOUL;
191 numCreatures = 1;
192 break;
193 case 2:
194 creature = SLIME;
195 numCreatures = rand() % 2 + 1;
196 break;
197 }
198
199 //// spawn humans
200 //creature = HUMAN;
201 //numCreatures = rand() % 3 + 1;
202
203 ////Spawn many/neat allies
204 //switch ( rand() % 2 )
205 //{
206 // case 0:
207 // // summon zap brigadiers
208 // numCreatures = rand() % 2 + 4;
209 // creature = HUMAN;
210 // break;
211 // case 1:
212 // // summon demons
213 // numCreatures = rand() % 2 + 4;
214 // creature = DEMON;
215 // break;
216 //
217 //}
218
219 int i;
220 bool spawnedMonster = false;
221 for ( i = 0; i < numCreatures; ++i )
222 {
223 Entity* monster = summonMonster(creature, floor(players[player]->entity->x / 16) * 16 + 8, floor(players[player]->entity->y / 16) * 16 + 8);
224
225 if ( monster )
226 {
227 spawnedMonster = true;
228
229 Stat* monsterStats = monster->getStats();
230 if ( monsterStats )
231 {
232 monsterStats->leader_uid = players[player]->entity->getUID();
233 monster->flags[USERFLAG2] = true;
234 monster->monsterAllyIndex = player;
235 if ( multiplayer == SERVER )
236 {
237 serverUpdateEntitySkill(monster, 42); // update monsterAllyIndex for clients.
238 }
239
240 // update followers for this player
241 node_t* newNode = list_AddNodeLast(&stats[player]->FOLLOWERS);
242 newNode->deconstructor = &defaultDeconstructor;
243 Uint32* myuid = (Uint32*)malloc(sizeof(Uint32));
244 newNode->element = myuid;
245 *myuid = monster->getUID();
246
247 // update client followers
248 if ( player > 0 && multiplayer == SERVER )
249 {
250 strcpy((char*)net_packet->data, "LEAD");
251 SDLNet_Write32((Uint32)monster->getUID(), &net_packet->data[4]);
252 strcpy((char*)(&net_packet->data[8]), monsterStats->name);
253 net_packet->data[8 + strlen(monsterStats->name)] = 0;
254 net_packet->address.host = net_clients[player - 1].host;
255 net_packet->address.port = net_clients[player - 1].port;
256 net_packet->len = 8 + strlen(monsterStats->name) + 1;
257 sendPacketSafe(net_sock, -1, net_packet, player - 1);
258
259 serverUpdateAllyStat(player, monster->getUID(), monsterStats->LVL, monsterStats->HP, monsterStats->MAXHP, monsterStats->type);
260 }
261
262 if ( !FollowerMenu.recentEntity && player == clientnum )
263 {
264 FollowerMenu.recentEntity = monster;
265 }
266 }
267 }
268 }
269 if ( spawnedMonster )
270 {
271 if ( numCreatures <= 1 )
272 {
273 if ( creature < KOBOLD ) //Original monster count
274 {
275 messagePlayer(player, language[879], language[90 + creature]);
276
277 }
278 else if ( creature >= KOBOLD ) //New monsters
279 {
280 messagePlayer(player, language[879], language[2000 + (creature - KOBOLD)]);
281 }
282 /*if ( item->beatitude >= 2 )
283 {
284 messagePlayer(player, language[880]);
285 }*/
286 }
287 else
288 {
289 if ( creature < KOBOLD ) //Original monster count
290 {
291 messagePlayer(player, language[881], language[111 + creature]);
292
293 }
294 else if ( creature >= KOBOLD ) //New monsters
295 {
296 messagePlayer(player, language[881], language[2050 + (creature - KOBOLD)]);
297 }
298 //if ( item->beatitude >= 2 )
299 //{
300 // messagePlayer(player, language[882]);
301 //}
302 }
303 }
304 }
305
spellEffectDominate(Entity & my,spellElement_t & element,Entity & caster,Entity * parent)306 bool spellEffectDominate(Entity& my, spellElement_t& element, Entity& caster, Entity* parent)
307 {
308 if ( !hit.entity )
309 {
310 return false;
311 }
312
313 if ( hit.entity->behavior != &actMonster )
314 {
315 return false;
316 }
317
318 Stat* hitstats = hit.entity->getStats();
319 if ( !hitstats )
320 {
321 return false;
322 }
323
324 //Abort if invalid creature (boss, shopkeep, etc).
325 if ( hitstats->type == MINOTAUR
326 || hitstats->type == LICH
327 || hitstats->type == DEVIL
328 || hitstats->type == SHOPKEEPER
329 || hitstats->type == LICH_ICE
330 || hitstats->type == LICH_FIRE
331 || hitstats->type == SHADOW
332 || (hitstats->type == VAMPIRE && !strncmp(hitstats->name, "Bram Kindly", 11))
333 || (hitstats->type == COCKATRICE && !strncmp(map.name, "Cockatrice Lair", 15))
334 )
335 {
336 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
337 if ( parent )
338 {
339 messagePlayerColor(parent->skill[2], color, language[2429]);
340 }
341 return false;
342 }
343
344 playSoundEntity(hit.entity, 174, 64); //TODO: Dominate spell sound effect.
345
346 //Make the monster a follower.
347 bool dominated = forceFollower(caster, *hit.entity);
348
349 if ( parent && dominated )
350 {
351 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
352 if ( parent->behavior == &actPlayer )
353 {
354 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2428], language[2427], MSG_COMBAT);
355 }
356
357 hit.entity->monsterAllyIndex = parent->skill[2];
358 if ( multiplayer == SERVER )
359 {
360 serverUpdateEntitySkill(hit.entity, 42); // update monsterAllyIndex for clients.
361 }
362 // change the color of the hit entity.
363
364 hit.entity->flags[USERFLAG2] = true;
365 serverUpdateEntityFlag(hit.entity, USERFLAG2);
366 if ( monsterChangesColorWhenAlly(hitstats) )
367 {
368 int bodypart = 0;
369 for ( node_t* node = (hit.entity)->children.first; node != nullptr; node = node->next )
370 {
371 if ( bodypart >= LIMB_HUMANOID_TORSO )
372 {
373 Entity* tmp = (Entity*)node->element;
374 if ( tmp )
375 {
376 tmp->flags[USERFLAG2] = true;
377 //serverUpdateEntityFlag(tmp, USERFLAG2);
378 }
379 }
380 ++bodypart;
381 }
382 }
383
384 caster.drainMP(hitstats->HP); //Drain additional MP equal to health of monster.
385 Stat* casterStats = caster.getStats();
386 if ( casterStats && casterStats->HP <= 0 )
387 {
388 // uh oh..
389 if ( casterStats->amulet && casterStats->amulet->type == AMULET_LIFESAVING && casterStats->amulet->beatitude >= 0 )
390 {
391 // we're good!
392 steamAchievementEntity(&caster, "BARONY_ACH_LIFE_FOR_A_LIFE");
393 }
394 }
395 }
396
397 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
398 return true;
399 }
400
spellEffectAcid(Entity & my,spellElement_t & element,Entity * parent,int resistance)401 void spellEffectAcid(Entity& my, spellElement_t& element, Entity* parent, int resistance)
402 {
403 playSoundEntity(&my, 173, 128);
404
405 if ( hit.entity )
406 {
407 int damage = element.damage;
408 damage += damage * ((my.actmagicSpellbookBonus / 100.f) + getBonusFromCasterOfSpellElement(parent, &element));
409 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
410
411 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
412 {
413 Entity* parent = uidToEntity(my.parent);
414 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
415 {
416 // test for friendly fire
417 if ( parent && parent->checkFriend(hit.entity) )
418 {
419 return;
420 }
421 }
422 //playSoundEntity(&my, 173, 64);
423 playSoundEntity(hit.entity, 249, 64);
424 //playSoundEntity(hit.entity, 28, 64);
425
426 Stat* hitstats = hit.entity->getStats();
427 if ( !hitstats )
428 {
429 return;
430 }
431 bool hasamulet = false;
432 if ( (hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE) || hitstats->type == INSECTOID )
433 {
434 resistance += 2;
435 hasamulet = true;
436 }
437 int oldHP = hitstats->HP;
438 damage /= (1 + (int)resistance);
439 damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
440 hit.entity->modHP(-damage);
441
442 // write the obituary
443 if ( parent )
444 {
445 parent->killedByMonsterObituary(hit.entity);
446 }
447
448 if ( !hasamulet )
449 {
450 hitstats->EFFECTS[EFF_POISONED] = true;
451 hitstats->EFFECTS_TIMERS[EFF_POISONED] = 300; // 6 seconds.
452 hitstats->EFFECTS_TIMERS[EFF_POISONED] /= (1 + (int)resistance);
453 }
454 /*hitstats->EFFECTS[EFF_SLOW] = true;
455 hitstats->EFFECTS_TIMERS[EFF_SLOW] = (element->duration * (((element->mana) / static_cast<double>(element->base_mana)) * element->overload_multiplier));
456 hitstats->EFFECTS_TIMERS[EFF_SLOW] /= (1 + (int)resistance);*/
457 if ( hit.entity->behavior == &actPlayer )
458 {
459 serverUpdateEffects(hit.entity->skill[2]);
460 }
461 // hit messages
462 if ( parent )
463 {
464 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
465 if ( parent->behavior == &actPlayer )
466 {
467 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2431], language[2430], MSG_COMBAT);
468 }
469 }
470
471 // update enemy bar for attacker
472 if ( !strcmp(hitstats->name, "") )
473 {
474 if ( hitstats->type < KOBOLD ) //Original monster count
475 {
476 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
477 }
478 else if ( hitstats->type >= KOBOLD ) //New monsters
479 {
480 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
481 }
482 }
483 else
484 {
485 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
486 }
487
488 if ( oldHP > 0 && hitstats->HP <= 0 && parent )
489 {
490 parent->awardXP(hit.entity, true, true);
491 }
492
493 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
494
495 int player = -1;
496 if ( hit.entity->behavior == &actPlayer )
497 {
498 player = hit.entity->skill[2];
499 }
500 if ( player >= 0 )
501 {
502 messagePlayerColor(player, color, language[2432]);
503 }
504
505 if ( hitstats->HP > 0 )
506 {
507 // damage armor
508 Item* armor = nullptr;
509 int armornum = -1;
510 if ( hitstats->defending && (rand() % (8 + resistance) == 0) ) // 1 in 8 to corrode shield
511 {
512 armornum = hitstats->pickRandomEquippedItem(&armor, true, false, true, true);
513 }
514 else if ( !hitstats->defending && (rand() % (4 + resistance) == 0) ) // 1 in 4 to corrode armor
515 {
516 armornum = hitstats->pickRandomEquippedItem(&armor, true, false, false, false);
517 }
518 //messagePlayer(0, "armornum: %d", armornum);
519 if ( armornum != -1 && armor != nullptr )
520 {
521 hit.entity->degradeArmor(*hitstats, *armor, armornum);
522 //messagePlayerColor(player, color, "Armor piece: %s", armor->getName());
523 }
524 }
525 }
526 else if ( hit.entity->behavior == &actDoor )
527 {
528 hit.entity->doorHandleDamageMagic(damage, my, parent);
529 }
530 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
531 }
532 else
533 {
534 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
535 }
536 }
537
spellEffectPoison(Entity & my,spellElement_t & element,Entity * parent,int resistance)538 void spellEffectPoison(Entity& my, spellElement_t& element, Entity* parent, int resistance)
539 {
540 playSoundEntity(&my, 173, 128);
541 if ( hit.entity )
542 {
543 int damage = element.damage;
544 damage += damage * ((my.actmagicSpellbookBonus / 100.f) + getBonusFromCasterOfSpellElement(parent, &element));
545 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
546
547 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
548 {
549 Entity* parent = uidToEntity(my.parent);
550 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
551 {
552 // test for friendly fire
553 if ( parent && parent->checkFriend(hit.entity) )
554 {
555 return;
556 }
557 }
558 playSoundEntity(hit.entity, 249, 64);
559
560 Stat* hitstats = hit.entity->getStats();
561 if ( !hitstats )
562 {
563 return;
564 }
565 bool hasamulet = false;
566 if ( (hitstats->amulet && hitstats->amulet->type == AMULET_POISONRESISTANCE) || hitstats->type == INSECTOID )
567 {
568 resistance += 2;
569 hasamulet = true;
570 }
571 damage /= (1 + (int)resistance);
572 damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
573 hit.entity->modHP(-damage);
574
575 // write the obituary
576 if ( parent )
577 {
578 parent->killedByMonsterObituary(hit.entity);
579 }
580
581 if ( !hasamulet )
582 {
583 if ( my.actmagicCastByMagicstaff == 1 )
584 {
585 hit.entity->setEffect(EFF_POISONED, true, 320, true); // 6 seconds.
586 }
587 else
588 {
589 hit.entity->setEffect(EFF_POISONED, true, std::max(200, 350 - hit.entity->getCON() * 5), true); // 4-7 seconds.
590 }
591 hitstats->poisonKiller = my.parent;
592 }
593
594 if ( hit.entity->behavior == &actPlayer )
595 {
596 serverUpdateEffects(hit.entity->skill[2]);
597 }
598 // hit messages
599 if ( parent )
600 {
601 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
602 if ( parent->behavior == &actPlayer )
603 {
604 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3427], language[3426], MSG_COMBAT);
605 }
606 }
607
608 // update enemy bar for attacker
609 if ( !strcmp(hitstats->name, "") )
610 {
611 if ( hitstats->type < KOBOLD ) //Original monster count
612 {
613 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
614 }
615 else if ( hitstats->type >= KOBOLD ) //New monsters
616 {
617 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
618 }
619 }
620 else
621 {
622 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
623 }
624
625 if ( hitstats->HP <= 0 && parent )
626 {
627 parent->awardXP(hit.entity, true, true);
628 }
629
630 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
631
632 int player = -1;
633 if ( hit.entity->behavior == &actPlayer )
634 {
635 player = hit.entity->skill[2];
636 }
637 if ( player >= 0 )
638 {
639 messagePlayerColor(player, color, language[3428]);
640 }
641 }
642 else if ( hit.entity->behavior == &actDoor )
643 {
644 hit.entity->doorHandleDamageMagic(damage, my, parent);
645 }
646 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
647 }
648 else
649 {
650 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
651 }
652 }
653
spellEffectFear(Entity * my,spellElement_t & element,Entity * forceParent,Entity * target,int resistance)654 bool spellEffectFear(Entity* my, spellElement_t& element, Entity* forceParent, Entity* target, int resistance)
655 {
656 if ( !target )
657 {
658 //spawnMagicEffectParticles(my.x, my.y, my.z, 863);
659 return false;
660 }
661
662 if ( target->behavior == &actMonster || target->behavior == &actPlayer )
663 {
664 Entity* parent = forceParent;
665 if ( my && !parent )
666 {
667 parent = uidToEntity(my->parent);
668 }
669 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
670 {
671 // test for friendly fire
672 if ( parent && parent->checkFriend(target) )
673 {
674 return false;
675 }
676 }
677
678 Stat* hitstats = target->getStats();
679 if ( !hitstats )
680 {
681 return false;
682 }
683
684 int duration = 400; // 8 seconds
685 duration = std::max(150, duration - TICKS_PER_SECOND * (hitstats->CON / 5)); // 3-8 seconds, depending on CON.
686 duration /= (1 + resistance);
687 if ( target->setEffect(EFF_FEAR, true, duration, true) )
688 {
689 playSoundEntity(target, 168, 128); // Healing.ogg
690 Uint32 color = 0;
691 if ( parent )
692 {
693 // update enemy bar for attacker
694 if ( !strcmp(hitstats->name, "") )
695 {
696 if ( hitstats->type < KOBOLD ) //Original monster count
697 {
698 updateEnemyBar(parent, target, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
699 }
700 else if ( hitstats->type >= KOBOLD ) //New monsters
701 {
702 updateEnemyBar(parent, target, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
703 }
704 }
705 else
706 {
707 updateEnemyBar(parent, target, hitstats->name, hitstats->HP, hitstats->MAXHP);
708 }
709 target->monsterAcquireAttackTarget(*parent, MONSTER_STATE_PATH);
710 target->monsterFearfulOfUid = parent->getUID();
711
712 if ( parent->behavior == &actPlayer && parent->getStats() && parent->getStats()->type == TROLL )
713 {
714 serverUpdatePlayerGameplayStats(parent->skill[2], STATISTICS_FORUM_TROLL, AchievementObserver::FORUM_TROLL_FEAR);
715 }
716 }
717 }
718 else
719 {
720 // no effect.
721 if ( parent )
722 {
723 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
724 if ( parent->behavior == &actPlayer )
725 {
726 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
727 }
728 }
729 return false;
730 }
731
732 // hit messages
733 if ( parent )
734 {
735 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
736 if ( parent->behavior == &actPlayer )
737 {
738 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3434], language[3435], MSG_COMBAT);
739 }
740 }
741
742 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
743
744 int player = -1;
745 if ( target->behavior == &actPlayer )
746 {
747 player = target->skill[2];
748 }
749 if ( player >= 0 )
750 {
751 messagePlayerColor(player, color, language[3436]);
752 }
753 }
754 spawnMagicEffectParticles(target->x, target->y, target->z, 863);
755 return true;
756 }
757
spellEffectSprayWeb(Entity & my,spellElement_t & element,Entity * parent,int resistance)758 void spellEffectSprayWeb(Entity& my, spellElement_t& element, Entity* parent, int resistance)
759 {
760 if ( hit.entity )
761 {
762 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
763 {
764 Entity* parent = uidToEntity(my.parent);
765 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
766 {
767 // test for friendly fire
768 if ( parent && parent->checkFriend(hit.entity) )
769 {
770 return;
771 }
772 }
773
774 Stat* hitstats = hit.entity->getStats();
775 if ( !hitstats )
776 {
777 return;
778 }
779
780 bool spawnParticles = true;
781 if ( hitstats->EFFECTS[EFF_WEBBED] )
782 {
783 spawnParticles = false;
784 }
785 int previousDuration = hitstats->EFFECTS_TIMERS[EFF_WEBBED];
786 int duration = 400;
787 duration /= (1 + resistance);
788 if ( hit.entity->setEffect(EFF_WEBBED, true, 400, true) ) // 8 seconds.
789 {
790 if ( duration - previousDuration > 10 )
791 {
792 playSoundEntity(hit.entity, 396 + rand() % 3, 64); // play sound only if not recently webbed. (triple shot makes many noise)
793 }
794 hit.entity->creatureWebbedSlowCount = std::min(3, hit.entity->creatureWebbedSlowCount + 1);
795 if ( hit.entity->behavior == &actPlayer )
796 {
797 serverUpdateEntitySkill(hit.entity, 52); // update player.
798 }
799 if ( spawnParticles )
800 {
801 createParticleAestheticOrbit(hit.entity, 863, 400, PARTICLE_EFFECT_SPELL_WEB_ORBIT);
802 serverSpawnMiscParticles(hit.entity, PARTICLE_EFFECT_SPELL_WEB_ORBIT, 863);
803 }
804 }
805 else
806 {
807 // no effect.
808 if ( parent )
809 {
810 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
811 if ( parent->behavior == &actPlayer )
812 {
813 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
814 }
815 }
816 return;
817 }
818
819 // hit messages
820 if ( parent )
821 {
822 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
823 if ( parent->behavior == &actPlayer )
824 {
825 if ( duration - previousDuration > 10 ) // message if not recently webbed
826 {
827 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3430], language[3429], MSG_COMBAT);
828 }
829 }
830 }
831
832 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
833
834 int player = -1;
835 if ( hit.entity->behavior == &actPlayer )
836 {
837 player = hit.entity->skill[2];
838 }
839 if ( player >= 0 )
840 {
841 if ( duration - previousDuration > 10 ) // message if not recently webbed
842 {
843 messagePlayerColor(player, color, language[3431]);
844 }
845 }
846 }
847 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, 863);
848 }
849 else
850 {
851 spawnMagicEffectParticles(my.x, my.y, my.z, 863);
852 }
853 }
854
spellEffectStealWeapon(Entity & my,spellElement_t & element,Entity * parent,int resistance)855 void spellEffectStealWeapon(Entity& my, spellElement_t& element, Entity* parent, int resistance)
856 {
857 if ( hit.entity )
858 {
859 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
860 {
861 Entity* parent = uidToEntity(my.parent);
862 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
863 {
864 // test for friendly fire
865 if ( parent && parent->checkFriend(hit.entity) )
866 {
867 return;
868 }
869 }
870
871 Stat* hitstats = hit.entity->getStats();
872 if ( !hitstats )
873 {
874 return;
875 }
876
877 if ( hitstats->type == LICH || hitstats->type == LICH_FIRE || hitstats->type == LICH_ICE || hitstats->type == DEVIL )
878 {
879 return;
880 }
881
882 if ( hit.entity->behavior == &actMonster
883 && (hit.entity->monsterAllySummonRank != 0
884 || (hitstats->type == INCUBUS && !strncmp(hitstats->name, "inner demon", strlen("inner demon"))))
885 )
886 {
887 return;
888 }
889
890 // update enemy bar for attacker
891 if ( !strcmp(hitstats->name, "") )
892 {
893 if ( hitstats->type < KOBOLD ) //Original monster count
894 {
895 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
896 }
897 else if ( hitstats->type >= KOBOLD ) //New monsters
898 {
899 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
900 }
901 }
902 else
903 {
904 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
905 }
906
907 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
908
909 int player = -1;
910 if ( hit.entity->behavior == &actPlayer )
911 {
912 player = hit.entity->skill[2];
913 }
914
915 if ( hitstats->weapon )
916 {
917 Entity* spellEntity = createParticleSapCenter(parent, hit.entity, SPELL_STEAL_WEAPON, my.sprite, my.sprite);
918 if ( spellEntity )
919 {
920 playSoundEntity(&my, 174, 128); // succeeded spell sound
921 spellEntity->skill[7] = 1; // found weapon
922
923 // store weapon data
924 spellEntity->skill[10] = hitstats->weapon->type;
925 if ( itemCategory(hitstats->weapon) == SPELLBOOK )
926 {
927 spellEntity->skill[11] = DECREPIT;
928 }
929 else
930 {
931 spellEntity->skill[11] = hitstats->weapon->status;
932 }
933 spellEntity->skill[12] = hitstats->weapon->beatitude;
934 spellEntity->skill[13] = hitstats->weapon->count;
935 spellEntity->skill[14] = hitstats->weapon->appearance;
936 spellEntity->skill[15] = hitstats->weapon->identified;
937 spellEntity->itemOriginalOwner = hit.entity->getUID();
938 // hit messages
939 if ( player >= 0 )
940 {
941 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
942 messagePlayerColor(player, color, language[2435], hitstats->weapon->getName());
943 }
944
945 if ( parent )
946 {
947 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
948 if ( parent->behavior == &actPlayer )
949 {
950 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2434], language[2433], MSG_STEAL_WEAPON);
951 }
952 }
953
954 if ( hit.entity->behavior == &actMonster && itemCategory(hitstats->weapon) != SPELLBOOK )
955 {
956 free(hitstats->weapon);
957 hitstats->weapon = nullptr;
958 }
959 else if ( hit.entity->behavior == &actPlayer )
960 {
961 // player.
962 Item* weapon = hitstats->weapon;
963 Item** slot = itemSlot(hitstats, weapon);
964 if ( slot )
965 {
966 *slot = nullptr;
967 }
968 if ( weapon->node )
969 {
970 list_RemoveNode(weapon->node);
971 }
972 else
973 {
974 free(weapon);
975 }
976 if ( player > 0 && multiplayer == SERVER )
977 {
978 strcpy((char*)net_packet->data, "STLA");
979 net_packet->data[4] = 5; // steal weapon index in STLA netcode.
980 net_packet->address.host = net_clients[player - 1].host;
981 net_packet->address.port = net_clients[player - 1].port;
982 net_packet->len = 5;
983 sendPacketSafe(net_sock, -1, net_packet, player - 1);
984 }
985 }
986 }
987 }
988 else
989 {
990 playSoundEntity(&my, 163, 128); // failed spell sound
991 // hit messages
992 if ( player >= 0 )
993 {
994 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
995 messagePlayerColor(player, color, language[2438]);
996 }
997
998 if ( parent )
999 {
1000 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
1001 if ( parent->behavior == &actPlayer )
1002 {
1003 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2437], language[2436], MSG_COMBAT);
1004 }
1005 }
1006 }
1007 }
1008 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
1009 }
1010 else
1011 {
1012 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
1013 }
1014 return;
1015 }
1016
spellEffectDrainSoul(Entity & my,spellElement_t & element,Entity * parent,int resistance)1017 void spellEffectDrainSoul(Entity& my, spellElement_t& element, Entity* parent, int resistance)
1018 {
1019 if ( hit.entity )
1020 {
1021 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
1022 {
1023 Entity* parent = uidToEntity(my.parent);
1024 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
1025 {
1026 // test for friendly fire
1027 if ( parent && parent->checkFriend(hit.entity) )
1028 {
1029 return;
1030 }
1031 }
1032
1033 Stat* hitstats = hit.entity->getStats();
1034 if ( !hitstats )
1035 {
1036 return;
1037 }
1038
1039 int damage = element.damage;
1040 damage += damage * ((my.actmagicSpellbookBonus / 100.f) + getBonusFromCasterOfSpellElement(parent, &element));
1041 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
1042 damage /= (1 + (int)resistance);
1043 damage *= hit.entity->getDamageTableMultiplier(*hitstats, DAMAGE_TABLE_MAGIC);
1044
1045 if ( parent )
1046 {
1047 Stat* casterStats = parent->getStats();
1048 if ( casterStats && casterStats->type == LICH_ICE )
1049 {
1050 damage *= 2;
1051 }
1052 }
1053
1054 int damageHP = hitstats->HP;
1055 int damageMP = hitstats->MP;
1056 hit.entity->modHP(-damage);
1057 if ( damage > hitstats->MP )
1058 {
1059 damage = hitstats->MP;
1060 }
1061 if ( parent && parent->behavior == &actPlayer )
1062 {
1063 damage /= 4; // reduced mana steal
1064 }
1065 hit.entity->drainMP(damage);
1066
1067 damageHP -= hitstats->HP;
1068 damageMP -= hitstats->MP;
1069
1070 // write the obituary
1071 if ( parent )
1072 {
1073 parent->killedByMonsterObituary(hit.entity);
1074 }
1075
1076 // update enemy bar for attacker
1077 if ( !strcmp(hitstats->name, "") )
1078 {
1079 if ( hitstats->type < KOBOLD ) //Original monster count
1080 {
1081 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1082 }
1083 else if ( hitstats->type >= KOBOLD ) //New monsters
1084 {
1085 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1086 }
1087 }
1088 else
1089 {
1090 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1091 }
1092
1093 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1094
1095 int player = -1;
1096 if ( hit.entity->behavior == &actPlayer )
1097 {
1098 player = hit.entity->skill[2];
1099 }
1100
1101 if ( hitstats->HP <= 0 && parent )
1102 {
1103 parent->awardXP(hit.entity, true, true);
1104 }
1105
1106 if ( damageHP > 0 && parent )
1107 {
1108 Entity* spellEntity = createParticleSapCenter(parent, hit.entity, SPELL_DRAIN_SOUL, my.sprite, my.sprite);
1109 if ( spellEntity )
1110 {
1111 playSoundEntity(&my, 167, 128); // succeeded spell sound
1112 playSoundEntity(&my, 28, 128); // damage
1113 spellEntity->skill[7] = damageHP; // damage taken to HP
1114 spellEntity->skill[8] = damageMP; // damage taken tp MP
1115
1116 // hit messages
1117 if ( player >= 0 )
1118 {
1119 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1120 messagePlayerColor(player, color, language[2441]);
1121 }
1122
1123 if ( parent )
1124 {
1125 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1126 if ( parent->behavior == &actPlayer )
1127 {
1128 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2440], language[2439], MSG_COMBAT);
1129 }
1130 }
1131 }
1132 }
1133 else
1134 {
1135 playSoundEntity(&my, 163, 128); // failed spell sound
1136 // hit messages
1137 if ( player >= 0 )
1138 {
1139 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1140 messagePlayerColor(player, color, language[2444]);
1141 }
1142
1143 if ( parent )
1144 {
1145 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
1146 if ( parent->behavior == &actPlayer )
1147 {
1148 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2443], language[2442], MSG_COMBAT);
1149 }
1150 }
1151 }
1152 }
1153 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
1154 }
1155 else
1156 {
1157 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
1158 }
1159 return;
1160 }
1161
spellEffectVampiricAura(Entity * caster,spell_t * spell,int extramagic_to_use)1162 spell_t* spellEffectVampiricAura(Entity* caster, spell_t* spell, int extramagic_to_use)
1163 {
1164 //Also refactor the duration determining code.
1165 node_t* node = spell->elements.first;
1166 if ( !node )
1167 {
1168 return nullptr;
1169 }
1170 spellElement_t* element = static_cast<spellElement_t*>(node->element);
1171 if ( !element )
1172 {
1173 return nullptr;
1174 }
1175 Stat* myStats = caster->getStats();
1176 if ( !myStats )
1177 {
1178 return nullptr;
1179 }
1180
1181 bool newbie = caster->isSpellcasterBeginner();
1182
1183 int duration = element->duration; // duration in ticks.
1184 duration += (((element->mana + extramagic_to_use) - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->duration;
1185 node_t* spellnode = list_AddNodeLast(&myStats->magic_effects);
1186 spellnode->element = copySpell(spell); //We need to save the spell since this is a channeled spell.
1187 spell_t* channeled_spell = (spell_t*)(spellnode->element);
1188 channeled_spell->magic_effects_node = spellnode;
1189 spellnode->size = sizeof(spell_t);
1190 ((spell_t*)spellnode->element)->caster = caster->getUID();
1191 spellnode->deconstructor = &spellDeconstructor;
1192 //if ( newbie )
1193 //{
1194 // //This guy's a newbie. There's a chance they've screwed up and negatively impacted the efficiency of the spell.
1195 // int chance = rand() % 10;
1196 // // spellcasting power is 0 to 100, based on spellcasting and intelligence.
1197 // int spellcastingPower = std::min(std::max(0, myStats->PROFICIENCIES[PRO_SPELLCASTING] + statGetINT(myStats, caster)), 100);
1198 // if ( chance >= spellcastingPower / 10 )
1199 // {
1200 // duration -= rand() % (1000 / (spellcastingPower + 1)); // reduce the duration by 0-20 seconds
1201 // }
1202 // if ( duration < 50 )
1203 // {
1204 // duration = 50; //Range checking.
1205 // }
1206 //}
1207 duration /= getCostOfSpell((spell_t*)spellnode->element);
1208 channeled_spell->channel_duration = duration; //Tell the spell how long it's supposed to last so that it knows what to reset its timer to.
1209 caster->setEffect(EFF_VAMPIRICAURA, true, duration, true);
1210 for ( int i = 0; i < MAXPLAYERS; ++i )
1211 {
1212 if ( players[i] && caster && (caster == players[i]->entity) )
1213 {
1214 serverUpdateEffects(i);
1215 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1216 messagePlayerColor(i, color, language[2477]);
1217 playSoundPlayer(i, 403, 32);
1218 }
1219 }
1220
1221 playSoundEntity(caster, 167, 128);
1222 createParticleDropRising(caster, 600, 0.7);
1223 serverSpawnMiscParticles(caster, PARTICLE_EFFECT_VAMPIRIC_AURA, 600);
1224 return channeled_spell;
1225 }
1226
spellEffectCharmMonster(Entity & my,spellElement_t & element,Entity * parent,int resistance,bool magicstaff)1227 void spellEffectCharmMonster(Entity& my, spellElement_t& element, Entity* parent, int resistance, bool magicstaff)
1228 {
1229 if ( hit.entity )
1230 {
1231 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
1232 {
1233 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
1234 {
1235 // test for friendly fire
1236 if ( parent && parent->checkFriend(hit.entity) )
1237 {
1238 return;
1239 }
1240 }
1241
1242 Stat* hitstats = hit.entity->getStats();
1243 if ( !hitstats )
1244 {
1245 return;
1246 }
1247
1248 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1249
1250 int player = -1;
1251 if ( hit.entity->behavior == &actPlayer )
1252 {
1253 player = hit.entity->skill[2];
1254 }
1255
1256 int difficulty = 0;
1257 switch ( hitstats->type )
1258 {
1259 case HUMAN:
1260 case RAT:
1261 case SLIME:
1262 case SPIDER:
1263 case SKELETON:
1264 case SCORPION:
1265 case SHOPKEEPER:
1266 difficulty = 0;
1267 break;
1268 case GOBLIN:
1269 case TROLL:
1270 case GHOUL:
1271 case GNOME:
1272 case SCARAB:
1273 case AUTOMATON:
1274 case SUCCUBUS:
1275 difficulty = 1;
1276 break;
1277 case CREATURE_IMP:
1278 case DEMON:
1279 case KOBOLD:
1280 case INCUBUS:
1281 case INSECTOID:
1282 case GOATMAN:
1283 difficulty = 2;
1284 break;
1285 case CRYSTALGOLEM:
1286 case VAMPIRE:
1287 difficulty = 5;
1288 break;
1289 case COCKATRICE:
1290 case SHADOW:
1291 case LICH:
1292 case DEVIL:
1293 case LICH_ICE:
1294 case LICH_FIRE:
1295 case MINOTAUR:
1296 difficulty = 666;
1297 break;
1298 }
1299
1300 int chance = 80;
1301 bool allowStealFollowers = false;
1302 Stat* casterStats = nullptr;
1303 int currentCharmedFollowerCount = 0;
1304
1305 /************** CHANCE CALCULATION ***********/
1306 if ( hitstats->EFFECTS[EFF_CONFUSED] || hitstats->EFFECTS[EFF_DRUNK] || player >= 0 )
1307 {
1308 difficulty -= 1; // players and confused/drunk monsters have lower resistance.
1309 }
1310 if ( strcmp(hitstats->name, "") && !monsterNameIsGeneric(*hitstats) )
1311 {
1312 difficulty += 1; // minibosses +1 difficulty.
1313 }
1314 chance -= difficulty * 30;
1315 if ( parent )
1316 {
1317 casterStats = parent->getStats();
1318 if ( casterStats )
1319 {
1320 if ( magicstaff )
1321 {
1322 chance += ((parent->getCHR() + casterStats->PROFICIENCIES[PRO_LEADERSHIP]) / 20) * 10;
1323 }
1324 else
1325 {
1326 chance += ((parent->getCHR() + casterStats->PROFICIENCIES[PRO_LEADERSHIP]) / 20) * 5;
1327 chance += (parent->getINT() * 2);
1328 }
1329
1330 if ( parent->behavior == &actMonster )
1331 {
1332 allowStealFollowers = true;
1333 if ( casterStats->type == INCUBUS || casterStats->type == SUCCUBUS )
1334 {
1335 if ( hitstats->type == DEMON || hitstats->type == INCUBUS
1336 || hitstats->type == SUCCUBUS || hitstats->type == CREATURE_IMP
1337 || hitstats->type == GOATMAN )
1338 {
1339 chance = 100; // bonus for demons.
1340 }
1341 else if ( difficulty <= 2 )
1342 {
1343 chance = 80; // special base chance for monsters.
1344 }
1345 }
1346 else if ( difficulty <= 2 )
1347 {
1348 chance = 60; // special base chance for monsters.
1349 }
1350 }
1351 else if ( parent->behavior == &actPlayer )
1352 {
1353 // search followers for charmed.
1354 for ( node_t* node = casterStats->FOLLOWERS.first; node != NULL; node = node->next )
1355 {
1356 Uint32* c = (Uint32*)node->element;
1357 Entity* follower = nullptr;
1358 if ( c )
1359 {
1360 follower = uidToEntity(*c);
1361 }
1362 if ( follower )
1363 {
1364 if ( Stat* followerStats = follower->getStats() )
1365 {
1366 if ( followerStats->monsterIsCharmed == 1 )
1367 {
1368 ++currentCharmedFollowerCount;
1369 }
1370 }
1371 }
1372 }
1373 }
1374 }
1375 }
1376 if ( hit.entity->monsterState == MONSTER_STATE_WAIT )
1377 {
1378 chance += 10;
1379 }
1380 chance /= (1 + resistance);
1381 /************** END CHANCE CALCULATION ***********/
1382
1383 // special cases:
1384 if ( (hitstats->type == VAMPIRE && !strncmp(hitstats->name, "Bram Kindly", 11))
1385 || (hitstats->type == COCKATRICE && !strncmp(map.name, "Cockatrice Lair", 15))
1386 )
1387 {
1388 chance = 0;
1389 }
1390 else if ( hit.entity->behavior == &actMonster
1391 && (hit.entity->monsterAllySummonRank != 0
1392 || (hitstats->type == INCUBUS && !strncmp(hitstats->name, "inner demon", strlen("inner demon"))))
1393 )
1394 {
1395 chance = 0; // not allowed to control summons
1396 }
1397
1398 if ( parent && hit.entity == parent )
1399 {
1400 // caster hit themselves somehow... get pacified.
1401 int duration = element.duration;
1402 duration /= (1 + resistance);
1403 if ( hit.entity->setEffect(EFF_PACIFY, true, duration, true) )
1404 {
1405 playSoundEntity(hit.entity, 168, 128); // Healing.ogg
1406 if ( player >= 0 )
1407 {
1408 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1409 messagePlayerColor(player, color, language[3144]);
1410 }
1411 if ( parent )
1412 {
1413 if ( parent->behavior == &actPlayer )
1414 {
1415 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3139], language[3140], MSG_COMBAT);
1416 }
1417 // update enemy bar for attacker
1418 if ( !strcmp(hitstats->name, "") )
1419 {
1420 if ( hitstats->type < KOBOLD ) //Original monster count
1421 {
1422 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1423 }
1424 else if ( hitstats->type >= KOBOLD ) //New monsters
1425 {
1426 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1427 }
1428 }
1429 else
1430 {
1431 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1432 }
1433 }
1434 }
1435 }
1436 else if ( chance <= 0 )
1437 {
1438 // no effect.
1439 playSoundEntity(hit.entity, 163, 64); // FailedSpell1V1.ogg
1440 if ( parent && parent->behavior == &actPlayer )
1441 {
1442 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
1443 steamAchievementClient(parent->skill[2], "BARONY_ACH_OFF_LIMITS");
1444 }
1445 if ( player >= 0 )
1446 {
1447 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1448 messagePlayerColor(player, color, language[3141]);
1449 }
1450 }
1451 else if ( parent && rand() % 100 < chance
1452 && (hitstats->leader_uid == 0 || (allowStealFollowers && hitstats->leader_uid != parent->getUID()) )
1453 && player < 0
1454 && hitstats->type != SHOPKEEPER
1455 && currentCharmedFollowerCount == 0
1456 )
1457 {
1458 // fully charmed. (players not affected here.)
1459 // does not affect shopkeepers
1460 // succubus/incubus can steal followers from others, checking to see if they don't already follow them.
1461 Entity* whoToFollow = parent;
1462 if ( parent->behavior == &actMonster && parent->monsterAllyGetPlayerLeader() )
1463 {
1464 whoToFollow = parent->monsterAllyGetPlayerLeader();
1465 }
1466
1467 if ( forceFollower(*whoToFollow, *hit.entity) )
1468 {
1469 serverSpawnMiscParticles(hit.entity, PARTICLE_EFFECT_CHARM_MONSTER, 0);
1470 createParticleCharmMonster(hit.entity);
1471 playSoundEntity(hit.entity, 174, 64); // WeirdSpell.ogg
1472 if ( whoToFollow->behavior == &actPlayer )
1473 {
1474 whoToFollow->increaseSkill(PRO_LEADERSHIP);
1475 messagePlayerMonsterEvent(whoToFollow->skill[2], color, *hitstats, language[3137], language[3138], MSG_COMBAT);
1476 hit.entity->monsterAllyIndex = whoToFollow->skill[2];
1477 if ( multiplayer == SERVER )
1478 {
1479 serverUpdateEntitySkill(hit.entity, 42); // update monsterAllyIndex for clients.
1480 }
1481 if ( hit.entity->monsterTarget == whoToFollow->getUID() )
1482 {
1483 hit.entity->monsterReleaseAttackTarget();
1484 }
1485 }
1486
1487 // change the color of the hit entity.
1488 hit.entity->flags[USERFLAG2] = true;
1489 serverUpdateEntityFlag(hit.entity, USERFLAG2);
1490 hitstats->monsterIsCharmed = 1;
1491 if ( monsterChangesColorWhenAlly(hitstats) )
1492 {
1493 int bodypart = 0;
1494 for ( node_t* node = (hit.entity)->children.first; node != nullptr; node = node->next )
1495 {
1496 if ( bodypart >= LIMB_HUMANOID_TORSO )
1497 {
1498 Entity* tmp = (Entity*)node->element;
1499 if ( tmp )
1500 {
1501 tmp->flags[USERFLAG2] = true;
1502 //serverUpdateEntityFlag(tmp, USERFLAG2);
1503 }
1504 }
1505 ++bodypart;
1506 }
1507 }
1508 if ( whoToFollow->behavior == &actMonster )
1509 {
1510 if ( whoToFollow->monsterTarget == hit.entity->getUID() )
1511 {
1512 whoToFollow->monsterReleaseAttackTarget(); // monsters stop attacking their new friend.
1513 }
1514
1515 // handle players losing their allies.
1516 if ( hit.entity->monsterAllyIndex != -1 )
1517 {
1518 hit.entity->monsterAllyIndex = -1;
1519 if ( multiplayer == SERVER )
1520 {
1521 serverUpdateEntitySkill(hit.entity, 42); // update monsterAllyIndex for clients.
1522 }
1523 }
1524 }
1525 }
1526 }
1527 else
1528 {
1529 // had a chance, or currently in service of another monster, or a player, or spell no parent, failed to completely charm.
1530 // loses will to attack.
1531 int duration = element.duration;
1532 duration /= (1 + resistance);
1533 if ( hitstats->type == SHOPKEEPER )
1534 {
1535 duration = 100;
1536 }
1537 if ( hit.entity->setEffect(EFF_PACIFY, true, duration, true) )
1538 {
1539 playSoundEntity(hit.entity, 168, 128); // Healing.ogg
1540 if ( player >= 0 )
1541 {
1542 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
1543 messagePlayerColor(player, color, language[3144]);
1544 }
1545 if ( parent )
1546 {
1547 if ( parent->behavior == &actPlayer )
1548 {
1549 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3139], language[3140], MSG_COMBAT);
1550 if ( currentCharmedFollowerCount > 0 )
1551 {
1552 messagePlayer(parent->skill[2], language[3327]);
1553 }
1554 }
1555 // update enemy bar for attacker
1556 if ( !strcmp(hitstats->name, "") )
1557 {
1558 if ( hitstats->type < KOBOLD ) //Original monster count
1559 {
1560 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
1561 }
1562 else if ( hitstats->type >= KOBOLD ) //New monsters
1563 {
1564 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
1565 }
1566 }
1567 else
1568 {
1569 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
1570 }
1571 }
1572 if ( hitstats->type == SHOPKEEPER && player >= 0 )
1573 {
1574 // reverses shop keeper grudges.
1575 swornenemies[SHOPKEEPER][HUMAN] = false;
1576 swornenemies[SHOPKEEPER][AUTOMATON] = false;
1577 monsterally[SHOPKEEPER][HUMAN] = true;
1578 monsterally[SHOPKEEPER][AUTOMATON] = true;
1579 hit.entity->monsterReleaseAttackTarget();
1580 }
1581 }
1582 else
1583 {
1584 // resists the charm.
1585 playSoundEntity(hit.entity, 163, 64); // FailedSpell1V1.ogg
1586 if ( parent && parent->behavior == &actPlayer )
1587 {
1588 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3142], language[3143], MSG_COMBAT);
1589 }
1590 if ( player >= 0 )
1591 {
1592 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
1593 messagePlayerColor(player, color, language[3141]);
1594 }
1595 }
1596 }
1597 }
1598 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
1599 }
1600 else
1601 {
1602 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
1603 }
1604 return;
1605 }
1606
spellEffectPolymorph(Entity * target,Stat * targetStats,Entity * parent,bool fromMagicSpell,int customDuration)1607 Entity* spellEffectPolymorph(Entity* target, Stat* targetStats, Entity* parent, bool fromMagicSpell, int customDuration)
1608 {
1609 int effectDuration = 0;
1610 effectDuration = TICKS_PER_SECOND * 60 * (4 + rand() % 3); // 4-6 minutes
1611 if ( customDuration > 0 )
1612 {
1613 effectDuration = customDuration;
1614 }
1615 if ( !target || !targetStats )
1616 {
1617 if ( parent && parent->behavior == &actPlayer )
1618 {
1619 messagePlayer(parent->skill[2], language[3191]); // had no effect
1620 }
1621 return nullptr;
1622 }
1623
1624 if ( targetStats->type == LICH || targetStats->type == SHOPKEEPER || targetStats->type == DEVIL
1625 || targetStats->type == MINOTAUR || targetStats->type == LICH_FIRE || targetStats->type == LICH_ICE
1626 || (target->behavior == &actMonster && target->monsterAllySummonRank != 0)
1627 || (targetStats->type == INCUBUS && !strncmp(targetStats->name, "inner demon", strlen("inner demon")))
1628 || targetStats->type == SENTRYBOT || targetStats->type == SPELLBOT || targetStats->type == GYROBOT
1629 || targetStats->type == DUMMYBOT
1630 )
1631 {
1632 if ( parent && parent->behavior == &actPlayer )
1633 {
1634 messagePlayer(parent->skill[2], language[3191]); // had no effect
1635 }
1636 return nullptr;
1637 }
1638
1639 if ( target->behavior == &actMonster )
1640 {
1641 Monster monsterSummonType = static_cast<Monster>(rand() % NUMMONSTERS);
1642 // pick a completely random monster (barring some exceptions).
1643 // disable shadow spawning if the monster has a leader since it'll aggro the player and bad things
1644 while ( monsterSummonType == LICH || monsterSummonType == SHOPKEEPER || monsterSummonType == DEVIL
1645 || monsterSummonType == MIMIC || monsterSummonType == BUGBEAR || monsterSummonType == OCTOPUS
1646 || monsterSummonType == MINOTAUR || monsterSummonType == LICH_FIRE || monsterSummonType == LICH_ICE
1647 || monsterSummonType == NOTHING || monsterSummonType == targetStats->type || monsterSummonType == HUMAN
1648 || (targetStats->leader_uid != 0 && monsterSummonType == SHADOW) || monsterSummonType == SENTRYBOT
1649 || monsterSummonType == SPELLBOT || monsterSummonType == GYROBOT || monsterSummonType == DUMMYBOT )
1650 {
1651 monsterSummonType = static_cast<Monster>(rand() % NUMMONSTERS);
1652 }
1653
1654 if ( targetStats->type == SHADOW )
1655 {
1656 monsterSummonType = CREATURE_IMP; // shadows turn to imps
1657 }
1658
1659 bool summonCanEquipItems = false;
1660 bool hitMonsterCanTransferEquipment = false;
1661
1662 switch ( monsterSummonType )
1663 {
1664 case RAT:
1665 case SLIME:
1666 case TROLL:
1667 case SPIDER:
1668 case GHOUL:
1669 case SCORPION:
1670 case CREATURE_IMP:
1671 case DEMON:
1672 case SCARAB:
1673 case CRYSTALGOLEM:
1674 case SHADOW:
1675 case COCKATRICE:
1676 summonCanEquipItems = false;
1677 break;
1678 default:
1679 summonCanEquipItems = true;
1680 break;
1681 }
1682
1683 switch ( targetStats->type )
1684 {
1685 case RAT:
1686 case SLIME:
1687 case TROLL:
1688 case SPIDER:
1689 case GHOUL:
1690 case SCORPION:
1691 case CREATURE_IMP:
1692 case DEMON:
1693 case SCARAB:
1694 case CRYSTALGOLEM:
1695 case SHADOW:
1696 case COCKATRICE:
1697 hitMonsterCanTransferEquipment = false;
1698 break;
1699 default:
1700 hitMonsterCanTransferEquipment = true;
1701 break;
1702 }
1703
1704 bool fellToDeath = false;
1705 bool tryReposition = false;
1706 bool fellInLava = false;
1707 bool fellInWater = false;
1708
1709 if ( targetStats->EFFECTS[EFF_LEVITATING]
1710 && (monsterSummonType != CREATURE_IMP && monsterSummonType != COCKATRICE && monsterSummonType != SHADOW) )
1711 {
1712 // check if there's a floor...
1713 int x, y, u, v;
1714 x = std::min(std::max<unsigned int>(1, target->x / 16), map.width - 2);
1715 y = std::min(std::max<unsigned int>(1, target->y / 16), map.height - 2);
1716 for ( u = x - 1; u <= x + 1; u++ )
1717 {
1718 for ( v = y - 1; v <= y + 1; v++ )
1719 {
1720 if ( entityInsideTile(target, u, v, 0) ) // no floor
1721 {
1722 if ( !map.tiles[0 + u * MAPLAYERS + v * MAPLAYERS * map.height] )
1723 {
1724 // no floor.
1725 fellToDeath = true;
1726 tryReposition = true;
1727 }
1728 else if ( lavatiles[map.tiles[0 + u * MAPLAYERS + v * MAPLAYERS * map.height]] )
1729 {
1730 fellInLava = true;
1731 tryReposition = true;
1732 }
1733 else if ( swimmingtiles[map.tiles[0 + u * MAPLAYERS + v * MAPLAYERS * map.height]] )
1734 {
1735 fellInWater = true;
1736 tryReposition = true;
1737 }
1738 else
1739 {
1740 tryReposition = true; // something else??
1741 }
1742 break;
1743 }
1744 }
1745 if ( tryReposition )
1746 {
1747 break;
1748 }
1749 }
1750 }
1751
1752 Entity* summonedEntity = nullptr;
1753 if ( tryReposition )
1754 {
1755 summonedEntity = summonMonster(monsterSummonType, target->x, target->y);
1756 if ( !summonedEntity && (fellToDeath || fellInLava) )
1757 {
1758 summonedEntity = summonMonster(monsterSummonType, target->x, target->y, true); // force try, kill monster later.
1759 }
1760 }
1761 else
1762 {
1763 summonedEntity = summonMonster(monsterSummonType, target->x, target->y, true);
1764 }
1765
1766 if ( !summonedEntity )
1767 {
1768 if ( parent && parent->behavior == &actPlayer )
1769 {
1770 if ( fellInWater )
1771 {
1772 messagePlayer(parent->skill[2], language[3192]); // water make no work :<
1773 }
1774 else
1775 {
1776 messagePlayer(parent->skill[2], language[3191]); // failed for some other reason
1777 }
1778 }
1779 return nullptr;
1780 }
1781
1782 Stat* summonedStats = summonedEntity->getStats();
1783 if ( !summonedStats )
1784 {
1785 if ( parent && parent->behavior == &actPlayer )
1786 {
1787 messagePlayer(parent->skill[2], language[3191]);
1788 }
1789 return nullptr;
1790 }
1791
1792 // remove equipment from new monster
1793 if ( summonCanEquipItems )
1794 {
1795 // monster does not have generated equipment yet, disable from generating.
1796 summonedStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] = 0;
1797 summonedStats->EDITOR_ITEMS[ITEM_SLOT_SHIELD] = 0;
1798 summonedStats->EDITOR_ITEMS[ITEM_SLOT_ARMOR] = 0;
1799 summonedStats->EDITOR_ITEMS[ITEM_SLOT_HELM] = 0;
1800 summonedStats->EDITOR_ITEMS[ITEM_SLOT_GLOVES] = 0;
1801 summonedStats->EDITOR_ITEMS[ITEM_SLOT_BOOTS] = 0;
1802 summonedStats->EDITOR_ITEMS[ITEM_SLOT_RING] = 0;
1803 summonedStats->EDITOR_ITEMS[ITEM_SLOT_CLOAK] = 0;
1804 summonedStats->EDITOR_ITEMS[ITEM_SLOT_AMULET] = 0;
1805 }
1806
1807 for ( int x = ITEM_SLOT_INV_1; x <= ITEM_SLOT_INV_6; x = x + ITEM_SLOT_NUMPROPERTIES )
1808 {
1809 if ( summonedStats->EDITOR_ITEMS[x] == 1 && summonedStats->EDITOR_ITEMS[x + ITEM_SLOT_CATEGORY] == 0 )
1810 {
1811 summonedStats->EDITOR_ITEMS[x] = 0; //clear default item in inventory
1812 }
1813 }
1814
1815 // copy stats from target to new creature.
1816 summonedStats->HP = targetStats->HP;
1817 summonedStats->OLDHP = targetStats->OLDHP;
1818 summonedStats->MP = targetStats->MP;
1819 summonedStats->MAXHP = targetStats->MAXHP;
1820 summonedStats->MAXMP = targetStats->MAXMP;
1821 summonedStats->STR = targetStats->STR;
1822 summonedStats->DEX = targetStats->DEX;
1823 summonedStats->CON = targetStats->CON;
1824 summonedStats->INT = targetStats->INT;
1825 summonedStats->PER = targetStats->PER;
1826 //summonedStats->CHR = targetStats->CHR;
1827 summonedStats->LVL = targetStats->LVL;
1828 summonedStats->GOLD = targetStats->GOLD;
1829
1830 // don't apply random stats again
1831 summonedStats->RANDOM_HP = 0;
1832 summonedStats->RANDOM_MP = 0;
1833 summonedStats->RANDOM_MAXHP = 0;
1834 summonedStats->RANDOM_MAXMP = 0;
1835 summonedStats->RANDOM_STR = 0;
1836 summonedStats->RANDOM_DEX = 0;
1837 summonedStats->RANDOM_CON = 0;
1838 summonedStats->RANDOM_INT = 0;
1839 summonedStats->RANDOM_PER = 0;
1840 summonedStats->RANDOM_CHR = 0;
1841 summonedStats->RANDOM_LVL = 0;
1842 summonedStats->RANDOM_GOLD = 0;
1843 summonedStats->MISC_FLAGS[STAT_FLAG_MONSTER_DISABLE_HC_SCALING] = 1;
1844 summonedStats->leader_uid = targetStats->leader_uid;
1845 if ( summonedStats->leader_uid != 0 && summonedStats->type != SHADOW )
1846 {
1847 Entity* leader = uidToEntity(summonedStats->leader_uid);
1848 if ( leader )
1849 {
1850 // lose old ally
1851 if ( target->monsterAllyIndex != -1 )
1852 {
1853 int playerFollower = MAXPLAYERS;
1854 for ( int c = 0; c < MAXPLAYERS; c++ )
1855 {
1856 if ( players[c] && players[c]->entity )
1857 {
1858 if ( targetStats->leader_uid == players[c]->entity->getUID() )
1859 {
1860 playerFollower = c;
1861 if ( stats[c] )
1862 {
1863 for ( node_t* allyNode = stats[c]->FOLLOWERS.first; allyNode != nullptr; allyNode = allyNode->next )
1864 {
1865 if ( *((Uint32*)allyNode->element) == target->getUID() )
1866 {
1867 list_RemoveNode(allyNode);
1868 if ( c != clientnum )
1869 {
1870 serverRemoveClientFollower(c, target->getUID());
1871 }
1872 else
1873 {
1874 if ( FollowerMenu.recentEntity && (FollowerMenu.recentEntity->getUID() == 0
1875 || FollowerMenu.recentEntity->getUID() == target->getUID()) )
1876 {
1877 FollowerMenu.recentEntity = nullptr;
1878 }
1879 }
1880 break;
1881 }
1882 }
1883 }
1884 break;
1885 }
1886 }
1887 }
1888 }
1889
1890 if ( forceFollower(*leader, *summonedEntity) )
1891 {
1892 if ( leader->behavior == &actPlayer )
1893 {
1894 summonedEntity->monsterAllyIndex = leader->skill[2];
1895 if ( multiplayer == SERVER )
1896 {
1897 serverUpdateEntitySkill(summonedEntity, 42); // update monsterAllyIndex for clients.
1898 }
1899
1900 }
1901 // change the color of the hit entity.
1902 summonedEntity->flags[USERFLAG2] = true;
1903 serverUpdateEntityFlag(summonedEntity, USERFLAG2);
1904 if ( monsterChangesColorWhenAlly(summonedStats) )
1905 {
1906 int bodypart = 0;
1907 for ( node_t* node = summonedEntity->children.first; node != nullptr; node = node->next )
1908 {
1909 if ( bodypart >= LIMB_HUMANOID_TORSO )
1910 {
1911 Entity* tmp = (Entity*)node->element;
1912 if ( tmp )
1913 {
1914 tmp->flags[USERFLAG2] = true;
1915 //serverUpdateEntityFlag(tmp, USERFLAG2);
1916 }
1917 }
1918 ++bodypart;
1919 }
1920 }
1921 }
1922 }
1923 }
1924 if ( targetStats->type == HUMAN )
1925 {
1926 strcpy(summonedStats->name, targetStats->name);
1927 }
1928
1929 if ( hitMonsterCanTransferEquipment && summonCanEquipItems )
1930 {
1931 // weapon
1932 Item** slot = itemSlot(targetStats, targetStats->weapon);
1933 if ( slot )
1934 {
1935 summonedStats->weapon = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1936 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
1937 }
1938
1939 // shield
1940 slot = itemSlot(targetStats, targetStats->shield);
1941 if ( slot )
1942 {
1943 summonedStats->shield = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1944 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
1945 }
1946
1947 // breastplate
1948 slot = itemSlot(targetStats, targetStats->breastplate);
1949 if ( slot )
1950 {
1951 if ( monsterSummonType == KOBOLD || monsterSummonType == GNOME )
1952 {
1953 // kobold/gnomes can't equip breastplate, drop it!
1954 Entity* dropped = dropItemMonster(targetStats->breastplate, target, targetStats);
1955 if ( dropped )
1956 {
1957 dropped->flags[USERFLAG1] = true;
1958 }
1959 }
1960 else
1961 {
1962 summonedStats->breastplate = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1963 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
1964 }
1965 }
1966
1967 // shoes
1968 slot = itemSlot(targetStats, targetStats->shoes);
1969 if ( slot )
1970 {
1971 summonedStats->shoes = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1972 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
1973 }
1974
1975 // helm
1976 slot = itemSlot(targetStats, targetStats->helmet);
1977 if ( slot )
1978 {
1979 if ( monsterSummonType == KOBOLD || monsterSummonType == GNOME )
1980 {
1981 // kobold/gnomes can't equip non-hoods, drop the rest
1982 if ( (*slot)->type == HAT_HOOD )
1983 {
1984 summonedStats->helmet = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1985 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
1986 }
1987 else
1988 {
1989 Entity* dropped = dropItemMonster(targetStats->helmet, target, targetStats);
1990 if ( dropped )
1991 {
1992 dropped->flags[USERFLAG1] = true;
1993 }
1994 }
1995 }
1996 else
1997 {
1998 summonedStats->helmet = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
1999 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
2000 }
2001 }
2002
2003 // amulet
2004 slot = itemSlot(targetStats, targetStats->amulet);
2005 if ( slot )
2006 {
2007 summonedStats->amulet = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
2008 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
2009 }
2010
2011 // ring
2012 slot = itemSlot(targetStats, targetStats->ring);
2013 if ( slot )
2014 {
2015 summonedStats->ring = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
2016 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
2017 }
2018
2019 // cloak
2020 slot = itemSlot(targetStats, targetStats->cloak);
2021 if ( slot )
2022 {
2023 summonedStats->cloak = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
2024 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
2025 }
2026
2027 // gloves
2028 slot = itemSlot(targetStats, targetStats->gloves);
2029 if ( slot )
2030 {
2031 if ( monsterSummonType == KOBOLD || monsterSummonType == GNOME )
2032 {
2033 // kobold/gnomes can't equip gloves, drop it!
2034 Entity* dropped = dropItemMonster(targetStats->gloves, target, targetStats);
2035 if ( dropped )
2036 {
2037 dropped->flags[USERFLAG1] = true;
2038 }
2039 }
2040 else
2041 {
2042 summonedStats->gloves = newItem((*slot)->type, (*slot)->status, (*slot)->beatitude,
2043 (*slot)->count, (*slot)->appearance, (*slot)->identified, nullptr);
2044 }
2045 }
2046 }
2047 else if ( hitMonsterCanTransferEquipment && !summonCanEquipItems )
2048 {
2049 Entity* dropped = dropItemMonster(targetStats->weapon, target, targetStats);
2050 if ( dropped )
2051 {
2052 dropped->flags[USERFLAG1] = true;
2053 }
2054 dropped = dropItemMonster(targetStats->shield, target, targetStats);
2055 if ( dropped )
2056 {
2057 dropped->flags[USERFLAG1] = true;
2058 }
2059 dropped = dropItemMonster(targetStats->breastplate, target, targetStats);
2060 if ( dropped )
2061 {
2062 dropped->flags[USERFLAG1] = true;
2063 }
2064 dropped = dropItemMonster(targetStats->shoes, target, targetStats);
2065 if ( dropped )
2066 {
2067 dropped->flags[USERFLAG1] = true;
2068 }
2069 dropped = dropItemMonster(targetStats->gloves, target, targetStats);
2070 if ( dropped )
2071 {
2072 dropped->flags[USERFLAG1] = true;
2073 }
2074 dropped = dropItemMonster(targetStats->ring, target, targetStats);
2075 if ( dropped )
2076 {
2077 dropped->flags[USERFLAG1] = true;
2078 }
2079 dropped = dropItemMonster(targetStats->amulet, target, targetStats);
2080 if ( dropped )
2081 {
2082 dropped->flags[USERFLAG1] = true;
2083 }
2084 dropped = dropItemMonster(targetStats->cloak, target, targetStats);
2085 if ( dropped )
2086 {
2087 dropped->flags[USERFLAG1] = true;
2088 }
2089 dropped = dropItemMonster(targetStats->helmet, target, targetStats);
2090 if ( dropped )
2091 {
2092 dropped->flags[USERFLAG1] = true;
2093 }
2094 }
2095
2096 node_t* nextnode = nullptr;
2097 for ( node_t* node = targetStats->inventory.first; node; node = nextnode )
2098 {
2099 nextnode = node->next;
2100 Item* item = (Item*)node->element;
2101 if ( item && item->appearance != MONSTER_ITEM_UNDROPPABLE_APPEARANCE && itemSlot(targetStats, item) == nullptr )
2102 {
2103 Item* copiedItem = newItem(item->type, item->status, item->beatitude, item->count, item->appearance, item->identified, &summonedStats->inventory);
2104 if ( item->node )
2105 {
2106 list_RemoveNode(item->node);
2107 }
2108 else
2109 {
2110 free(item);
2111 }
2112 }
2113 }
2114
2115 if ( parent && parent->behavior == &actPlayer )
2116 {
2117 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2118 bool namedMonsterAsGeneric = monsterNameIsGeneric(*targetStats);
2119 // the %s polymorph into a %s!
2120 if ( !strcmp((*targetStats).name, "") || namedMonsterAsGeneric )
2121 {
2122 if ( (*targetStats).type < KOBOLD ) //Original monster count
2123 {
2124 if ( summonedStats->type < KOBOLD )
2125 {
2126 messagePlayerColor(parent->skill[2], color, language[3187], language[90 + (*targetStats).type], language[90 + summonedStats->type]);
2127 }
2128 else
2129 {
2130 messagePlayerColor(parent->skill[2], color, language[3187], language[90 + (*targetStats).type], language[2000 + summonedStats->type - KOBOLD]);
2131 }
2132 }
2133 else if ( (*targetStats).type >= KOBOLD ) //New monsters
2134 {
2135 if ( summonedStats->type < KOBOLD )
2136 {
2137 messagePlayerColor(parent->skill[2], color, language[3187], language[2000 + (*targetStats).type - KOBOLD], language[90 + summonedStats->type]);
2138 }
2139 else
2140 {
2141 messagePlayerColor(parent->skill[2], color, language[3187], language[2000 + (*targetStats).type - KOBOLD], language[2000 + summonedStats->type - KOBOLD]);
2142 }
2143 }
2144 }
2145 else
2146 {
2147 if ( summonedStats->type < KOBOLD )
2148 {
2149 messagePlayerColor(parent->skill[2], color, language[3188], (*targetStats).name, language[90 + summonedStats->type]);
2150 }
2151 else
2152 {
2153 messagePlayerColor(parent->skill[2], color, language[3188], (*targetStats).name, language[2000 + summonedStats->type - KOBOLD]);
2154 }
2155 }
2156 }
2157
2158 playSoundEntity(target, 400, 92);
2159 spawnExplosion(target->x, target->y, target->z);
2160 createParticleDropRising(target, 593, 1.f);
2161 serverSpawnMiscParticles(target, PARTICLE_EFFECT_RISING_DROP, 593);
2162
2163 if ( fellToDeath )
2164 {
2165 summonedEntity->setObituary(language[3010]); // fell to their death.
2166 summonedStats->HP = 0; // kill me instantly
2167 }
2168 else if ( fellInLava )
2169 {
2170 summonedEntity->setObituary(language[1506]); // goes for a swim in some lava.
2171 summonedStats->HP = 0; // kill me instantly
2172 }
2173 else
2174 {
2175 for ( node_t* node = map.creatures->first; node != nullptr; node = node->next )
2176 {
2177 Entity* creature = (Entity*)node->element;
2178 if ( creature && creature->behavior == &actMonster && creature != target && creature != summonedEntity )
2179 {
2180 if ( creature->monsterTarget == target->getUID() )
2181 {
2182 if ( creature->checkEnemy(summonedEntity) )
2183 {
2184 creature->monsterAcquireAttackTarget(*summonedEntity, MONSTER_STATE_PATH); // re-acquire new target
2185 }
2186 else
2187 {
2188 creature->monsterReleaseAttackTarget(); // release if new target is ally.
2189 }
2190 }
2191 }
2192 }
2193 }
2194
2195 list_RemoveNode(target->mynode);
2196 target = nullptr;
2197 return summonedEntity;
2198 }
2199 else if ( target->behavior == &actPlayer )
2200 {
2201 if ( target->setEffect(EFF_POLYMORPH, true, effectDuration, true) )
2202 {
2203 spawnExplosion(target->x, target->y, target->z);
2204 playSoundEntity(target, 400, 92);
2205 createParticleDropRising(target, 593, 1.f);
2206 serverSpawnMiscParticles(target, PARTICLE_EFFECT_RISING_DROP, 593);
2207
2208 if ( targetStats->playerRace == RACE_HUMAN )
2209 {
2210 int roll = (RACE_HUMAN + 1) + rand() % 8;
2211 if ( target->effectPolymorph == 0 )
2212 {
2213 target->effectPolymorph = target->getMonsterFromPlayerRace(roll);
2214 }
2215 else
2216 {
2217 while ( target->effectPolymorph == target->getMonsterFromPlayerRace(roll) )
2218 {
2219 roll = (RACE_HUMAN + 1) + rand() % 8; // re roll to not polymorph into the same thing
2220 }
2221 target->effectPolymorph = target->getMonsterFromPlayerRace(roll);
2222 }
2223 }
2224 else if ( (targetStats->playerRace != RACE_HUMAN && targetStats->appearance == 0) )
2225 {
2226 target->effectPolymorph = 100 + rand() % NUMAPPEARANCES;
2227 }
2228 serverUpdateEntitySkill(target, 50);
2229
2230 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2231 Monster race = NOTHING;
2232 if ( target->effectPolymorph > NUMMONSTERS )
2233 {
2234 race = HUMAN;
2235 }
2236 else
2237 {
2238 race = static_cast<Monster>(target->effectPolymorph);
2239 }
2240 if ( race < KOBOLD )
2241 {
2242 messagePlayerColor(target->skill[2], color, language[3186], language[90 + race]);
2243 }
2244 else
2245 {
2246 messagePlayerColor(target->skill[2], color, language[3186], language[2000 + race - KOBOLD]);
2247 }
2248
2249 // change player's type here, don't like this.. will get auto reset in actPlayer() though
2250 // otherwise the below aggro check will still assume previous race since actPlayer() hasn't run yet.
2251 targetStats->type = race;
2252
2253 for ( node_t* node = map.creatures->first; node != nullptr; node = node->next )
2254 {
2255 Entity* creature = (Entity*)node->element;
2256 if ( creature && creature->behavior == &actMonster && creature != target )
2257 {
2258 if ( creature->monsterTarget == target->getUID() )
2259 {
2260 if ( creature->checkEnemy(target) )
2261 {
2262 creature->monsterAcquireAttackTarget(*target, MONSTER_STATE_PATH); // re-acquire new target
2263 }
2264 else
2265 {
2266 creature->monsterReleaseAttackTarget(); // release if new target is ally.
2267 }
2268 }
2269 }
2270 }
2271 }
2272 else
2273 {
2274 messagePlayer(target->skill[2], language[3189]);
2275 }
2276
2277 }
2278
2279 return nullptr;
2280 }
2281
spellEffectTeleportPull(Entity * my,spellElement_t & element,Entity * parent,Entity * target,int resistance)2282 bool spellEffectTeleportPull(Entity* my, spellElement_t& element, Entity* parent, Entity* target, int resistance)
2283 {
2284 if ( !parent )
2285 {
2286 return false;
2287 }
2288 if ( target )
2289 {
2290 playSoundEntity(target, 173, 128);
2291 //int damage = element.damage;
2292 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
2293
2294 if ( target->behavior == &actMonster || target->behavior == &actPlayer
2295 /*|| target->behavior == &actDoor || target->behavior == &actChest*/ )
2296 {
2297 Stat* hitstats = target->getStats();
2298 if ( hitstats )
2299 {
2300 if ( !(svFlags & SV_FLAG_FRIENDLYFIRE) )
2301 {
2302 // test for friendly fire
2303 if ( parent && parent->checkFriend(target) )
2304 {
2305 return false;
2306 }
2307 }
2308 }
2309 //playSoundEntity(target, 249, 64);
2310
2311 if ( parent )
2312 {
2313 if ( target->behavior == &actPlayer )
2314 {
2315 if ( MFLAG_DISABLETELEPORT )
2316 {
2317 // can't teleport here.
2318 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
2319 messagePlayerColor(target->skill[2], color, language[2381]);
2320 if ( parent->behavior == &actPlayer )
2321 {
2322 messagePlayerColor(parent->skill[2], color, language[3452]);
2323 }
2324 return false;
2325 }
2326 }
2327 if ( target->behavior == &actMonster && target->isBossMonster() )
2328 {
2329 if ( parent->behavior == &actPlayer )
2330 {
2331 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2332 if ( hitstats )
2333 {
2334 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[2905], language[2906], MSG_COMBAT);
2335 }
2336 }
2337 return false;
2338 }
2339
2340 // try find a teleport location in front of the caster.
2341 int tx = static_cast<int>(std::floor(parent->x + 32 * cos(parent->yaw))) >> 4;
2342 int ty = static_cast<int>(std::floor(parent->y + 32 * sin(parent->yaw))) >> 4;
2343 int dist = 2;
2344 bool foundLocation = false;
2345 int numlocations = 0;
2346 std::vector<std::pair<int, int>> goodspots;
2347 std::vector<std::pair<int, int>> spotsWithLineOfSight;
2348 if ( !checkObstacle((tx << 4) + 8, (ty << 4) + 8, target, NULL) ) // try find directly infront of caster.
2349 {
2350 Entity* ohitentity = hit.entity;
2351 real_t ox = target->x;
2352 real_t oy = target->y;
2353 target->x = (tx << 4) + 8;
2354 target->y = (ty << 4) + 8;
2355 TileEntityList.updateEntity(*target); // important - lineTrace needs the TileEntityListUpdated.
2356
2357 // pretend the target is in the supposed spawn locations and try linetrace from each position.
2358 real_t tangent = atan2(target->y - parent->y, target->x - parent->x);
2359 lineTraceTarget(parent, parent->x, parent->y, tangent, 92, 0, true, target);
2360 if ( hit.entity == target )
2361 {
2362 foundLocation = true;
2363 }
2364
2365 // reset the coordinates we messed with
2366 target->x = ox;
2367 target->y = oy;
2368 TileEntityList.updateEntity(*target); // important - lineTrace needs the TileEntityListUpdated.
2369 hit.entity = ohitentity;
2370 }
2371 if ( !foundLocation )
2372 {
2373 // otherwise, let's search in an area
2374 for ( int iy = std::max(1, ty - dist); iy < std::min(ty + dist, static_cast<int>(map.height)); ++iy )
2375 {
2376 for ( int ix = std::max(1, tx - dist); ix < std::min(tx + dist, static_cast<int>(map.width)); ++ix )
2377 {
2378 if ( !checkObstacle((ix << 4) + 8, (iy << 4) + 8, target, NULL) )
2379 {
2380 Entity* ohitentity = hit.entity;
2381 real_t ox = target->x;
2382 real_t oy = target->y;
2383 target->x = (ix << 4) + 8;
2384 target->y = (iy << 4) + 8;
2385 TileEntityList.updateEntity(*target); // important - lineTrace needs the TileEntityListUpdated.
2386
2387 // pretend the target is in the supposed spawn locations and try linetrace from each position.
2388 real_t tangent = atan2(target->y - parent->y, target->x - parent->x);
2389 lineTraceTarget(parent, parent->x, parent->y, tangent, 92, 0, false, target);
2390 if ( hit.entity == target )
2391 {
2392 spotsWithLineOfSight.push_back(std::make_pair(ix, iy));
2393 }
2394 goodspots.push_back(std::make_pair(ix, iy));
2395 numlocations++;
2396 // reset the coordinates we messed with
2397 target->x = ox;
2398 target->y = oy;
2399 TileEntityList.updateEntity(*target); // important - lineTrace needs the TileEntityListUpdated.
2400 hit.entity = ohitentity;
2401 }
2402 }
2403 }
2404 if ( numlocations == 0 )
2405 {
2406 if ( parent->behavior == &actPlayer )
2407 {
2408 // no room to teleport!
2409 messagePlayer(parent->skill[2], language[3453]);
2410 }
2411 return false;
2412 }
2413
2414 if ( !spotsWithLineOfSight.empty() )
2415 {
2416 std::pair<int, int> tmpPair = spotsWithLineOfSight[rand() % spotsWithLineOfSight.size()];
2417 tx = tmpPair.first;
2418 ty = tmpPair.second;
2419 }
2420 else if ( !goodspots.empty() )
2421 {
2422 std::pair<int, int> tmpPair = goodspots[rand() % goodspots.size()];
2423 tx = tmpPair.first;
2424 ty = tmpPair.second;
2425 }
2426 else
2427 {
2428 if ( parent->behavior == &actPlayer )
2429 {
2430 // no room to teleport!
2431 messagePlayer(parent->skill[2], language[3453]);
2432 }
2433 return false;
2434 }
2435 }
2436
2437 // this timer is the entity spawn location.
2438 Entity* locationTimer = createParticleTimer(parent, 40, 593);
2439 locationTimer->x = tx * 16.0 + 8;
2440 locationTimer->y = ty * 16.0 + 8;
2441 locationTimer->z = 0;
2442 locationTimer->particleTimerCountdownAction = PARTICLE_EFFECT_TELEPORT_PULL_TARGET_LOCATION;
2443 locationTimer->particleTimerCountdownSprite = 593;
2444 locationTimer->particleTimerTarget = static_cast<Sint32>(target->getUID()); // get the target to teleport around.
2445 locationTimer->particleTimerEndAction = PARTICLE_EFFECT_TELEPORT_PULL; // teleport behavior of timer.
2446 locationTimer->particleTimerEndSprite = 593; // sprite to use for end of timer function.
2447 locationTimer->flags[PASSABLE] = false; // so this location is reserved for teleporting the entity.
2448 locationTimer->sizex = 4;
2449 locationTimer->sizey = 4;
2450 if ( !locationTimer->myTileListNode )
2451 {
2452 locationTimer->setUID(-2);
2453 TileEntityList.addEntity(*locationTimer);
2454 locationTimer->setUID(-3);
2455 }
2456
2457 // set a coundown to spawn particles on the monster.
2458 Entity* spellTimer = createParticleTimer(target, 40, 593);
2459 spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SHOOT_PARTICLES;
2460 spellTimer->particleTimerCountdownSprite = 593;
2461 spellTimer->particleTimerTarget = static_cast<Sint32>(parent->getUID()); // get the target to teleport around.
2462
2463
2464 if ( multiplayer == SERVER )
2465 {
2466 serverSpawnMiscParticles(target, PARTICLE_EFFECT_TELEPORT_PULL, 593);
2467 serverSpawnMiscParticlesAtLocation(tx, ty, 0, PARTICLE_EFFECT_TELEPORT_PULL_TARGET_LOCATION, 593);
2468 }
2469
2470 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2471 if ( parent->behavior == &actPlayer )
2472 {
2473 // play a sound for the player to confirm the hit.
2474 playSoundPlayer(parent->skill[2], 251, 128);
2475 }
2476
2477 // update enemy bar for attacker
2478 if ( hitstats )
2479 {
2480 if ( !strcmp(hitstats->name, "") )
2481 {
2482 if ( hitstats->type < KOBOLD ) //Original monster count
2483 {
2484 updateEnemyBar(parent, target, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
2485 }
2486 else if ( hitstats->type >= KOBOLD ) //New monsters
2487 {
2488 updateEnemyBar(parent, target, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
2489 }
2490 }
2491 else
2492 {
2493 updateEnemyBar(parent, target, hitstats->name, hitstats->HP, hitstats->MAXHP);
2494 }
2495 }
2496 }
2497 return true;
2498 }
2499 if ( my )
2500 {
2501 spawnMagicEffectParticles(target->x, target->y, target->z, my->sprite);
2502 }
2503 }
2504 else if ( my )
2505 {
2506 spawnMagicEffectParticles(my->x, my->y, my->z, my->sprite);
2507 }
2508 return false;
2509 }
2510
spellEffectShadowTag(Entity & my,spellElement_t & element,Entity * parent,int resistance)2511 void spellEffectShadowTag(Entity& my, spellElement_t& element, Entity* parent, int resistance)
2512 {
2513 if ( hit.entity )
2514 {
2515 //int damage = element.damage;
2516 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
2517
2518 if ( hit.entity->behavior == &actMonster || hit.entity->behavior == &actPlayer )
2519 {
2520 playSoundEntity(&my, 174, 128);
2521 Stat* hitstats = hit.entity->getStats();
2522 if ( !hitstats )
2523 {
2524 return;
2525 }
2526
2527 if ( parent )
2528 {
2529 bool sameAsPrevious = false;
2530 if ( parent->creatureShadowTaggedThisUid != 0 )
2531 {
2532 Entity* oldTarget = nullptr;
2533 if ( oldTarget = uidToEntity(parent->creatureShadowTaggedThisUid) )
2534 {
2535 if ( oldTarget != hit.entity )
2536 {
2537 oldTarget->setEffect(EFF_SHADOW_TAGGED, false, 0, true);
2538 }
2539 else
2540 {
2541 sameAsPrevious = true;
2542 }
2543 }
2544 }
2545 if ( parent->checkFriend(hit.entity) )
2546 {
2547 hit.entity->setEffect(EFF_SHADOW_TAGGED, true, 60 * TICKS_PER_SECOND, true);
2548 }
2549 else
2550 {
2551 hit.entity->setEffect(EFF_SHADOW_TAGGED, true, 10 * TICKS_PER_SECOND, true);
2552 }
2553 parent->creatureShadowTaggedThisUid = hit.entity->getUID();
2554 serverUpdateEntitySkill(parent, 54);
2555 if ( !sameAsPrevious )
2556 {
2557 createParticleShadowTag(hit.entity, parent->getUID(), 60 * TICKS_PER_SECOND);
2558 serverSpawnMiscParticles(hit.entity, PARTICLE_EFFECT_SHADOW_TAG, 870, parent->getUID());
2559 }
2560 }
2561
2562 // hit messages
2563 if ( parent )
2564 {
2565 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2566 if ( parent->behavior == &actPlayer )
2567 {
2568 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3463], language[3464], MSG_COMBAT);
2569 }
2570 }
2571
2572 // update enemy bar for attacker
2573 if ( !strcmp(hitstats->name, "") )
2574 {
2575 if ( hitstats->type < KOBOLD ) //Original monster count
2576 {
2577 updateEnemyBar(parent, hit.entity, language[90 + hitstats->type], hitstats->HP, hitstats->MAXHP);
2578 }
2579 else if ( hitstats->type >= KOBOLD ) //New monsters
2580 {
2581 updateEnemyBar(parent, hit.entity, language[2000 + (hitstats->type - KOBOLD)], hitstats->HP, hitstats->MAXHP);
2582 }
2583 }
2584 else
2585 {
2586 updateEnemyBar(parent, hit.entity, hitstats->name, hitstats->HP, hitstats->MAXHP);
2587 }
2588
2589 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2590 int player = -1;
2591 if ( hit.entity->behavior == &actPlayer )
2592 {
2593 player = hit.entity->skill[2];
2594 if ( player >= 0 )
2595 {
2596 messagePlayerColor(player, color, language[3465]);
2597 }
2598 }
2599 }
2600 spawnMagicEffectParticles(hit.entity->x, hit.entity->y, hit.entity->z, my.sprite);
2601 }
2602 else
2603 {
2604 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
2605 }
2606 }
2607
spellEffectDemonIllusion(Entity & my,spellElement_t & element,Entity * parent,Entity * target,int resistance)2608 bool spellEffectDemonIllusion(Entity& my, spellElement_t& element, Entity* parent, Entity* target, int resistance)
2609 {
2610 if ( target )
2611 {
2612 //int damage = element.damage;
2613 //damage += ((element->mana - element->base_mana) / static_cast<double>(element->overload_multiplier)) * element->damage;
2614
2615 if ( target->behavior == &actMonster || target->behavior == &actPlayer )
2616 {
2617 Stat* hitstats = target->getStats();
2618 if ( !hitstats )
2619 {
2620 return false;
2621 }
2622
2623 if ( hitstats->type == INCUBUS || hitstats->type == SUCCUBUS
2624 || hitstats->type == AUTOMATON || hitstats->type == DEVIL || hitstats->type == DEMON || hitstats->type == CREATURE_IMP
2625 || hitstats->type == SHADOW
2626 || (hitstats->type == INCUBUS && !strncmp(hitstats->name, "inner demon", strlen("inner demon"))) )
2627 {
2628 if ( parent && parent->behavior == &actPlayer )
2629 {
2630 // unable to taunt!
2631 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
2632 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3472], language[3473], MSG_COMBAT);
2633 }
2634 return false;
2635 }
2636 else if ( hitstats->monsterDemonHasBeenExorcised != 0
2637 && target->behavior != &actPlayer )
2638 {
2639 if ( parent && parent->behavior == &actPlayer )
2640 {
2641 // already exorcised!
2642 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 255);
2643 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3735], language[3736], MSG_COMBAT);
2644 }
2645 return false;
2646 }
2647
2648 if ( parent )
2649 {
2650 // try find a summon location around the entity.
2651 int tx = static_cast<int>(std::floor(target->x)) >> 4;
2652 int ty = static_cast<int>(std::floor(target->y)) >> 4;
2653 int dist = 3;
2654 int numlocations = 0;
2655 std::vector<std::pair<int, int>> goodspots;
2656 for ( int iy = std::max(1, ty - dist); iy < std::min(ty + dist, static_cast<int>(map.height)); ++iy )
2657 {
2658 for ( int ix = std::max(1, tx - dist); ix < std::min(tx + dist, static_cast<int>(map.width)); ++ix )
2659 {
2660 if ( !checkObstacle((ix << 4) + 8, (iy << 4) + 8, target, NULL) )
2661 {
2662 Entity* ohitentity = hit.entity;
2663 real_t ox = parent->x;
2664 real_t oy = parent->y;
2665 parent->x = (ix << 4) + 8;
2666 parent->y = (iy << 4) + 8;
2667 TileEntityList.updateEntity(*parent); // important - lineTrace needs the TileEntityListUpdated.
2668
2669 // pretend the parent is in the supposed spawn locations and try linetrace from each position.
2670 real_t tangent = atan2(parent->y - target->y, parent->x - target->x);
2671 lineTraceTarget(target, target->x, target->y, tangent, 64, 0, false, parent);
2672 if ( hit.entity == parent )
2673 {
2674 goodspots.push_back(std::make_pair(ix, iy));
2675 numlocations++;
2676 }
2677 // reset the coordinates we messed with
2678 parent->x = ox;
2679 parent->y = oy;
2680 TileEntityList.updateEntity(*parent); // important - lineTrace needs the TileEntityListUpdated.
2681 hit.entity = ohitentity;
2682 }
2683 }
2684 }
2685 if ( numlocations == 0 )
2686 {
2687 if ( parent->behavior == &actPlayer )
2688 {
2689 // no room to spawn!
2690 messagePlayer(parent->skill[2], language[3471]);
2691 }
2692 return false;
2693 }
2694 std::pair<int, int> tmpPair = goodspots[rand() % goodspots.size()];
2695 tx = tmpPair.first;
2696 ty = tmpPair.second;
2697
2698 Entity* monster = summonMonster(INCUBUS, tx * 16.0 + 8, ty * 16.0 + 8, true);
2699 if ( monster )
2700 {
2701 spawnExplosion(monster->x, monster->y, -1);
2702 playSoundEntity(monster, 171, 128);
2703 //playSoundEntity(&my, 178, 128);
2704 createParticleErupt(monster, 983);
2705 serverSpawnMiscParticles(monster, PARTICLE_EFFECT_ERUPT, 983);
2706
2707 monster->parent = parent->getUID();
2708 monster->monsterIllusionTauntingThisUid = static_cast<Sint32>(target->getUID());
2709 switch ( target->getRace() )
2710 {
2711 case LICH:
2712 case LICH_FIRE:
2713 case LICH_ICE:
2714 case MINOTAUR:
2715 break;
2716 default:
2717 target->monsterAcquireAttackTarget(*monster, MONSTER_STATE_PATH);
2718 break;
2719 }
2720 monster->lookAtEntity(*target);
2721 Stat* monsterStats = monster->getStats();
2722 if ( monsterStats )
2723 {
2724 monsterStats->leader_uid = 0;
2725 strcpy(monsterStats->name, "inner demon");
2726 monster->setEffect(EFF_STUNNED, true, 20, false);
2727 monster->flags[USERFLAG2] = true;
2728 serverUpdateEntityFlag(monster, USERFLAG2);
2729 if ( monsterChangesColorWhenAlly(monsterStats) )
2730 {
2731 int bodypart = 0;
2732 for ( node_t* node = (monster)->children.first; node != nullptr; node = node->next )
2733 {
2734 if ( bodypart >= LIMB_HUMANOID_TORSO )
2735 {
2736 Entity* tmp = (Entity*)node->element;
2737 if ( tmp )
2738 {
2739 tmp->flags[USERFLAG2] = true;
2740 serverUpdateEntityFlag(tmp, USERFLAG2);
2741 }
2742 }
2743 ++bodypart;
2744 }
2745 }
2746 }
2747 Stat* parentStats = parent->getStats();
2748 if ( parentStats )
2749 {
2750 if ( parent->behavior == &actPlayer )
2751 {
2752 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 255, 0);
2753 messagePlayerColor(parent->skill[2], color, language[621]);
2754 }
2755 parent->modHP(-(parentStats->MAXHP / 10));
2756 if ( parentStats->sex == MALE )
2757 {
2758 parent->setObituary(language[1528]);
2759 }
2760 else
2761 {
2762 parent->setObituary(language[1529]);
2763 }
2764 }
2765
2766 hitstats->monsterDemonHasBeenExorcised++;
2767
2768 // hit messages
2769 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2770 if ( parent->behavior == &actPlayer )
2771 {
2772 messagePlayerMonsterEvent(parent->skill[2], color, *hitstats, language[3469], language[3470], MSG_COMBAT);
2773 }
2774 }
2775 }
2776
2777 Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
2778 int player = -1;
2779 if ( target->behavior == &actPlayer )
2780 {
2781 player = target->skill[2];
2782 if ( player >= 0 )
2783 {
2784 messagePlayerColor(player, color, language[3468]);
2785 if ( hitstats->monsterDemonHasBeenExorcised == 3 )
2786 {
2787 Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 0);
2788 messagePlayerColor(player, color, language[3468]);
2789 }
2790 }
2791 }
2792 spawnMagicEffectParticles(target->x, target->y, target->z, my.sprite);
2793 return true;
2794 }
2795 spawnMagicEffectParticles(target->x, target->y, target->z, my.sprite);
2796 }
2797 else
2798 {
2799 spawnMagicEffectParticles(my.x, my.y, my.z, my.sprite);
2800 }
2801 return false;
2802 }