1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_lich.cpp
5 Desc: implements all of the lich monster's code
6
7 Copyright 2013-2016 (c) Turning Wheel LLC, all rights reserved.
8 See LICENSE for details.
9
10 -------------------------------------------------------------------------------*/
11
12 #include "main.hpp"
13 #include "game.hpp"
14 #include "stat.hpp"
15 #include "entity.hpp"
16 #include "monster.hpp"
17 #include "sound.hpp"
18 #include "items.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23
24 static const int LICH_BODY = 0;
25 static const int LICH_RIGHTARM = 2;
26 static const int LICH_LEFTARM = 3;
27 static const int LICH_HEAD = 4;
28 static const int LICH_WEAPON = 5;
29
initLichFire(Entity * my,Stat * myStats)30 void initLichFire(Entity* my, Stat* myStats)
31 {
32 my->initMonster(646);
33
34 if ( multiplayer != CLIENT )
35 {
36 MONSTER_SPOTSND = 372;
37 MONSTER_SPOTVAR = 4;
38 MONSTER_IDLESND = -1;
39 MONSTER_IDLEVAR = 1;
40 }
41 if ( multiplayer != CLIENT && !MONSTER_INIT )
42 {
43 if ( myStats != nullptr )
44 {
45 if ( !myStats->leader_uid )
46 {
47 myStats->leader_uid = 0;
48 }
49
50 // apply random stat increases if set in stat_shared.cpp or editor
51 setRandomMonsterStats(myStats);
52
53 for ( int c = 1; c < MAXPLAYERS; ++c )
54 {
55 if ( !client_disconnected[c] )
56 {
57 myStats->MAXHP += 500;
58 }
59 }
60
61 myStats->HP = myStats->MAXHP;
62 myStats->OLDHP = myStats->HP;
63
64 // generate 6 items max, less if there are any forced items from boss variants
65 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
66
67 // boss variants
68
69 // random effects
70 myStats->EFFECTS[EFF_LEVITATING] = true;
71 myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
72
73 // generates equipment and weapons if available from editor
74 createMonsterEquipment(myStats);
75
76 // create any custom inventory items from editor if available
77 createCustomInventory(myStats, customItemsToGenerate);
78
79 // count if any custom inventory items from editor
80 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
81
82 // count any inventory items set to default in edtior
83 int defaultItems = countDefaultItems(myStats);
84
85 my->setHardcoreStats(*myStats);
86
87 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
88 switch ( defaultItems )
89 {
90 case 6:
91 case 5:
92 case 4:
93 case 3:
94 case 2:
95 case 1:
96 default:
97 break;
98 }
99
100 //give weapon
101 if ( myStats->weapon == NULL && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
102 {
103 myStats->weapon = newItem(CRYSTAL_SWORD, EXCELLENT, -5, 1, rand(), false, NULL);
104 }
105 }
106 }
107
108 // right arm
109 Entity* entity = newEntity(649, 0, map.entities, nullptr);
110 entity->sizex = 4;
111 entity->sizey = 4;
112 entity->skill[2] = my->getUID();
113 entity->flags[PASSABLE] = true;
114 entity->flags[NOUPDATE] = true;
115 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
116 entity->focalx = limbs[LICH_FIRE][1][0]; // 0
117 entity->focaly = limbs[LICH_FIRE][1][1]; // 0
118 entity->focalz = limbs[LICH_FIRE][1][2]; // 2
119 entity->behavior = &actLichFireLimb;
120 entity->parent = my->getUID();
121 node_t* node = list_AddNodeLast(&my->children);
122 node->element = entity;
123 node->deconstructor = &emptyDeconstructor;
124 node->size = sizeof(Entity*);
125 my->bodyparts.push_back(entity);
126
127 // left arm
128 entity = newEntity(648, 0, map.entities, nullptr);
129 entity->sizex = 4;
130 entity->sizey = 4;
131 entity->skill[2] = my->getUID();
132 entity->flags[PASSABLE] = true;
133 entity->flags[NOUPDATE] = true;
134 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
135 entity->focalx = limbs[LICH_FIRE][2][0]; // 0
136 entity->focaly = limbs[LICH_FIRE][2][1]; // 0
137 entity->focalz = limbs[LICH_FIRE][2][2]; // 2
138 entity->behavior = &actLichFireLimb;
139 entity->parent = my->getUID();
140 node = list_AddNodeLast(&my->children);
141 node->element = entity;
142 node->deconstructor = &emptyDeconstructor;
143 node->size = sizeof(Entity*);
144 my->bodyparts.push_back(entity);
145
146 // head
147 entity = newEntity(647, 0, map.entities, nullptr);
148 entity->yaw = my->yaw;
149 entity->sizex = 4;
150 entity->sizey = 4;
151 entity->skill[2] = my->getUID();
152 entity->flags[PASSABLE] = true;
153 entity->flags[NOUPDATE] = true;
154 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
155 entity->focalx = limbs[LICH_FIRE][3][0]; // 0
156 entity->focaly = limbs[LICH_FIRE][3][1]; // 0
157 entity->focalz = limbs[LICH_FIRE][3][2]; // -2
158 entity->behavior = &actLichFireLimb;
159 entity->parent = my->getUID();
160 node = list_AddNodeLast(&my->children);
161 node->element = entity;
162 node->deconstructor = &emptyDeconstructor;
163 node->size = sizeof(Entity*);
164 my->bodyparts.push_back(entity);
165
166 // world weapon
167 entity = newEntity(-1, 0, map.entities, nullptr);
168 entity->sizex = 4;
169 entity->sizey = 4;
170 entity->skill[2] = my->getUID();
171 entity->flags[PASSABLE] = true;
172 entity->flags[NOUPDATE] = true;
173 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
174 entity->focalx = limbs[LICH_FIRE][4][0]; // 1.5
175 entity->focaly = limbs[LICH_FIRE][4][1]; // 0
176 entity->focalz = limbs[LICH_FIRE][4][2]; // -.5
177 entity->behavior = &actLichFireLimb;
178 entity->parent = my->getUID();
179 entity->pitch = .25;
180 node = list_AddNodeLast(&my->children);
181 node->element = entity;
182 node->deconstructor = &emptyDeconstructor;
183 node->size = sizeof(Entity*);
184 my->bodyparts.push_back(entity);
185 }
186
lichFireDie(Entity * my)187 void lichFireDie(Entity* my)
188 {
189 node_t* node, *nextnode;
190 int c;
191 for ( c = 0; c < 20; c++ )
192 {
193 Entity* entity = spawnGib(my);
194 if ( entity )
195 {
196 switch ( c )
197 {
198 case 0:
199 entity->sprite = 230;
200 break;
201 case 1:
202 entity->sprite = 231;
203 break;
204 case 2:
205 entity->sprite = 233;
206 break;
207 case 3:
208 entity->sprite = 235;
209 break;
210 case 4:
211 entity->sprite = 236;
212 break;
213 case 5:
214 entity->sprite = 646;
215 break;
216 case 6:
217 entity->sprite = 647;
218 break;
219 case 7:
220 entity->sprite = 648;
221 break;
222 case 8:
223 entity->sprite = 649;
224 break;
225 default:
226 break;
227 }
228 serverSpawnGibForClient(entity);
229 }
230 }
231 my->removeMonsterDeathNodes();
232 playSoundEntity(my, 94, 128);
233 my->removeLightField();
234 // kill all other monsters on the level
235 for ( node = map.creatures->first; my->monsterLichAllyStatus == LICH_ALLY_DEAD && node != NULL; node = nextnode )
236 {
237 nextnode = node->next;
238 Entity* entity = (Entity*)node->element;
239 if ( entity )
240 {
241 if ( entity == my || entity->sprite == 650 )
242 {
243 continue;
244 }
245 if ( entity->behavior == &actMonster )
246 {
247 spawnExplosion(entity->x, entity->y, entity->z);
248 Stat* stats = entity->getStats();
249 if ( stats )
250 {
251 if ( stats->type != HUMAN )
252 {
253 stats->HP = 0;
254 }
255 }
256 }
257 }
258 }
259
260 spawnExplosion(my->x, my->y, my->z);
261 list_RemoveNode(my->mynode);
262 return;
263 }
264
actLichFireLimb(Entity * my)265 void actLichFireLimb(Entity* my)
266 {
267 my->actMonsterLimb();
268 }
269
lichFireAnimate(Entity * my,Stat * myStats,double dist)270 void lichFireAnimate(Entity* my, Stat* myStats, double dist)
271 {
272 node_t* node;
273 Entity* entity = nullptr, *entity2 = nullptr;
274 Entity* rightbody = nullptr;
275 Entity* weaponarm = nullptr;
276 Entity* head = nullptr;
277 Entity* spellarm = nullptr;
278 int bodypart;
279 bool wearingring = false;
280
281 // remove old light field
282 my->removeLightField();
283
284 // obtain head entity
285 node = list_Node(&my->children, LICH_HEAD);
286 if ( node )
287 {
288 head = (Entity*)node->element;
289 }
290
291 // set invisibility //TODO: isInvisible()?
292 if ( multiplayer != CLIENT )
293 {
294 if ( myStats->ring != nullptr )
295 if ( myStats->ring->type == RING_INVISIBILITY )
296 {
297 wearingring = true;
298 }
299 if ( myStats->cloak != nullptr )
300 if ( myStats->cloak->type == CLOAK_INVISIBILITY )
301 {
302 wearingring = true;
303 }
304 if ( myStats->EFFECTS[EFF_INVISIBLE] == true || wearingring == true )
305 {
306 my->flags[INVISIBLE] = true;
307 my->flags[BLOCKSIGHT] = false;
308 bodypart = 0;
309 for ( node = my->children.first; node != nullptr; node = node->next )
310 {
311 if ( bodypart < LICH_RIGHTARM )
312 {
313 bodypart++;
314 continue;
315 }
316 if ( bodypart >= LICH_WEAPON )
317 {
318 break;
319 }
320 entity = (Entity*)node->element;
321 if ( !entity->flags[INVISIBLE] )
322 {
323 entity->flags[INVISIBLE] = true;
324 serverUpdateEntityBodypart(my, bodypart);
325 }
326 bodypart++;
327 }
328 }
329 else
330 {
331 my->flags[INVISIBLE] = false;
332 my->flags[BLOCKSIGHT] = true;
333 bodypart = 0;
334 for ( node = my->children.first; node != nullptr; node = node->next )
335 {
336 if ( bodypart < LICH_RIGHTARM )
337 {
338 bodypart++;
339 continue;
340 }
341 if ( bodypart >= LICH_WEAPON )
342 {
343 break;
344 }
345 entity = (Entity*)node->element;
346 if ( entity->flags[INVISIBLE] )
347 {
348 entity->flags[INVISIBLE] = false;
349 serverUpdateEntityBodypart(my, bodypart);
350 serverUpdateEntityFlag(my, INVISIBLE);
351 }
352 bodypart++;
353 }
354 }
355
356 if ( my->monsterLichBattleState == LICH_BATTLE_IMMOBILE && my->ticks > TICKS_PER_SECOND )
357 {
358 int sides = 0;
359 int my_x = static_cast<int>(my->x) >> 4;
360 int my_y = static_cast<int>(my->y) >> 4;
361 int mapIndex = (my_y)* MAPLAYERS + (my_x + 1) * MAPLAYERS * map.height;
362 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
363 {
364 ++sides;
365 }
366 mapIndex = (my_y)* MAPLAYERS + (my_x - 1) * MAPLAYERS * map.height;
367 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
368 {
369 ++sides;
370 }
371 mapIndex = (my_y + 1) * MAPLAYERS + (my_x)* MAPLAYERS * map.height;
372 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
373 {
374 ++sides;
375 }
376 mapIndex = (my_y - 1) * MAPLAYERS + (my_x)* MAPLAYERS * map.height;
377 if ( map.tiles[OBSTACLELAYER + mapIndex] ) // wall
378 {
379 ++sides;
380 }
381 if ( sides != 4 )
382 {
383 my->monsterLichBattleState = LICH_BATTLE_READY;
384 real_t distToPlayer = 0;
385 int c, playerToChase = -1;
386 for ( c = 0; c < MAXPLAYERS; c++ )
387 {
388 if ( players[c] && players[c]->entity )
389 {
390 if ( !distToPlayer )
391 {
392 distToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
393 playerToChase = c;
394 }
395 else
396 {
397 double newDistToPlayer = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
398 if ( newDistToPlayer < distToPlayer )
399 {
400 distToPlayer = newDistToPlayer;
401 playerToChase = c;
402 }
403 }
404 }
405 }
406 if ( playerToChase >= 0 )
407 {
408 if ( players[playerToChase] && players[playerToChase]->entity )
409 {
410 my->monsterAcquireAttackTarget(*players[playerToChase]->entity, MONSTER_STATE_PATH);
411 }
412 }
413 }
414 }
415
416 // passive floating effect, server only.
417 if ( my->monsterState == MONSTER_STATE_LICHFIRE_DIE )
418 {
419 my->z -= 0.03;
420 }
421 else if ( my->monsterAttack == 0 )
422 {
423 if ( my->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_NONE )
424 {
425 if ( my->z < -1.2 )
426 {
427 my->z += 0.25;
428 }
429 else
430 {
431 my->z = -1.2;
432 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
433 }
434 }
435 if ( dist < 0.1 )
436 {
437 // not moving, float.
438 limbAnimateWithOvershoot(my, ANIMATE_Z, 0.005, -1.5, 0.005, -1.2, ANIMATE_DIR_NEGATIVE);
439 }
440 }
441 else if ( my->monsterAttack == 1 || my->monsterAttack == 3 )
442 {
443 if ( my->z < -1.2 )
444 {
445 my->z += 0.25;
446 }
447 else
448 {
449 my->z = -1.2;
450 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_NONE;
451 }
452 }
453 }
454 else
455 {
456 }
457
458 if ( !my->light )
459 {
460 my->light = lightSphereShadow(my->x / 16, my->y / 16, 4, 192);
461 }
462
463 //Lich stares you down while he does his special ability windup, and any of his spellcasting animations.
464 if ( (my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1
465 || my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP2
466 || my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1
467 || my->monsterAttack == MONSTER_POSE_MAGIC_CAST1
468 || my->monsterState == MONSTER_STATE_LICH_CASTSPELLS)
469 && my->monsterState != MONSTER_STATE_LICHFIRE_DIE )
470 {
471 //Always turn to face the target.
472 Entity* target = uidToEntity(my->monsterTarget);
473 if ( target )
474 {
475 my->lookAtEntity(*target);
476 my->monsterRotate();
477 }
478 }
479
480 // move arms
481 Entity* rightarm = nullptr;
482 for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++)
483 {
484 if ( bodypart < LICH_RIGHTARM )
485 {
486 if ( bodypart == 0 ) // insert head/body animation here.
487 {
488 if ( my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1 )
489 {
490 if ( multiplayer != CLIENT && my->monsterAnimationLimbOvershoot >= ANIMATE_OVERSHOOT_TO_SETPOINT )
491 {
492 // handle z movement on windup
493 limbAnimateWithOvershoot(my, ANIMATE_Z, 0.2, -0.6, 0.1, -3.2, ANIMATE_DIR_POSITIVE); // default z is -1.2
494 if ( my->z > -0.5 )
495 {
496 my->z = -0.6; //failsafe for floating too low sometimes?
497 }
498 }
499 }
500 else if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP3 )
501 {
502 if ( multiplayer != CLIENT && my->monsterAnimationLimbOvershoot >= ANIMATE_OVERSHOOT_TO_SETPOINT )
503 {
504 // handle z movement on windup
505 limbAnimateWithOvershoot(my, ANIMATE_Z, 0.3, -0.6, 0.3, -4.0, ANIMATE_DIR_POSITIVE); // default z is -1.2
506 if ( my->z > -0.5 )
507 {
508 my->z = -0.6; //failsafe for floating too low sometimes?
509 }
510 }
511 }
512 else
513 {
514 if ( head->pitch > PI )
515 {
516 limbAnimateToLimit(head, ANIMATE_PITCH, 0.1, 0, false, 0.0); // return head to a neutral position.
517 }
518 }
519 }
520 continue;
521 }
522 entity = (Entity*)node->element;
523 entity->x = my->x;
524 entity->y = my->y;
525 entity->z = my->z;
526 if ( bodypart != LICH_HEAD )
527 {
528 // lich head turns to track player, other limbs will rotate as normal.
529 if ( bodypart == LICH_LEFTARM && my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1 )
530 {
531 // don't rotate leftarm here during spellcast.
532 }
533 else
534 {
535 entity->yaw = my->yaw;
536 }
537 }
538 else
539 {
540
541 }
542 if ( bodypart == LICH_RIGHTARM )
543 {
544 // weapon holding arm.
545 weaponarm = entity;
546 if ( my->monsterAttack == 0 )
547 {
548 entity->pitch = PI / 8; // default arm pitch when not attacking.
549 }
550 else
551 {
552 // vertical chop windup
553 if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP1 )
554 {
555 if ( my->monsterAttackTime == 0 )
556 {
557 // init rotations
558 my->monsterWeaponYaw = 0;
559 weaponarm->roll = 0;
560 weaponarm->skill[1] = 0;
561 }
562
563 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.4, 5 * PI / 4, false, 0.0);
564
565 if ( my->monsterAttackTime >= 6 / (monsterGlobalAnimationMultiplier / 10.0) )
566 {
567 if ( multiplayer != CLIENT )
568 {
569 my->attack(1, 0, nullptr);
570 }
571 }
572 }
573 // vertical chop attack
574 else if ( my->monsterAttack == 1 )
575 {
576 if ( weaponarm->skill[1] == 0 )
577 {
578 // chop forwards
579 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.4, PI / 2, false, 0.0) )
580 {
581 weaponarm->skill[1] = 1;
582 }
583 }
584 else if ( weaponarm->skill[1] == 1 )
585 {
586 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, PI / 8, false, 0.0) )
587 {
588 my->monsterWeaponYaw = 0;
589 weaponarm->pitch = PI / 8;
590 weaponarm->roll = 0;
591 my->monsterAttack = 0;
592 if ( multiplayer != CLIENT )
593 {
594 if ( my->monsterLichFireMeleePrev == LICH_ATK_VERTICAL_QUICK )
595 {
596 my->monsterHitTime = HITRATE;
597 }
598 }
599 }
600 }
601 }
602 // horizontal chop windup
603 else if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2 )
604 {
605 int windupDuration = (my->monsterState == MONSTER_STATE_LICH_CASTSPELLS) ? 10 : 6;
606 if ( my->monsterAttackTime == 0 )
607 {
608 // init rotations
609 weaponarm->pitch = PI / 4;
610 weaponarm->roll = 0;
611 my->monsterArmbended = 1; // don't actually bend the arm, we're just using this to adjust the limb offsets in the weapon code.
612 weaponarm->skill[1] = 0;
613 my->monsterWeaponYaw = 6 * PI / 4;
614 if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS )
615 {
616 createParticleDot(my);
617 }
618 }
619
620 limbAnimateToLimit(weaponarm, ANIMATE_ROLL, -0.3, 3 * PI / 2, false, 0.0);
621 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.3, 0, false, 0.0);
622
623
624 if ( my->monsterAttackTime >= windupDuration / (monsterGlobalAnimationMultiplier / 10.0) )
625 {
626 if ( multiplayer != CLIENT )
627 {
628 my->attack(2, 0, nullptr);
629 }
630 }
631 }
632 // horizontal chop attack
633 else if ( my->monsterAttack == 2 )
634 {
635 if ( weaponarm->skill[1] == 0 )
636 {
637 // swing
638 // this->weaponyaw is OK to change for clients, as server doesn't update it for them.
639 if ( limbAnimateToLimit(my, ANIMATE_WEAPON_YAW, 0.3, 2 * PI / 8, false, 0.0) )
640 {
641 weaponarm->skill[1] = 1;
642 }
643 }
644 else if ( weaponarm->skill[1] == 1 )
645 {
646 // post-swing return to normal weapon yaw
647 if ( limbAnimateToLimit(my, ANIMATE_WEAPON_YAW, -0.5, 0, false, 0.0) )
648 {
649 // restore pitch and roll after yaw is set
650 if ( limbAnimateToLimit(weaponarm, ANIMATE_ROLL, 0.4, 0, false, 0.0)
651 && limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.4, PI / 8, false, 0.0) )
652 {
653 weaponarm->skill[1] = 0;
654 my->monsterWeaponYaw = 0;
655 weaponarm->pitch = PI / 8;
656 weaponarm->roll = 0;
657 my->monsterArmbended = 0;
658 my->monsterAttack = 0;
659 if ( multiplayer != CLIENT )
660 {
661 if ( my->monsterLichFireMeleePrev == LICH_ATK_HORIZONTAL_QUICK )
662 {
663 my->monsterHitTime = HITRATE;
664 }
665 }
666 }
667 }
668 }
669 }
670 else if ( my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1 )
671 {
672 if ( my->monsterAttackTime == 0 )
673 {
674 // init rotations
675 my->monsterWeaponYaw = 0;
676 weaponarm->roll = 0;
677 weaponarm->skill[1] = 0;
678 if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS
679 || my->monsterState == MONSTER_STATE_LICHFIRE_DIE )
680 {
681 createParticleDropRising(my, 672, 0.7);
682 }
683 else
684 {
685 createParticleDropRising(my, 607, 1.0);
686 }
687 if ( multiplayer != CLIENT )
688 {
689 if ( my->monsterState != MONSTER_STATE_LICHFIRE_DIE )
690 {
691 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
692 // lich can't be paralyzed, use EFF_STUNNED instead.
693 myStats->EFFECTS[EFF_STUNNED] = true;
694 myStats->EFFECTS_TIMERS[EFF_STUNNED] = 50;
695 }
696 else
697 {
698 myStats->EFFECTS[EFF_STUNNED] = true;
699 myStats->EFFECTS_TIMERS[EFF_STUNNED] = 25;
700 }
701 }
702 }
703
704 // only do the following during 2nd + end stage of overshoot animation.
705 if ( my->monsterAnimationLimbOvershoot != ANIMATE_OVERSHOOT_TO_SETPOINT )
706 {
707 limbAnimateToLimit(head, ANIMATE_PITCH, -0.1, 11 * PI / 6, true, 0.05);
708 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.3, 5 * PI / 4, false, 0.0);
709
710 if ( my->monsterAttackTime >= 50 / (monsterGlobalAnimationMultiplier / 10.0) )
711 {
712 if ( multiplayer != CLIENT )
713 {
714 if ( my->monsterState != MONSTER_STATE_LICHFIRE_DIE )
715 {
716 my->attack(1, 0, nullptr); //optional?
717 }
718 else
719 {
720 my->monsterAttackTime = 20; //reset this attack time to allow successive strikes
721 }
722 real_t dir = 0.f;
723 Entity* target = uidToEntity(my->monsterTarget);
724 if ( my->monsterState == MONSTER_STATE_LICH_CASTSPELLS )
725 {
726 if ( target )
727 {
728 real_t targetDist = std::max(8.0, entityDist(my, target) - 48.0);
729 for ( int i = 0; i < 5; ++i )
730 {
731 my->castFallingMagicMissile(SPELL_FIREBALL, targetDist -4 + rand() % 9 + i * 16, 0.f, i * 20);
732 }
733 }
734 }
735 else if ( my->monsterState == MONSTER_STATE_LICHFIRE_DIE )
736 {
737 real_t randomAngle = (PI / 180.f) * (rand() % 360);
738 for ( int i = 0; i < 5; ++i )
739 {
740 my->castFallingMagicMissile(SPELL_FIREBALL, 16.f - 4 + rand() % 9 + i * 16, randomAngle, i * 20);
741 }
742 for ( int i = 0; i < 5; ++i )
743 {
744 my->castFallingMagicMissile(SPELL_FIREBALL, 16.f - 4 + rand() % 9 + i * 16, randomAngle + PI / 2, i * 20);
745 }
746 for ( int i = 0; i < 5; ++i )
747 {
748 my->castFallingMagicMissile(SPELL_FIREBALL, 16.f - 4 + rand() % 9 + i * 16, randomAngle + PI, i * 20);
749 }
750 for ( int i = 0; i < 5; ++i )
751 {
752 my->castFallingMagicMissile(SPELL_FIREBALL, 16.f - 4 + rand() % 9 + i * 16, randomAngle + 3 * PI / 2, i * 20);
753 }
754 }
755 else
756 {
757 if ( target )
758 {
759 real_t targetDist = std::min(entityDist(my, target), 32.0);
760 for ( int i = 0; i < 8; ++i )
761 {
762 my->castFallingMagicMissile(SPELL_FIREBALL, std::max(targetDist - 8 + rand() % 8, 4.0), dir + i * PI / 4, 0);
763 }
764 }
765 else
766 {
767 for ( int i = 0; i < 8; ++i )
768 {
769 my->castFallingMagicMissile(SPELL_FIREBALL, 16 + rand() % 8, dir + i * PI / 4, 0);
770 }
771 }
772 }
773 }
774 }
775 }
776 }
777 else if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP3 )
778 {
779 int windupDuration = (my->monsterState == MONSTER_STATE_LICH_CASTSPELLS) ? 20 : 40;
780 if ( multiplayer != CLIENT && myStats->EFFECTS[EFF_VAMPIRICAURA] )
781 {
782 windupDuration = 20;
783 }
784 if ( my->monsterAttackTime == 0 )
785 {
786 // init rotations
787 my->monsterWeaponYaw = 10 * PI / 6;
788 weaponarm->roll = 0;
789 weaponarm->skill[1] = 0;
790 createParticleDot(my);
791 if ( multiplayer != CLIENT )
792 {
793 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
794 // // lich can't be paralyzed, use EFF_STUNNED instead.
795 myStats->EFFECTS[EFF_STUNNED] = true;
796 myStats->EFFECTS_TIMERS[EFF_STUNNED] = windupDuration;
797 }
798 }
799
800 // only do the following during 2nd + end stage of overshoot animation.
801 if ( my->monsterAnimationLimbOvershoot != ANIMATE_OVERSHOOT_TO_SETPOINT )
802 {
803 limbAnimateToLimit(head, ANIMATE_PITCH, -0.1, 11 * PI / 6, true, 0.05);
804 limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.3, 5 * PI / 4, false, 0.0);
805
806 if ( my->monsterAttackTime >= windupDuration / (monsterGlobalAnimationMultiplier / 10.0) )
807 {
808 if ( multiplayer != CLIENT )
809 {
810 my->attack(3, 0, nullptr);
811 }
812 }
813 }
814 }
815 // vertical chop after melee3
816 else if ( my->monsterAttack == 3 )
817 {
818 if ( weaponarm->skill[1] == 0 )
819 {
820 // chop forwards
821 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, 0.4, PI / 2, false, 0.0) )
822 {
823 weaponarm->skill[1] = 1;
824 }
825 limbAnimateToLimit(my, ANIMATE_WEAPON_YAW, 0.15, 1 * PI / 6, false, 0.0); // swing across the body
826 }
827 else if ( weaponarm->skill[1] == 1 )
828 {
829 if ( limbAnimateToLimit(weaponarm, ANIMATE_PITCH, -0.25, PI / 8, false, 0.0) )
830 {
831 my->monsterWeaponYaw = 0;
832 weaponarm->pitch = PI / 8;
833 weaponarm->roll = 0;
834 my->monsterAttack = 0;
835 }
836 }
837 }
838 }
839 }
840 else if ( bodypart == LICH_LEFTARM )
841 {
842 spellarm = entity;
843 if ( my->monsterAttack == MONSTER_POSE_SPECIAL_WINDUP1 || my->monsterAttack == MONSTER_POSE_MELEE_WINDUP3 )
844 {
845 spellarm->pitch = weaponarm->pitch;
846 }
847 else if ( my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1 )
848 {
849 if ( my->monsterAttackTime == 0 )
850 {
851 // init rotations
852 spellarm->roll = 0;
853 spellarm->skill[1] = 0;
854 spellarm->pitch = 12 * PI / 8;
855 spellarm->yaw = my->yaw;
856 createParticleDot(my);
857 playSoundEntityLocal(my, 170, 32);
858 if ( multiplayer != CLIENT )
859 {
860 myStats->EFFECTS[EFF_STUNNED] = true;
861 myStats->EFFECTS_TIMERS[EFF_STUNNED] = 20;
862 }
863 }
864 double animationYawSetpoint = normaliseAngle2PI(my->yaw + 1 * PI / 8);
865 double animationYawEndpoint = normaliseAngle2PI(my->yaw - 1 * PI / 8);
866 double armSwingRate = 0.15;
867 double animationPitchSetpoint = 13 * PI / 8;
868 double animationPitchEndpoint = 11 * PI / 8;
869
870 if ( spellarm->skill[1] == 0 )
871 {
872 if ( limbAnimateToLimit(spellarm, ANIMATE_PITCH, armSwingRate, animationPitchSetpoint, false, 0.0) )
873 {
874 if ( limbAnimateToLimit(spellarm, ANIMATE_YAW, armSwingRate, animationYawSetpoint, false, 0.0) )
875 {
876 spellarm->skill[1] = 1;
877 }
878 }
879 }
880 else
881 {
882 if ( limbAnimateToLimit(spellarm, ANIMATE_PITCH, -armSwingRate, animationPitchEndpoint, false, 0.0) )
883 {
884 if ( limbAnimateToLimit(spellarm, ANIMATE_YAW, -armSwingRate, animationYawEndpoint, false, 0.0) )
885 {
886 spellarm->skill[1] = 0;
887 }
888 }
889 }
890
891 if ( my->monsterAttackTime >= 1 * ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
892 {
893 if ( multiplayer != CLIENT )
894 {
895 // swing the arm after we prepped the spell
896 //my->castFallingMagicMissile(SPELL_FIREBALL, 16.0, 0.0);
897 my->attack(MONSTER_POSE_MAGIC_WINDUP2, 0, nullptr);
898 }
899 }
900 }
901 // raise arm to cast spell
902 else if ( my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP2 )
903 {
904 if ( my->monsterAttackTime == 0 )
905 {
906 // init rotations
907 spellarm->pitch = 0;
908 spellarm->roll = 0;
909 }
910 spellarm->skill[1] = 0;
911
912 if ( limbAnimateToLimit(spellarm, ANIMATE_PITCH, -0.3, 5 * PI / 4, false, 0.0) )
913 {
914 if ( multiplayer != CLIENT )
915 {
916 my->attack(MONSTER_POSE_MAGIC_CAST1, 0, nullptr);
917 }
918 }
919 }
920 // vertical spell attack
921 else if ( my->monsterAttack == MONSTER_POSE_MAGIC_CAST1 )
922 {
923 if ( spellarm->skill[1] == 0 )
924 {
925 // chop forwards
926 if ( limbAnimateToLimit(spellarm, ANIMATE_PITCH, 0.4, PI / 2, false, 0.0) )
927 {
928 spellarm->skill[1] = 1;
929 if ( multiplayer != CLIENT )
930 {
931 //my->castOrbitingMagicMissile(SPELL_FIREBALL, 16.0, 0.0);
932 if ( rand() % 2 )
933 {
934 if ( my->monsterLichAllyStatus == LICH_ALLY_DEAD
935 && !myStats->EFFECTS[EFF_VAMPIRICAURA]
936 && my->monsterState != MONSTER_STATE_LICH_CASTSPELLS )
937 {
938 createParticleDropRising(my, 600, 0.7);
939 serverSpawnMiscParticles(my, PARTICLE_EFFECT_VAMPIRIC_AURA, 600);
940 myStats->EFFECTS[EFF_VAMPIRICAURA] = true;
941 myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] = 600;
942 }
943 else
944 {
945 castSpell(my->getUID(), getSpellFromID(SPELL_FIREBALL), true, false);
946 }
947 }
948 else
949 {
950 castSpell(my->getUID(), getSpellFromID(SPELL_BLEED), true, false);
951 }
952 }
953 }
954 }
955 else if ( spellarm->skill[1] == 1 )
956 {
957 if ( limbAnimateToLimit(spellarm, ANIMATE_PITCH, -0.25, PI / 8, false, 0.0) )
958 {
959 spellarm->pitch = 0;
960 spellarm->roll = 0;
961 my->monsterAttack = 0;
962 }
963 }
964 }
965 else
966 {
967 entity->pitch = 0;
968 }
969 }
970 switch ( bodypart )
971 {
972 // right arm
973 case LICH_RIGHTARM:
974 entity->x += 2.75 * cos(my->yaw + PI / 2);
975 entity->y += 2.75 * sin(my->yaw + PI / 2);
976 entity->z -= 3.25;
977 entity->yaw += MONSTER_WEAPONYAW;
978 break;
979 // left arm
980 case LICH_LEFTARM:
981 entity->x -= 2.75 * cos(my->yaw + PI / 2);
982 entity->y -= 2.75 * sin(my->yaw + PI / 2);
983 entity->z -= 3.25;
984 if ( !(my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2
985 || my->monsterAttack == 2
986 || my->monsterAttack == MONSTER_POSE_MAGIC_WINDUP1
987 || my->monsterAttack == 3)
988 )
989 {
990 entity->yaw -= MONSTER_WEAPONYAW;
991 }
992 break;
993 // head
994 case LICH_HEAD:
995 {
996 entity->z -= 4.25;
997 node_t* tempNode;
998 Entity* playertotrack = NULL;
999 double disttoplayer = 0.0;
1000 Entity* target = uidToEntity(my->monsterTarget);
1001 if ( target && my->monsterAttack == 0 )
1002 {
1003 entity->lookAtEntity(*target);
1004 entity->monsterRotate();
1005 }
1006 else
1007 {
1008 // align head as normal if attacking.
1009 entity->yaw = my->yaw;
1010 }
1011 break;
1012 }
1013 case LICH_WEAPON:
1014 // set sprites, invisibility check etc.
1015 if ( multiplayer != CLIENT )
1016 {
1017 if ( myStats->weapon == nullptr || myStats->EFFECTS[EFF_INVISIBLE] || wearingring ) //TODO: isInvisible()?
1018 {
1019 entity->flags[INVISIBLE] = true;
1020 }
1021 else
1022 {
1023 entity->sprite = itemModel(myStats->weapon);
1024 if ( itemCategory(myStats->weapon) == SPELLBOOK )
1025 {
1026 entity->flags[INVISIBLE] = true;
1027 }
1028 else
1029 {
1030 entity->flags[INVISIBLE] = false;
1031 }
1032 }
1033 if ( multiplayer == SERVER )
1034 {
1035 // update sprites for clients
1036 if ( entity->skill[10] != entity->sprite )
1037 {
1038 entity->skill[10] = entity->sprite;
1039 serverUpdateEntityBodypart(my, bodypart);
1040 }
1041 if ( entity->skill[11] != entity->flags[INVISIBLE] )
1042 {
1043 entity->skill[11] = entity->flags[INVISIBLE];
1044 serverUpdateEntityBodypart(my, bodypart);
1045 }
1046 if ( entity->getUID() % (TICKS_PER_SECOND * 10) == ticks % (TICKS_PER_SECOND * 10) )
1047 {
1048 serverUpdateEntityBodypart(my, bodypart);
1049 }
1050 }
1051 }
1052 else
1053 {
1054 if ( entity->sprite <= 0 )
1055 {
1056 entity->flags[INVISIBLE] = true;
1057 }
1058 }
1059
1060 // animation
1061 if ( entity != nullptr )
1062 {
1063 if ( weaponarm == nullptr )
1064 {
1065 return;
1066 }
1067 entity->x = weaponarm->x;// +1.5 * cos(weaponarm->yaw);// *(my->monsterAttack == 0);
1068 entity->y = weaponarm->y;// +1.5 * sin(weaponarm->yaw);// * (my->monsterAttack == 0);
1069 entity->z = weaponarm->z;// -.5 * (my->monsterAttack == 0);
1070 entity->pitch = weaponarm->pitch;
1071 entity->yaw = weaponarm->yaw + 0.1 * (my->monsterAttack == 0);
1072 entity->roll = weaponarm->roll;
1073 if ( my->monsterAttack == 2 || my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2 )
1074 {
1075 // don't boost pitch during side-swipe
1076 }
1077 else
1078 {
1079 entity->pitch += PI / 2 + 0.25;
1080 }
1081
1082 entity->focalx = limbs[LICH_FIRE][4][0];
1083 entity->focaly = limbs[LICH_FIRE][4][1];
1084 entity->focalz = limbs[LICH_FIRE][4][2];
1085 if ( my->monsterArmbended )
1086 {
1087 // adjust focal points during side swing
1088 entity->focalx = limbs[LICH_FIRE][4][0] - 0.8;
1089 entity->focalz = limbs[LICH_FIRE][4][2] + 1;
1090 entity->pitch += cos(weaponarm->roll) * PI / 2;
1091 entity->yaw -= sin(weaponarm->roll) * PI / 2;
1092 }
1093 }
1094 break;
1095 default:
1096 break;
1097 }
1098 }
1099 if ( my->monsterAttack > 0 && my->monsterAttack <= MONSTER_POSE_MAGIC_CAST3 )
1100 {
1101 my->monsterAttackTime++;
1102 }
1103 else if ( my->monsterAttack == 0 )
1104 {
1105 my->monsterAttackTime = 0;
1106 }
1107 else
1108 {
1109 // do nothing, don't reset attacktime or increment it.
1110 }
1111 }
1112
lichFireSetNextAttack(Stat & myStats)1113 void Entity::lichFireSetNextAttack(Stat& myStats)
1114 {
1115 monsterLichFireMeleePrev = monsterLichFireMeleeSeq;
1116 //messagePlayer(0, "melee: %d, magic %d", monsterLichMeleeSwingCount, monsterLichMagicCastCount);
1117 switch ( monsterLichFireMeleeSeq )
1118 {
1119 case LICH_ATK_VERTICAL_SINGLE:
1120 ++monsterLichMeleeSwingCount;
1121 switch ( rand() % 8 )
1122 {
1123 case 0:
1124 case 1:
1125 case 2:
1126 if ( monsterLichMeleeSwingCount < 5 )
1127 {
1128 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1129 }
1130 else
1131 {
1132 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_QUICK;
1133 monsterLichMeleeSwingCount = 0;
1134 }
1135 break;
1136 case 3:
1137 case 4:
1138 case 5:
1139 if ( monsterLichMeleeSwingCount < 5 )
1140 {
1141 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_SINGLE;
1142 }
1143 else
1144 {
1145 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_QUICK;
1146 monsterLichMeleeSwingCount = 0;
1147 }
1148 break;
1149 case 6:
1150 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_QUICK;
1151 monsterLichMeleeSwingCount = 0;
1152 break;
1153 case 7:
1154 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_QUICK;
1155 monsterLichMeleeSwingCount = 0;
1156 break;
1157 default:
1158 break;
1159 }
1160 break;
1161 case LICH_ATK_HORIZONTAL_SINGLE:
1162 ++monsterLichMeleeSwingCount;
1163 switch ( rand() % 4 )
1164 {
1165 case 0:
1166 case 1:
1167 case 2:
1168 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1169 break;
1170 case 3:
1171 monsterLichFireMeleeSeq = LICH_ATK_RISING_RAIN;
1172 monsterLichMeleeSwingCount = 0;
1173 break;
1174 default:
1175 break;
1176 }
1177 break;
1178 case LICH_ATK_RISING_RAIN:
1179 switch ( rand() % 4 )
1180 {
1181 case 0:
1182 case 1:
1183 case 2:
1184 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1185 break;
1186 case 3:
1187 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_SINGLE;
1188 break;
1189 default:
1190 break;
1191 }
1192 break;
1193 case LICH_ATK_BASICSPELL_SINGLE:
1194 ++monsterLichMagicCastCount;
1195 if ( monsterLichMagicCastCount > 2 || rand() % 2 == 0 )
1196 {
1197 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1198 monsterLichMagicCastCount = 0;
1199 }
1200 break;
1201 case LICH_ATK_RISING_SINGLE:
1202 switch ( rand() % 4 )
1203 {
1204 case 0:
1205 case 1:
1206 case 2:
1207 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1208 break;
1209 case 3:
1210 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_SINGLE;
1211 break;
1212 default:
1213 break;
1214 }
1215 break;
1216 case LICH_ATK_VERTICAL_QUICK:
1217 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_RETURN;
1218 break;
1219 case LICH_ATK_HORIZONTAL_RETURN:
1220 switch ( rand() % 4 )
1221 {
1222 case 0:
1223 case 1:
1224 case 2:
1225 monsterLichFireMeleeSeq = LICH_ATK_VERTICAL_SINGLE;
1226 break;
1227 case 3:
1228 monsterLichFireMeleeSeq = LICH_ATK_HORIZONTAL_SINGLE;
1229 break;
1230 default:
1231 break;
1232 }
1233 break;
1234 case LICH_ATK_HORIZONTAL_QUICK:
1235 monsterLichFireMeleeSeq = LICH_ATK_RISING_SINGLE;
1236 break;
1237 default:
1238 break;
1239 }
1240 }
1241
lichFireTeleport()1242 void Entity::lichFireTeleport()
1243 {
1244 monsterLichTeleportTimer = 0;
1245 Entity* spellTimer = createParticleTimer(this, 40, 593);
1246 if ( monsterState == MONSTER_STATE_LICHFIRE_TELEPORT_STATIONARY )
1247 {
1248 spellTimer->particleTimerEndAction = PARTICLE_EFFECT_LICHFIRE_TELEPORT_STATIONARY; // teleport behavior of timer.
1249 }
1250 else
1251 {
1252 spellTimer->particleTimerEndAction = PARTICLE_EFFECT_LICH_TELEPORT_ROAMING; // teleport behavior of timer.
1253 }
1254 spellTimer->particleTimerEndSprite = 593; // sprite to use for end of timer function.
1255 spellTimer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SHOOT_PARTICLES;
1256 spellTimer->particleTimerCountdownSprite = 593;
1257 if ( multiplayer == SERVER )
1258 {
1259 serverSpawnMiscParticles(this, spellTimer->particleTimerEndAction, 593);
1260 }
1261 }
1262
lichFireSummonMonster(Monster creature)1263 void Entity::lichFireSummonMonster(Monster creature)
1264 {
1265 Entity* target = nullptr;
1266 for ( node_t* searchNode = map.entities->first; searchNode != nullptr; searchNode = searchNode->next )
1267 {
1268 target = (Entity*)searchNode->element;
1269 if ( target->behavior == &actDevilTeleport
1270 && target->sprite == 128 )
1271 {
1272 break; // found specified center of map
1273 }
1274 }
1275 if ( target )
1276 {
1277 int tries = 25; // max iteration in while loop, fail safe.
1278 long spawn_x = (target->x / 16) - 11 + rand() % 23;
1279 long spawn_y = (target->y / 16) - 11 + rand() % 23;
1280 int index = (spawn_x)* MAPLAYERS + (spawn_y)* MAPLAYERS * map.height;
1281 while ( tries > 0 &&
1282 (map.tiles[OBSTACLELAYER + index] == 1
1283 || map.tiles[index] == 0
1284 || swimmingtiles[map.tiles[index]]
1285 || lavatiles[map.tiles[index]])
1286 )
1287 {
1288 // find a spot that isn't wall, no floor or lava/water tiles.
1289 spawn_x = (target->x / 16) - 11 + rand() % 23;
1290 spawn_y = (target->y / 16) - 11 + rand() % 23;
1291 index = (spawn_x)* MAPLAYERS + (spawn_y)* MAPLAYERS * map.height;
1292 --tries;
1293 }
1294 if ( tries > 0 )
1295 {
1296 Entity* timer = createParticleTimer(this, 70, 174);
1297 timer->x = spawn_x * 16.0 + 8;
1298 timer->y = spawn_y * 16.0 + 8;
1299 timer->z = 0;
1300 timer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SUMMON_MONSTER;
1301 timer->particleTimerCountdownSprite = 174;
1302 timer->particleTimerEndAction = PARTICLE_EFFECT_SUMMON_MONSTER;
1303 timer->particleTimerVariable1 = creature;
1304 serverSpawnMiscParticlesAtLocation(spawn_x, spawn_y, 0, PARTICLE_EFFECT_SUMMON_MONSTER, 174);
1305 }
1306 }
1307 }