1 /*-------------------------------------------------------------------------------
2
3 BARONY
4 File: monster_cockatrice.cpp
5 Desc: implements all of the cockatrice 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 "items.hpp"
17 #include "monster.hpp"
18 #include "sound.hpp"
19 #include "net.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23
initCockatrice(Entity * my,Stat * myStats)24 void initCockatrice(Entity* my, Stat* myStats)
25 {
26 node_t* node;
27
28 my->initMonster(413);
29
30 if ( multiplayer != CLIENT )
31 {
32 MONSTER_SPOTSND = 385;
33 MONSTER_SPOTVAR = 3;
34 MONSTER_IDLESND = 382;
35 MONSTER_IDLEVAR = 2;
36 }
37 if ( multiplayer != CLIENT && !MONSTER_INIT )
38 {
39 if ( myStats != nullptr )
40 {
41 if ( !myStats->leader_uid )
42 {
43 myStats->leader_uid = 0;
44 }
45
46 // apply random stat increases if set in stat_shared.cpp or editor
47 setRandomMonsterStats(myStats);
48
49 // generate 6 items max, less if there are any forced items from boss variants
50 int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
51
52 // boss variants
53
54 // random effects
55 myStats->EFFECTS[EFF_LEVITATING] = true;
56 myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
57
58 // cockatrices don't sleep!
59 /*if ( rand() % 4 == 0 )
60 {
61 myStats->EFFECTS[EFF_ASLEEP] = true;
62 myStats->EFFECTS_TIMERS[EFF_ASLEEP] = 1800 + rand() % 3600;
63 }*/
64
65 // generates equipment and weapons if available from editor
66 createMonsterEquipment(myStats);
67
68 // create any custom inventory items from editor if available
69 createCustomInventory(myStats, customItemsToGenerate);
70
71 // count if any custom inventory items from editor
72 int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
73
74 // count any inventory items set to default in edtior
75 int defaultItems = countDefaultItems(myStats);
76
77 my->setHardcoreStats(*myStats);
78
79 // always give special spell to cockatrice, undroppable.
80 newItem(SPELLBOOK_STONEBLOOD, DECREPIT, 0, 1, MONSTER_ITEM_UNDROPPABLE_APPEARANCE, false, &myStats->inventory);
81 // variables for potion drops below.
82 int minValue = 70;
83 int maxValue = 80;
84 int numRolls = 1; // 0-2 extra rolls
85 if ( rand() % 2 == 0 ) // 50% chance
86 {
87 ++numRolls;
88 if ( rand() % 2 == 0 ) // 25% chance, including the previous roll
89 {
90 ++numRolls;
91 }
92 }
93
94 // generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
95 switch ( defaultItems )
96 {
97 case 6:
98 case 5:
99 // TODO: cockatrice head.
100 case 4:
101 if ( rand() % 20 == 0 ) // 5% drop stoneblood spellbook
102 {
103 newItem(static_cast<ItemType>(SPELLBOOK_STONEBLOOD), static_cast<Status>(1 + rand() % 4), -1 + rand() % 3, 1, rand(), false, &myStats->inventory);
104 }
105 case 3:
106 if ( rand() % 5 == 0 ) // 20% for gemstone, luckstone to obsidian. qty 1-2.
107 {
108 if ( rand() % 3 == 0 )
109 {
110 newItem(ENCHANTED_FEATHER, WORN, 0, 1, (2 * (ENCHANTED_FEATHER_MAX_DURABILITY - 1)) / 4, false, &myStats->inventory);
111 }
112 else
113 {
114 newItem(static_cast<ItemType>(GEM_LUCK + rand() % 16), static_cast<Status>(EXCELLENT), 0, 1 + rand() % 2, rand(), false, &myStats->inventory);
115 }
116 }
117 case 2:
118 if ( rand() % 10 < 3 ) // 30% drop stoneblood magicstaff
119 {
120 newItem(static_cast<ItemType>(MAGICSTAFF_STONEBLOOD), static_cast<Status>(1 + rand() % 4), -1 + rand() % 3, 1, rand(), false, &myStats->inventory);
121 }
122 case 1:
123 for ( int i = 0; i < numRolls; ++i )
124 {
125 if ( rand() % 3 == 0 ) // 33% chance to choose high value item
126 {
127 minValue = 100;
128 maxValue = 100;
129 }
130 ItemType itemType = itemTypeWithinGoldValue(Category::POTION, minValue, maxValue);
131 newItem(itemType, static_cast<Status>(1 + rand() % 4), -1 + rand() % 3, 1, rand(), false, &myStats->inventory);
132 // reset values for next loop.
133 minValue = 70;
134 maxValue = 80;
135 }
136 break;
137 default:
138 break;
139 }
140
141 //give weapon
142 if ( myStats->weapon == nullptr && myStats->EDITOR_ITEMS[ITEM_SLOT_WEAPON] == 1 )
143 {
144 //TODO: normal spell?
145 }
146 }
147 }
148
149 // torso
150 Entity* entity = newEntity(414, 0, map.entities, nullptr); //Limb entity.
151 entity->sizex = 4;
152 entity->sizey = 4;
153 entity->focaly = 1;
154 entity->skill[2] = my->getUID();
155 entity->flags[PASSABLE] = true;
156 entity->flags[NOUPDATE] = true;
157 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
158 entity->focalx = limbs[COCKATRICE][1][0]; // 0
159 entity->focaly = limbs[COCKATRICE][1][1]; // 1
160 entity->focalz = limbs[COCKATRICE][1][2]; // 0
161 entity->behavior = &actCockatriceLimb;
162 entity->parent = my->getUID();
163 node = list_AddNodeLast(&my->children);
164 node->element = entity;
165 node->deconstructor = &emptyDeconstructor;
166 node->size = sizeof(Entity*);
167 my->bodyparts.push_back(entity);
168
169 // right leg
170 entity = newEntity(416, 0, map.entities, nullptr); //Limb entity.
171 entity->sizex = 4;
172 entity->sizey = 4;
173 entity->skill[2] = my->getUID();
174 entity->flags[PASSABLE] = true;
175 entity->flags[NOUPDATE] = true;
176 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
177 entity->focalx = limbs[COCKATRICE][2][0]; // 0
178 entity->focaly = limbs[COCKATRICE][2][1]; // 0
179 entity->focalz = limbs[COCKATRICE][2][2]; // 2
180 entity->behavior = &actCockatriceLimb;
181 entity->parent = my->getUID();
182 node = list_AddNodeLast(&my->children);
183 node->element = entity;
184 node->deconstructor = &emptyDeconstructor;
185 node->size = sizeof(Entity*);
186 my->bodyparts.push_back(entity);
187
188 // left leg
189 entity = newEntity(415, 0, map.entities, nullptr); //Limb entity.
190 entity->sizex = 4;
191 entity->sizey = 4;
192 entity->skill[2] = my->getUID();
193 entity->flags[PASSABLE] = true;
194 entity->flags[NOUPDATE] = true;
195 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
196 entity->focalx = limbs[COCKATRICE][3][0]; // 0
197 entity->focaly = limbs[COCKATRICE][3][1]; // 0
198 entity->focalz = limbs[COCKATRICE][3][2]; // 2
199 entity->behavior = &actCockatriceLimb;
200 entity->parent = my->getUID();
201 node = list_AddNodeLast(&my->children);
202 node->element = entity;
203 node->deconstructor = &emptyDeconstructor;
204 node->size = sizeof(Entity*);
205 my->bodyparts.push_back(entity);
206
207 // right arm
208 entity = newEntity(418, 0, map.entities, nullptr); //Limb entity.
209 entity->sizex = 4;
210 entity->sizey = 4;
211 entity->skill[2] = my->getUID();
212 entity->flags[PASSABLE] = true;
213 entity->flags[NOUPDATE] = true;
214 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
215 entity->focalx = limbs[COCKATRICE][4][0]; // 0
216 entity->focaly = limbs[COCKATRICE][4][1]; // 0
217 entity->focalz = limbs[COCKATRICE][4][2]; // 3
218 entity->behavior = &actCockatriceLimb;
219 entity->parent = my->getUID();
220 node = list_AddNodeLast(&my->children);
221 node->element = entity;
222 node->deconstructor = &emptyDeconstructor;
223 node->size = sizeof(Entity*);
224 my->bodyparts.push_back(entity);
225
226 // left arm
227 entity = newEntity(417, 0, map.entities, nullptr); //Limb entity.
228 entity->sizex = 4;
229 entity->sizey = 4;
230 entity->skill[2] = my->getUID();
231 entity->flags[PASSABLE] = true;
232 entity->flags[NOUPDATE] = true;
233 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
234 entity->focalx = limbs[COCKATRICE][5][0]; // 0
235 entity->focaly = limbs[COCKATRICE][5][1]; // 0
236 entity->focalz = limbs[COCKATRICE][5][2]; // 3
237 entity->behavior = &actCockatriceLimb;
238 entity->parent = my->getUID();
239 node = list_AddNodeLast(&my->children);
240 node->element = entity;
241 node->deconstructor = &emptyDeconstructor;
242 node->size = sizeof(Entity*);
243 my->bodyparts.push_back(entity);
244
245 // right wing
246 entity = newEntity(420, 0, map.entities, nullptr); //Limb entity.
247 entity->sizex = 4;
248 entity->sizey = 4;
249 entity->skill[2] = my->getUID();
250 entity->flags[PASSABLE] = true;
251 entity->flags[NOUPDATE] = true;
252 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
253 entity->focalx = limbs[COCKATRICE][6][0]; // 0
254 entity->focaly = limbs[COCKATRICE][6][1]; // 4
255 entity->focalz = limbs[COCKATRICE][6][2]; // 0
256 entity->behavior = &actCockatriceLimb;
257 entity->parent = my->getUID();
258 node = list_AddNodeLast(&my->children);
259 node->element = entity;
260 node->deconstructor = &emptyDeconstructor;
261 node->size = sizeof(Entity*);
262 my->bodyparts.push_back(entity);
263
264 // left wing
265 entity = newEntity(419, 0, map.entities, nullptr); //Limb entity.
266 entity->sizex = 4;
267 entity->sizey = 4;
268 entity->skill[2] = my->getUID();
269 entity->flags[PASSABLE] = true;
270 entity->flags[NOUPDATE] = true;
271 entity->flags[USERFLAG2] = my->flags[USERFLAG2];
272 entity->focalx = limbs[COCKATRICE][7][0]; // 0
273 entity->focaly = limbs[COCKATRICE][7][1]; // -4
274 entity->focalz = limbs[COCKATRICE][7][2]; // 0
275 entity->behavior = &actCockatriceLimb;
276 entity->parent = my->getUID();
277 node = list_AddNodeLast(&my->children);
278 node->element = entity;
279 node->deconstructor = &emptyDeconstructor;
280 node->size = sizeof(Entity*);
281 my->bodyparts.push_back(entity);
282 }
283
actCockatriceLimb(Entity * my)284 void actCockatriceLimb(Entity* my)
285 {
286 my->actMonsterLimb();
287 }
288
cockatriceDie(Entity * my)289 void cockatriceDie(Entity* my)
290 {
291 int c;
292 for ( c = 0; c < 5; c++ )
293 {
294 Entity* gib = spawnGib(my);
295 serverSpawnGibForClient(gib);
296 }
297
298 my->spawnBlood();
299
300 //playSoundEntity(my, 28, 128);
301 playSoundEntity(my, 388 + rand() % 2, 128);
302
303 my->removeMonsterDeathNodes();
304
305 list_RemoveNode(my->mynode);
306 return;
307 }
308
309 #define COCKATRICEWALKSPEED .01
310
cockatriceMoveBodyparts(Entity * my,Stat * myStats,double dist)311 void cockatriceMoveBodyparts(Entity* my, Stat* myStats, double dist)
312 {
313 node_t* node;
314 Entity* entity = nullptr;
315 Entity* leftbody = nullptr;
316 Entity* leftarm = nullptr;
317 Entity* rightarm = nullptr;
318 int bodypart = 0;
319 int limbSpeedMultiplier = 1;
320
321 // set invisibility //TODO: use isInvisible()?
322 if ( multiplayer != CLIENT )
323 {
324 if ( myStats->EFFECTS[EFF_INVISIBLE] == true )
325 {
326 my->flags[INVISIBLE] = true;
327 my->flags[BLOCKSIGHT] = false;
328 bodypart = 0;
329 for (node = my->children.first; node != nullptr; node = node->next)
330 {
331 if ( bodypart < 2 )
332 {
333 bodypart++;
334 continue;
335 }
336 if ( bodypart >= 7 )
337 {
338 break;
339 }
340 entity = (Entity*)node->element;
341 if ( !entity->flags[INVISIBLE] )
342 {
343 entity->flags[INVISIBLE] = true;
344 serverUpdateEntityBodypart(my, bodypart);
345 }
346 bodypart++;
347 }
348 }
349 else
350 {
351 my->flags[INVISIBLE] = false;
352 my->flags[BLOCKSIGHT] = true;
353 bodypart = 0;
354 for (node = my->children.first; node != nullptr; node = node->next)
355 {
356 if ( bodypart < 2 )
357 {
358 bodypart++;
359 continue;
360 }
361 if ( bodypart >= 7 )
362 {
363 break;
364 }
365 entity = (Entity*)node->element;
366 if ( entity->flags[INVISIBLE] )
367 {
368 entity->flags[INVISIBLE] = false;
369 serverUpdateEntityBodypart(my, bodypart);
370 serverUpdateEntityFlag(my, INVISIBLE);
371 }
372 bodypart++;
373 }
374 }
375
376 // sleeping
377 if ( myStats->EFFECTS[EFF_ASLEEP] )
378 {
379 my->pitch = PI / 4;
380 }
381 else
382 {
383 if ( MONSTER_ATTACK != MONSTER_POSE_MAGIC_WINDUP2 && MONSTER_ATTACK != MONSTER_POSE_MELEE_WINDUP3 && my->monsterAnimationLimbOvershoot == ANIMATE_OVERSHOOT_NONE )
384 {
385 my->pitch = 0; // dont adjust head when attacking
386 }
387 }
388
389 // cockatrices are always flying
390 myStats->EFFECTS[EFF_LEVITATING] = true;
391 myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
392 }
393
394 //Move bodyparts
395 for (bodypart = 0, node = my->children.first; node != nullptr; node = node->next, bodypart++)
396 {
397 if ( bodypart < 2 )
398 {
399 if ( bodypart == 1 && multiplayer != CLIENT ) // only trigger once per loop, skip bodypart 0
400 {
401 if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 )
402 {
403 if ( my->monsterAnimationLimbOvershoot >= ANIMATE_OVERSHOOT_TO_SETPOINT )
404 {
405 // handle z movement on windup
406 limbAnimateWithOvershoot(my, ANIMATE_Z, 0.2, -3.5, 0.05, -5.5, ANIMATE_DIR_POSITIVE); // default z is -4.5 in actmonster.cpp
407 }
408 }
409 else if(MONSTER_ATTACK != MONSTER_POSE_MELEE_WINDUP3 )
410 {
411 // post-swing head animation. client doesn't need to adjust the entity pitch, server will handle.
412 limbAnimateWithOvershoot(my, ANIMATE_PITCH, 0.2, PI / 4, 0.1, 0, ANIMATE_DIR_POSITIVE);
413 limbAnimateToLimit(my, ANIMATE_Z, 0.2, -4.5, false, 0);
414 }
415 }
416
417 continue;
418 }
419 entity = (Entity*)node->element;
420 entity->x = my->x;
421 entity->y = my->y;
422 entity->z = my->z;
423 entity->yaw = my->yaw;
424 if ( bodypart == 3 || bodypart == 6 )
425 {
426 // right leg, left arm.
427 if ( bodypart == 3 )
428 {
429 // set leftbody to the left leg.
430 leftbody = (Entity*)node->next->element;
431 }
432 if ( bodypart == 3 || !MONSTER_ATTACK )
433 {
434 if ( (MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 || MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP3) && bodypart == 3 )
435 {
436 limbSpeedMultiplier = 4;
437 }
438 else
439 {
440 limbSpeedMultiplier = 1;
441 }
442
443 if ( !leftbody->skill[0] )
444 {
445 entity->pitch -= COCKATRICEWALKSPEED * limbSpeedMultiplier;
446 if ( entity->pitch < -PI / 8.0 )
447 {
448 entity->pitch = -PI / 8.0;
449 if ( bodypart == 3 )
450 {
451 entity->skill[0] = 1;
452 }
453 }
454 }
455 else
456 {
457 entity->pitch += COCKATRICEWALKSPEED * limbSpeedMultiplier;
458 if ( entity->pitch > PI / 8.0 )
459 {
460 entity->pitch = PI / 8.0;
461 if ( bodypart == 3 )
462 {
463 entity->skill[0] = 0;
464 }
465 }
466 }
467 }
468 else
469 {
470 rightarm = (Entity*)node->prev->element;
471 // ATTACK!
472 // move left arm
473
474 // vertical chop windup
475 if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP1 )
476 {
477 if ( MONSTER_ATTACKTIME == 0 )
478 {
479 // init rotations
480 entity->pitch = 0;
481 entity->roll = 0;
482 }
483
484 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0);
485 entity->skill[0] = 0;
486
487 if ( MONSTER_ATTACKTIME >= ANIMATE_DURATION_WINDUP )
488 {
489 if ( multiplayer != CLIENT )
490 {
491 my->attack(1, 0, nullptr);
492 }
493 }
494 }
495 // vertical chop attack
496 else if ( MONSTER_ATTACK == 1 )
497 {
498 if ( MONSTER_ATTACKTIME > 0 )
499 {
500 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.3, PI / 3, false, 0.0) == 1 )
501 {
502 entity->skill[0] = leftbody->skill[0];
503 entity->pitch = leftbody->pitch;
504 MONSTER_ATTACK = 0;
505 }
506 }
507 }
508 // horizontal chop windup
509 if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP2 )
510 {
511 if ( MONSTER_ATTACKTIME == 0 )
512 {
513 // init rotations
514 entity->pitch = 0;
515 entity->roll = 0;
516 }
517
518 // get rightarm from bodypart 5 element if ready to attack
519 // vertical chop
520 if ( rightarm->pitch > PI && rightarm->pitch < 7 * PI / 4 )
521 {
522 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 6 * PI / 4, false, 0);
523 entity->skill[0] = 0;
524 }
525 }
526 // horizontal chop attack
527 else if ( MONSTER_ATTACK == 2 )
528 {
529 //limbAnimateToLimit(entity, ANIMATE_PITCH, 0.3, 0, false, 0.0);
530 if ( MONSTER_ATTACKTIME > 0 )
531 {
532 if ( entity->skill[0] == 0 )
533 {
534 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.25, PI / 3, false, 0) )
535 {
536 entity->skill[0] = 1;
537 }
538 }
539 else if ( entity->skill[0] == 1 )
540 {
541 entity->pitch = rightarm->pitch;
542 }
543 }
544 }
545 // double attack windup
546 else if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP3 )
547 {
548 if ( MONSTER_ATTACKTIME == 0 )
549 {
550 // init rotations
551 entity->pitch = 0;
552 entity->roll = 0;
553 playSoundEntityLocal(my, 383, 128);
554 createParticleDot(my);
555 if ( multiplayer != CLIENT )
556 {
557 // cockatrice can't be paralyzed, use EFF_STUNNED instead.
558 myStats->EFFECTS[EFF_STUNNED] = true;
559 myStats->EFFECTS_TIMERS[EFF_STUNNED] = 20;
560 }
561 entity->skill[0] = 0;
562 }
563 else if ( MONSTER_ATTACKTIME > 20 )
564 {
565 entity->skill[0] = 2;
566 }
567
568 switch ( entity->skill[0] )
569 {
570 case 0:
571 // swing forwards.
572 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 7 * PI / 4, false, 0) == 1 )
573 {
574 entity->skill[0] = 1;
575 }
576 break;
577 case 1:
578 // move backwards, shake at endpoint
579 // move the head.
580 limbAnimateToLimit(my, ANIMATE_PITCH, -0.1, 11 * PI / 6, true, 0.05);
581 limbAnimateToLimit(entity, ANIMATE_PITCH, 0.1, PI / 8, true, 0.05);
582 break;
583 case 2:
584 // start attack swing
585 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.4, 5 * PI / 4, false, 0) == 1 )
586 {
587 //playSoundEntityLocal(my, 79, 128);
588 if ( multiplayer != CLIENT )
589 {
590 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
591 my->attack(MONSTER_POSE_COCKATRICE_DOUBLEATTACK, 0, nullptr);
592 }
593 }
594 break;
595 default:
596 break;
597 }
598 }
599 // double attack
600 else if ( MONSTER_ATTACK == MONSTER_POSE_COCKATRICE_DOUBLEATTACK )
601 {
602 switch ( entity->skill[0] )
603 {
604 case 2:
605 // swing down
606 entity->roll = 31 * PI / 16;
607 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.5, PI / 3, false, 0) == 1 )
608 {
609 entity->skill[0] = 3;
610 }
611 break;
612 case 3:
613 // raise arms up again
614 entity->roll = 0;
615 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.4, 5 * PI / 4, false, 0) == 1 )
616 {
617 entity->roll = 31 * PI / 16;
618 if ( multiplayer != CLIENT )
619 {
620 my->attack(3, 0, nullptr);
621 }
622 }
623 break;
624 default:
625 break;
626 }
627 MONSTER_ATTACKTIME++; // manually increment counter
628 }
629 else if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 )
630 {
631 if ( MONSTER_ATTACKTIME == 0 )
632 {
633 // init rotations
634 entity->pitch = 0;
635 entity->roll = 0;
636 // set overshoot for z axis animation
637 playSoundEntityLocal(my, 383, 128);
638 createParticleDot(my);
639 if ( multiplayer != CLIENT )
640 {
641 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
642 // cockatrice can't be paralyzed, use EFF_STUNNED instead.
643 myStats->EFFECTS[EFF_STUNNED] = true;
644 myStats->EFFECTS_TIMERS[EFF_STUNNED] = 50;
645 }
646 }
647
648 // only do the following during 2nd + end stage of overshoot animation.
649 if ( my->monsterAnimationLimbOvershoot != ANIMATE_OVERSHOOT_TO_SETPOINT )
650 {
651 // move the head.
652 limbAnimateToLimit(my, ANIMATE_PITCH, -0.1, 11 * PI / 6, true, 0.05);
653
654 // raise left arm and tilt.
655 limbAnimateToLimit(entity, ANIMATE_ROLL, -0.1, 31 * PI / 16, false, 0);
656 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.1, 5 * PI / 4, false, 0);
657 }
658
659 if ( MONSTER_ATTACKTIME > 50 )
660 {
661 // reset roll
662 entity->roll = 0;
663
664 if ( multiplayer != CLIENT )
665 {
666 // set overshoot for head animation
667 my->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
668 my->attack(3, 0, nullptr);
669 }
670 }
671 }
672 // default swing
673 else if ( MONSTER_ATTACK == 3 )
674 {
675 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.3, PI / 3, false, 0.0) == 1 )
676 {
677 entity->skill[0] = leftbody->skill[0];
678 entity->pitch = leftbody->pitch;
679 MONSTER_ATTACK = 0;
680 }
681 }
682 }
683 }
684 else if ( bodypart == 4 || bodypart == 5 )
685 {
686 // right arm
687 if ( bodypart == 5 )
688 {
689 if ( MONSTER_ATTACK > 0 )
690 {
691 // get leftarm from bodypart 6 element if ready to attack
692 leftarm = (Entity*)node->next->element;
693 // vertical chop
694 if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP1 )
695 {
696 if ( MONSTER_ATTACKTIME == 0 )
697 {
698 // init rotations.
699 entity->pitch = 0;
700 entity->roll = 0;
701 }
702 else if ( leftarm->pitch > PI && leftarm->pitch < 7 * PI / 4 )
703 {
704 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0);
705 entity->skill[0] = 0;
706 }
707 }
708 // vertical chop attack
709 else if ( MONSTER_ATTACK == 1 )
710 {
711 if ( MONSTER_ATTACKTIME > 0 )
712 {
713 if ( entity->skill[0] == 0 )
714 {
715 if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.3, PI / 3, false, 0) )
716 {
717 entity->skill[0] = 1;
718 }
719 }
720 else if ( entity->skill[0] == 1 )
721 {
722 entity->pitch = leftarm->pitch;
723 }
724 }
725 }
726
727 else if ( MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP2 )
728 {
729 if ( MONSTER_ATTACKTIME == 0 )
730 {
731 // init rotations
732 entity->pitch = 0;
733 entity->roll = 0;
734 }
735 limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 6 * PI / 4, false, 0);
736 limbAnimateToLimit(entity, ANIMATE_ROLL, -0.1, 31 * PI / 16, false, 0);
737 entity->monsterAnimationLimbOvershoot = ANIMATE_OVERSHOOT_TO_SETPOINT;
738
739 if ( MONSTER_ATTACKTIME >= ANIMATE_DURATION_WINDUP )
740 {
741 if ( multiplayer != CLIENT )
742 {
743 my->attack(2, 0, nullptr);
744 }
745 }
746 }
747 else if ( MONSTER_ATTACK == 2 )
748 {
749 if ( MONSTER_ATTACKTIME > 0 )
750 {
751 limbAnimateToLimit(entity, ANIMATE_PITCH, 0.3, 0, false, 0.0);
752 if ( limbAnimateWithOvershoot(entity, ANIMATE_ROLL, 0.1, PI / 8, 0.05, 0, ANIMATE_DIR_POSITIVE) == ANIMATE_OVERSHOOT_TO_ENDPOINT )
753 {
754 entity->skill[0] = leftbody->skill[0];
755 entity->pitch = leftbody->pitch;
756 entity->roll = 0;
757 MONSTER_ATTACK = 0;
758 }
759 }
760 }
761 else
762 {
763 if ( leftarm != nullptr )
764 {
765 // follow the left arm animation.
766 entity->pitch = leftarm->pitch;
767 entity->roll = -leftarm->roll;
768 }
769 }
770 }
771 else
772 {
773 entity->skill[0] = leftbody->skill[0];
774 entity->pitch = leftbody->pitch;
775 entity->roll = 0;
776 }
777 }
778 // swing right arm/ left leg in sync
779 if ( bodypart != 5 || (MONSTER_ATTACK == 0 && MONSTER_ATTACKTIME == 0) )
780 {
781 if ( entity->skill[0] )
782 {
783 entity->pitch -= COCKATRICEWALKSPEED * limbSpeedMultiplier;
784 if ( entity->pitch < -PI / 8.0 )
785 {
786 entity->skill[0] = 0;
787 entity->pitch = -PI / 8.0;
788 }
789 }
790 else
791 {
792 entity->pitch += COCKATRICEWALKSPEED * limbSpeedMultiplier;
793 if ( entity->pitch > PI / 8.0 )
794 {
795 entity->skill[0] = 1;
796 entity->pitch = PI / 8.0;
797 }
798 }
799 }
800 }
801 // wings
802 else if ( bodypart == 7 || bodypart == 8 )
803 {
804 if ( MONSTER_ATTACK == MONSTER_POSE_MAGIC_WINDUP2 || MONSTER_ATTACK == MONSTER_POSE_MELEE_WINDUP3 )
805 {
806 // flap wings faster during attack.
807 entity->fskill[1] += .4;
808 }
809 else
810 {
811 entity->fskill[1] += .1;
812 }
813 if ( entity->fskill[1] >= PI * 2 )
814 {
815 entity->fskill[1] -= PI * 2;
816 }
817 }
818 switch ( bodypart )
819 {
820 // torso
821 case 2:
822 entity->x -= 2 * cos(my->yaw);
823 entity->y -= 2 * sin(my->yaw);
824 entity->z += 2.75;
825 break;
826 // right leg
827 case 3:
828 entity->x += 1 * cos(my->yaw + PI / 2);
829 entity->y += 1 * sin(my->yaw + PI / 2);
830 entity->z += 6;
831 break;
832 // left leg
833 case 4:
834 entity->x -= 1 * cos(my->yaw + PI / 2);
835 entity->y -= 1 * sin(my->yaw + PI / 2);
836 entity->z += 6;
837 break;
838 // right arm
839 case 5:
840 entity->x += 3 * cos(my->yaw + PI / 2) - 1 * cos(my->yaw);
841 entity->y += 3 * sin(my->yaw + PI / 2) - 1 * sin(my->yaw);
842 entity->z += 1;
843 entity->yaw += MONSTER_WEAPONYAW;
844 break;
845 // left arm
846 case 6:
847 entity->x -= 3 * cos(my->yaw + PI / 2) + 1 * cos(my->yaw);
848 entity->y -= 3 * sin(my->yaw + PI / 2) + 1 * sin(my->yaw);
849 entity->z += 1;
850 break;
851 // right wing
852 case 7:
853 entity->x += 1 * cos(my->yaw + PI / 2) - 2.5 * cos(my->yaw);
854 entity->y += 1 * sin(my->yaw + PI / 2) - 2.5 * sin(my->yaw);
855 entity->z += 1;
856 entity->yaw += cos(entity->fskill[1]) * PI / 6 + PI / 6;
857 break;
858 // left wing
859 case 8:
860 entity->x -= 1 * cos(my->yaw + PI / 2) + 2.5 * cos(my->yaw);
861 entity->y -= 1 * sin(my->yaw + PI / 2) + 2.5 * sin(my->yaw);
862 entity->z += 1;
863 entity->yaw -= cos(entity->fskill[1]) * PI / 6 + PI / 6;
864 break;
865 }
866 }
867 if ( MONSTER_ATTACK > 0 && MONSTER_ATTACK <= MONSTER_POSE_MAGIC_CAST3 )
868 {
869 MONSTER_ATTACKTIME++;
870 }
871 else if ( MONSTER_ATTACK == 0 )
872 {
873 MONSTER_ATTACKTIME = 0;
874 }
875 else
876 {
877 // do nothing, don't reset attacktime or increment it.
878 }
879 }
880