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