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 }