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