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