1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: monster_minotaur.cpp
5 	Desc: implements all of the minotaur 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 "magic/magic.hpp"
22 #include "player.hpp"
23 #include "colors.hpp"
24 
initMinotaur(Entity * my,Stat * myStats)25 void initMinotaur(Entity* my, Stat* myStats)
26 {
27 	int c;
28 	node_t* node;
29 
30 	my->initMonster(239);
31 
32 	if ( multiplayer != CLIENT )
33 	{
34 		MONSTER_SPOTSND = 107;
35 		MONSTER_SPOTVAR = 3;
36 		MONSTER_IDLESND = 110;
37 		MONSTER_IDLEVAR = 3;
38 	}
39 	if ( multiplayer != CLIENT && !MONSTER_INIT )
40 	{
41 		if ( myStats != NULL )
42 		{
43 			if ( !myStats->leader_uid )
44 			{
45 				myStats->leader_uid = 0;
46 			}
47 
48 			// apply random stat increases if set in stat_shared.cpp or editor
49 			setRandomMonsterStats(myStats);
50 
51 			// generate 6 items max, less if there are any forced items from boss variants
52 			int customItemsToGenerate = ITEM_CUSTOM_SLOT_LIMIT;
53 
54 			// boss variants
55 			if ( strcmp(map.name, "Hell Boss") == 0 )
56 			{
57 				myStats->STR = 50;
58 				myStats->DEX = 20;
59 				myStats->CON = 20;
60 			}
61 			else if ( currentlevel >= 25 )
62 			{
63 				myStats->HP += 400;
64 				myStats->MAXHP += 400;
65 				myStats->STR = 60;
66 				myStats->DEX = 20;
67 				myStats->CON = 20;
68 				myStats->EFFECTS[EFF_VAMPIRICAURA] = true;
69 				myStats->EFFECTS_TIMERS[EFF_VAMPIRICAURA] = -1;
70 			}
71 
72 
73 			// random effects
74 			// minotaurs can traverse waters and pits (pits with magic :))
75 			myStats->EFFECTS[EFF_LEVITATING] = true;
76 			myStats->EFFECTS_TIMERS[EFF_LEVITATING] = 0;
77 
78 			// generates equipment and weapons if available from editor
79 			createMonsterEquipment(myStats);
80 
81 			// create any custom inventory items from editor if available
82 			createCustomInventory(myStats, customItemsToGenerate);
83 
84 			// count if any custom inventory items from editor
85 			int customItems = countCustomItems(myStats); //max limit of 6 custom items per entity.
86 
87 														 // count any inventory items set to default in edtior
88 			int defaultItems = countDefaultItems(myStats);
89 
90 			my->setHardcoreStats(*myStats);
91 
92 			// generate the default inventory items for the monster, provided the editor sprite allowed enough default slots
93 
94 			ItemType gemtype = GEM_RUBY;
95 
96 			switch ( defaultItems )
97 			{
98 				case 6:
99 				case 5:
100 				case 4:
101 				case 3:
102 				case 2:
103 				case 1:
104 					switch ( rand() % 4 )
105 					{
106 						case 0:
107 							gemtype = GEM_RUBY;
108 							break;
109 						case 1:
110 							gemtype = GEM_EMERALD;
111 							break;
112 						case 2:
113 							gemtype = GEM_SAPPHIRE;
114 							break;
115 						case 3:
116 							gemtype = GEM_DIAMOND;
117 							break;
118 					}
119 					newItem(gemtype, EXCELLENT, 0, 1, rand(), true, &myStats->inventory);
120 					break;
121 				default:
122 					break;
123 			}
124 		}
125 	}
126 
127 	// head
128 	Entity* entity = newEntity(237, 0, map.entities, nullptr); //Limb entity.
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[MINOTAUR][1][0]; // 0
136 	entity->focaly = limbs[MINOTAUR][1][1]; // 0
137 	entity->focalz = limbs[MINOTAUR][1][2]; // 0
138 	entity->behavior = &actMinotaurLimb;
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 	// chest
147 	entity = newEntity(238, 0, map.entities, nullptr); //Limb entity.
148 	entity->sizex = 4;
149 	entity->sizey = 4;
150 	entity->skill[2] = my->getUID();
151 	entity->flags[PASSABLE] = true;
152 	entity->flags[NOUPDATE] = true;
153 	entity->flags[USERFLAG2] = my->flags[USERFLAG2];
154 	entity->focalx = limbs[MINOTAUR][2][0]; // 0
155 	entity->focaly = limbs[MINOTAUR][2][1]; // 0
156 	entity->focalz = limbs[MINOTAUR][2][2]; // 0
157 	entity->behavior = &actMinotaurLimb;
158 	entity->parent = my->getUID();
159 	node = list_AddNodeLast(&my->children);
160 	node->element = entity;
161 	node->deconstructor = &emptyDeconstructor;
162 	node->size = sizeof(Entity*);
163 	my->bodyparts.push_back(entity);
164 
165 	// right leg
166 	entity = newEntity(243, 0, map.entities, nullptr); //Limb entity.
167 	entity->sizex = 4;
168 	entity->sizey = 4;
169 	entity->skill[2] = my->getUID();
170 	entity->flags[PASSABLE] = true;
171 	entity->flags[NOUPDATE] = true;
172 	entity->flags[USERFLAG2] = my->flags[USERFLAG2];
173 	entity->focalx = limbs[MINOTAUR][3][0]; // 1
174 	entity->focaly = limbs[MINOTAUR][3][1]; // 0
175 	entity->focalz = limbs[MINOTAUR][3][2]; // 5
176 	entity->behavior = &actMinotaurLimb;
177 	entity->parent = my->getUID();
178 	node = list_AddNodeLast(&my->children);
179 	node->element = entity;
180 	node->deconstructor = &emptyDeconstructor;
181 	node->size = sizeof(Entity*);
182 	my->bodyparts.push_back(entity);
183 
184 	// left leg
185 	entity = newEntity(242, 0, map.entities, nullptr); //Limb entity.
186 	entity->sizex = 4;
187 	entity->sizey = 4;
188 	entity->skill[2] = my->getUID();
189 	entity->flags[PASSABLE] = true;
190 	entity->flags[NOUPDATE] = true;
191 	entity->flags[USERFLAG2] = my->flags[USERFLAG2];
192 	entity->focalx = limbs[MINOTAUR][4][0]; // 1
193 	entity->focaly = limbs[MINOTAUR][4][1]; // 0
194 	entity->focalz = limbs[MINOTAUR][4][2]; // 5
195 	entity->behavior = &actMinotaurLimb;
196 	entity->parent = my->getUID();
197 	node = list_AddNodeLast(&my->children);
198 	node->element = entity;
199 	node->deconstructor = &emptyDeconstructor;
200 	node->size = sizeof(Entity*);
201 	my->bodyparts.push_back(entity);
202 
203 	// right arm
204 	entity = newEntity(241, 0, map.entities, nullptr); //Limb entity.
205 	entity->sizex = 4;
206 	entity->sizey = 4;
207 	entity->skill[2] = my->getUID();
208 	entity->flags[PASSABLE] = true;
209 	entity->flags[NOUPDATE] = true;
210 	entity->flags[USERFLAG2] = my->flags[USERFLAG2];
211 	entity->focalx = limbs[MINOTAUR][5][0]; // 2.5
212 	entity->focaly = limbs[MINOTAUR][5][1]; // 7
213 	entity->focalz = limbs[MINOTAUR][5][2]; // 3.5
214 	entity->behavior = &actMinotaurLimb;
215 	entity->parent = my->getUID();
216 	node = list_AddNodeLast(&my->children);
217 	node->element = entity;
218 	node->deconstructor = &emptyDeconstructor;
219 	node->size = sizeof(Entity*);
220 	my->bodyparts.push_back(entity);
221 
222 	// left arm
223 	entity = newEntity(240, 0, map.entities, nullptr); //Limb entity.
224 	entity->sizex = 4;
225 	entity->sizey = 4;
226 	entity->skill[2] = my->getUID();
227 	entity->flags[PASSABLE] = true;
228 	entity->flags[NOUPDATE] = true;
229 	entity->flags[USERFLAG2] = my->flags[USERFLAG2];
230 	entity->focalx = limbs[MINOTAUR][6][0]; // 2.5
231 	entity->focaly = limbs[MINOTAUR][6][1]; // -7
232 	entity->focalz = limbs[MINOTAUR][6][2]; // 3.5
233 	entity->behavior = &actMinotaurLimb;
234 	entity->parent = my->getUID();
235 	node = list_AddNodeLast(&my->children);
236 	node->element = entity;
237 	node->deconstructor = &emptyDeconstructor;
238 	node->size = sizeof(Entity*);
239 	my->bodyparts.push_back(entity);
240 }
241 
actMinotaurLimb(Entity * my)242 void actMinotaurLimb(Entity* my)
243 {
244 	my->actMonsterLimb();
245 }
246 
minotaurDie(Entity * my)247 void minotaurDie(Entity* my)
248 {
249 	int c;
250 	for ( c = 0; c < 5; c++ )
251 	{
252 		Entity* gib = spawnGib(my);
253 		serverSpawnGibForClient(gib);
254 	}
255 
256 	my->spawnBlood();
257 
258 	for ( c = 0; c < MAXPLAYERS; c++ )
259 	{
260 		playSoundPlayer(c, 114, 128);
261 	}
262 
263 	my->removeMonsterDeathNodes();
264 
265 	list_RemoveNode(my->mynode);
266 	return;
267 }
268 
269 #define MINOTAURWALKSPEED .07
270 
minotaurMoveBodyparts(Entity * my,Stat * myStats,double dist)271 void minotaurMoveBodyparts(Entity* my, Stat* myStats, double dist)
272 {
273 	node_t* node;
274 	Entity* entity = NULL;
275 	Entity* rightbody = NULL;
276 	Entity* head = NULL;
277 	Entity* chest = NULL;
278 	int bodypart;
279 
280 	// set invisibility //TODO: isInvisible()?
281 	if ( multiplayer != CLIENT )
282 	{
283 		if ( myStats->EFFECTS[EFF_INVISIBLE] == true )
284 		{
285 			my->flags[INVISIBLE] = true;
286 			my->flags[BLOCKSIGHT] = false;
287 			bodypart = 0;
288 			for (node = my->children.first; node != NULL; node = node->next)
289 			{
290 				if ( bodypart < 2 )
291 				{
292 					bodypart++;
293 					continue;
294 				}
295 				if ( bodypart >= 7 )
296 				{
297 					break;
298 				}
299 				entity = (Entity*)node->element;
300 				if ( !entity->flags[INVISIBLE] )
301 				{
302 					entity->flags[INVISIBLE] = true;
303 					serverUpdateEntityBodypart(my, bodypart);
304 				}
305 				bodypart++;
306 			}
307 		}
308 		else
309 		{
310 			my->flags[INVISIBLE] = false;
311 			my->flags[BLOCKSIGHT] = true;
312 			bodypart = 0;
313 			for (node = my->children.first; node != NULL; node = node->next)
314 			{
315 				if ( bodypart < 2 )
316 				{
317 					bodypart++;
318 					continue;
319 				}
320 				if ( bodypart >= 7 )
321 				{
322 					break;
323 				}
324 				entity = (Entity*)node->element;
325 				if ( entity->flags[INVISIBLE] )
326 				{
327 					entity->flags[INVISIBLE] = false;
328 					serverUpdateEntityBodypart(my, bodypart);
329 					serverUpdateEntityFlag(my, INVISIBLE);
330 				}
331 				bodypart++;
332 			}
333 		}
334 	}
335 
336 	//Move bodyparts
337 	for (bodypart = 0, node = my->children.first; node != NULL; node = node->next, bodypart++)
338 	{
339 		if ( bodypart < 2 )
340 		{
341 			continue;
342 		}
343 		entity = (Entity*)node->element;
344 		entity->x = my->x;
345 		entity->y = my->y;
346 		entity->z = my->z;
347 		entity->yaw = my->yaw;
348 		if ( bodypart == 2 )
349 		{
350 			head = entity;
351 		}
352 		else if ( bodypart == 3 )
353 		{
354 			chest = entity;
355 		}
356 		if ( bodypart == 4 || bodypart == 7 )
357 		{
358 			if ( bodypart == 4 )
359 			{
360 				rightbody = (Entity*)node->next->element;
361 			}
362 			if ( dist > 0.1 )
363 			{
364 				if ( !rightbody->skill[0] )
365 				{
366 					entity->pitch -= dist * MINOTAURWALKSPEED;
367 					if ( entity->pitch < -PI / 4.0 )
368 					{
369 						entity->pitch = -PI / 4.0;
370 						if (bodypart == 4 && dist > .4)
371 						{
372 							playSound(115, 128);
373 						}
374 					}
375 				}
376 				else
377 				{
378 					entity->pitch += dist * MINOTAURWALKSPEED;
379 					if ( entity->pitch > PI / 4.0 )
380 					{
381 						entity->pitch = PI / 4.0;
382 						if (bodypart == 4 && dist > .4)
383 						{
384 							playSound(115, 128);
385 						}
386 					}
387 				}
388 			}
389 			else
390 			{
391 				if ( entity->pitch < 0 )
392 				{
393 					entity->pitch += 1 / fmax(dist * .1, 10.0);
394 					if ( entity->pitch > 0 )
395 					{
396 						entity->pitch = 0;
397 					}
398 				}
399 				else if ( entity->pitch > 0 )
400 				{
401 					entity->pitch -= 1 / fmax(dist * .1, 10.0);
402 					if ( entity->pitch < 0 )
403 					{
404 						entity->pitch = 0;
405 					}
406 				}
407 			}
408 		}
409 		else if ( bodypart == 5 || bodypart == 6 )
410 		{
411 			if ( bodypart == 6 )
412 			{
413 				if ( my->monsterAttack > 0 )
414 				{
415 					// vertical chop windup
416 					if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP1 )
417 					{
418 						if ( my->monsterAttackTime == 0 )
419 						{
420 							// init rotations
421 							entity->pitch = 0;
422 							my->monsterArmbended = 0;
423 							my->monsterWeaponYaw = 0;
424 							entity->roll = 0;
425 							entity->skill[1] = 0;
426 						}
427 
428 						limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
429 
430 						if ( my->monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
431 						{
432 							if ( multiplayer != CLIENT )
433 							{
434 								my->attack(1, 0, nullptr);
435 							}
436 						}
437 					}
438 					// ceiling buster chop windup
439 					if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2 )
440 					{
441 						if ( my->monsterAttackTime == 0 )
442 						{
443 							// init rotations
444 							entity->pitch = 0;
445 							my->monsterArmbended = 0;
446 							my->monsterWeaponYaw = 0;
447 							entity->roll = 0;
448 							entity->skill[1] = 0;
449 						}
450 
451 						limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 5 * PI / 4, false, 0.0);
452 
453 						if ( my->monsterAttackTime >= ANIMATE_DURATION_WINDUP / (monsterGlobalAnimationMultiplier / 10.0) )
454 						{
455 							my->monsterAttack = 1;
456 						}
457 					}
458 					// vertical chop attack
459 					else if ( my->monsterAttack == 1 )
460 					{
461 						if ( entity->pitch >= 3 * PI / 2 )
462 						{
463 							my->monsterArmbended = 1;
464 						}
465 
466 						if ( entity->skill[1] == 0 )
467 						{
468 							// chop forwards
469 							if ( limbAnimateToLimit(entity, ANIMATE_PITCH, 0.4, PI / 3, false, 0.0) )
470 							{
471 								entity->skill[1] = 1;
472 							}
473 						}
474 						else if ( entity->skill[1] == 1 )
475 						{
476 							// return to neutral
477 							if ( limbAnimateToLimit(entity, ANIMATE_PITCH, -0.25, 7 * PI / 4, false, 0.0) )
478 							{
479 								entity->skill[0] = rightbody->skill[0];
480 								my->monsterWeaponYaw = 0;
481 								entity->pitch = rightbody->pitch;
482 								entity->roll = 0;
483 								my->monsterArmbended = 0;
484 								my->monsterAttack = 0;
485 							}
486 						}
487 					}
488 				}
489 			}
490 
491 			if ( bodypart != 6 || (MONSTER_ATTACK == 0 && MONSTER_ATTACKTIME == 0) )
492 			{
493 				if ( dist > 0.1 )
494 				{
495 					if ( entity->skill[0] )
496 					{
497 						entity->pitch -= dist * MINOTAURWALKSPEED;
498 						if ( entity->pitch < -PI / 4.0 )
499 						{
500 							entity->skill[0] = 0;
501 							entity->pitch = -PI / 4.0;
502 						}
503 					}
504 					else
505 					{
506 						entity->pitch += dist * MINOTAURWALKSPEED;
507 						if ( entity->pitch > PI / 4.0 )
508 						{
509 							entity->skill[0] = 1;
510 							entity->pitch = PI / 4.0;
511 						}
512 					}
513 				}
514 				else
515 				{
516 					if ( entity->pitch < 0 )
517 					{
518 						entity->pitch += 1 / fmax(dist * .1, 10.0);
519 						if ( entity->pitch > 0 )
520 						{
521 							entity->pitch = 0;
522 						}
523 					}
524 					else if ( entity->pitch > 0 )
525 					{
526 						entity->pitch -= 1 / fmax(dist * .1, 10.0);
527 						if ( entity->pitch < 0 )
528 						{
529 							entity->pitch = 0;
530 						}
531 					}
532 				}
533 			}
534 		}
535 		if ( !MONSTER_ATTACK )
536 		{
537 			if ( bodypart == 6 )
538 			{
539 				entity->yaw += entity->pitch;
540 				head->yaw = entity->yaw;
541 				chest->yaw = entity->yaw;
542 			}
543 			else if ( bodypart == 7 )
544 			{
545 				entity->yaw -= entity->pitch;
546 			}
547 		}
548 		switch ( bodypart )
549 		{
550 			// head
551 			case 2:
552 				entity->z -= 11;
553 				break;
554 			// chest
555 			case 3:
556 				entity->z -= 4.5;
557 				break;
558 			// right leg
559 			case 4:
560 				entity->x += 2.5 * cos(my->yaw + PI / 2) - .5 * cos(my->yaw);
561 				entity->y += 2.5 * sin(my->yaw + PI / 2) - .5 * sin(my->yaw);
562 				entity->z += 2.5;
563 				break;
564 			// left leg
565 			case 5:
566 				entity->x -= 2.5 * cos(my->yaw + PI / 2) + .5 * cos(my->yaw);
567 				entity->y -= 2.5 * sin(my->yaw + PI / 2) + .5 * sin(my->yaw);
568 				entity->z += 2.5;
569 				break;
570 			// right arm
571 			case 6:
572 				entity->z -= 6;
573 				entity->yaw += MONSTER_WEAPONYAW;
574 				break;
575 			// left arm
576 			case 7:
577 				entity->z -= 6;
578 				break;
579 		}
580 	}
581 	if ( MONSTER_ATTACK != 0 )
582 	{
583 		MONSTER_ATTACKTIME++;
584 	}
585 	else
586 	{
587 		MONSTER_ATTACKTIME = 0;
588 	}
589 }
590 
591 /*-------------------------------------------------------------------------------
592 
593 	act*
594 
595 	The following function describes an entity behavior. The function
596 	takes a pointer to the entity that uses it as an argument.
597 
598 -------------------------------------------------------------------------------*/
599 
600 #define MINOTAURTRAP_FIRED my->skill[0]
601 
actMinotaurTrap(Entity * my)602 void actMinotaurTrap(Entity* my)
603 {
604 	if ( !my->skill[28] )
605 	{
606 		return;
607 	}
608 
609 	// received on signal
610 	if ( my->skill[28] == 2)
611 	{
612 		if ( !MINOTAURTRAP_FIRED )
613 		{
614 			Entity* monster = summonMonster(MINOTAUR, my->x, my->y);
615 			if ( monster )
616 			{
617 				MINOTAURTRAP_FIRED = 1;
618 				if ( strcmp(map.name, "Hell Boss") )
619 				{
620 					int c;
621 					for ( c = 0; c < MAXPLAYERS; c++ )
622 					{
623 						playSoundPlayer( c, 107 + rand() % 3, 128 );
624 						Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
625 						messagePlayerColor(c, color, language[1113]);
626 					}
627 				}
628 			}
629 		}
630 	}
631 }
632 
633 #define MINOTAURTIMER_LIFE my->skill[0]
634 #define MINOTAURTIMER_ACTIVE my->skill[1]
635 
actMinotaurTimer(Entity * my)636 void actMinotaurTimer(Entity* my)
637 {
638 	node_t* node;
639 
640 	MINOTAURTIMER_LIFE++;
641 	if (( (currentlevel < 25 && MINOTAURTIMER_LIFE == TICKS_PER_SECOND * 120)
642 			|| (currentlevel >= 25 && MINOTAURTIMER_LIFE == TICKS_PER_SECOND * 180)
643 		)
644 		&& rand() % 5 == 0 )   // two minutes if currentlevel < 25, else 3 minutes.
645 	{
646 		int c;
647 		bool spawnedsomebody = false;
648 		for ( c = 0; c < 9; c++ )
649 		{
650 			Uint32 zapLeaderUid = 0;
651 			Entity* monster = summonMonster(HUMAN, my->x, my->y);
652 			if ( monster )
653 			{
654 				monster->skill[29] = 1; // so we spawn a zap brigadier
655 				spawnedsomebody = true;
656 				if ( !zapLeaderUid )
657 				{
658 					zapLeaderUid = monster->getUID();
659 				}
660 				else
661 				{
662 					Stat* monsterStats = monster->getStats();
663 					monsterStats->leader_uid = zapLeaderUid;
664 				}
665 			}
666 		}
667 
668 		if ( spawnedsomebody )
669 		{
670 #ifdef MUSIC
671 			fadein_increment = default_fadein_increment * 20;
672 			fadeout_increment = default_fadeout_increment * 5;
673 			playmusic( sounds[175], false, true, false);
674 #endif
675 			for ( c = 0; c < MAXPLAYERS; c++ )
676 			{
677 				Uint32 color = SDL_MapRGB(mainsurface->format, 0, 255, 255);
678 				if ( stats[c]->type == HUMAN )
679 				{
680 					messagePlayerColor(c, color, language[1114], stats[c]->name);
681 				}
682 				else
683 				{
684 					messagePlayerColor(c, color, language[3285]);
685 				}
686 			}
687 		}
688 	}
689 	else if (( (currentlevel < 25 && MINOTAURTIMER_LIFE >= TICKS_PER_SECOND * 150)
690 					|| (currentlevel >= 25 && MINOTAURTIMER_LIFE >= TICKS_PER_SECOND * 210)
691 				)
692 		&& !MINOTAURTIMER_ACTIVE )     // two and a half minutes if currentlevel < 25, else 3.5 minutes
693 	{
694 		Entity* monster = summonMonster(MINOTAUR, my->x, my->y);
695 		if ( monster )
696 		{
697 			int c;
698 			for ( c = 0; c < MAXPLAYERS; c++ )
699 			{
700 				playSoundPlayer( c, 107 + rand() % 3, 128 );
701 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
702 				messagePlayerColor(c, color, language[1115]);
703 			}
704 			MINOTAURTIMER_ACTIVE = MINOTAURTIMER_LIFE;
705 		}
706 	}
707 	if ( MINOTAURTIMER_ACTIVE && MINOTAURTIMER_LIFE >= MINOTAURTIMER_ACTIVE + TICKS_PER_SECOND * 3 )
708 	{
709 		int c;
710 		for ( c = 0; c < MAXPLAYERS; c++ )
711 		{
712 			if ( currentlevel < 25 )
713 			{
714 				playSoundPlayer(c, 120 + rand() % 3, 128);
715 				Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
716 				messagePlayerColor(c, color, language[1116]);
717 				messagePlayerColor(c, color, language[73]);
718 			}
719 			else
720 			{
721 				playSoundPlayer(c, 375, 128);
722 				playSoundPlayer(c, 379, 128);
723 				messagePlayerColor(c, uint32ColorOrange(*mainsurface), language[1116]);
724 				messagePlayerColor(c, uint32ColorOrange(*mainsurface), language[73]);
725 				messagePlayerColor(c, uint32ColorBaronyBlue(*mainsurface), language[73]);
726 			}
727 		}
728 		list_RemoveNode(my->mynode);
729 		return;
730 	}
731 }
732 
actMinotaurCeilingBuster(Entity * my)733 void actMinotaurCeilingBuster(Entity* my)
734 {
735 	double x, y;
736 
737 	// levitate particles
738 	int u = std::min<unsigned int>(std::max<int>(0, my->x / 16), map.width - 1);
739 	int v = std::min<unsigned int>(std::max<int>(0, my->y / 16), map.height - 1);
740 	if ( !map.tiles[v * MAPLAYERS + u * MAPLAYERS * map.height] )
741 	{
742 		int c;
743 		for ( c = 0; c < 2; c++ )
744 		{
745 			Entity* entity = newEntity(171, 1, map.entities, nullptr); //Particle entity.
746 			entity->x = my->x - 8 + rand() % 17;
747 			entity->y = my->y - 8 + rand() % 17;
748 			entity->z = 10 + rand() % 3;
749 			entity->scalex = 0.7;
750 			entity->scaley = 0.7;
751 			entity->scalez = 0.7;
752 			entity->sizex = 1;
753 			entity->sizey = 1;
754 			entity->yaw = (rand() % 360) * PI / 180.f;
755 			entity->flags[PASSABLE] = true;
756 			entity->flags[BRIGHT] = true;
757 			entity->flags[NOUPDATE] = true;
758 			entity->flags[UNCLICKABLE] = true;
759 			entity->behavior = &actMagicParticle;
760 			if ( multiplayer != CLIENT )
761 			{
762 				entity_uids--;
763 			}
764 			entity->setUID(-3);
765 		}
766 	}
767 
768 	// bust ceilings
769 	for ( x = my->x - my->sizex - 1; x <= my->x + my->sizex + 1; x += 1 )
770 	{
771 		for ( y = my->y - my->sizey - 1; y <= my->y + my->sizey + 1; y += 1 )
772 		{
773 			if ( x >= 0 && y >= 0 && x < map.width << 4 && y < map.height << 4 )
774 			{
775 				int index = (MAPLAYERS - 1) + ((int)floor(y / 16)) * MAPLAYERS + ((int)floor(x / 16)) * MAPLAYERS * map.height;
776 				if ( map.tiles[index] )
777 				{
778 					if ( my->monsterAttack == 0 )
779 					{
780 						if ( multiplayer != CLIENT )
781 						{
782 							my->attack(MONSTER_POSE_MELEE_WINDUP2, 0, nullptr);
783 						}
784 						return;
785 					}
786 					else if ( my->monsterAttack == MONSTER_POSE_MELEE_WINDUP2 )
787 					{
788 						return;
789 					}
790 					map.tiles[index] = 0;
791 					if ( multiplayer != CLIENT )
792 					{
793 						playSoundEntity(my, 67, 128);
794 						//MONSTER_ATTACK = 1;
795 						Stat* myStats = my->getStats();
796 						if ( myStats )
797 						{
798 							// easy hack to stop the minotaur while he breaks stuff
799 							myStats->EFFECTS[EFF_PARALYZED] = true;
800 							myStats->EFFECTS_TIMERS[EFF_PARALYZED] = 10;
801 						}
802 					}
803 
804 					// spawn several rock particles (NOT items)
805 					int c, i = 6 + rand() % 4;
806 					for ( c = 0; c < i; c++ )
807 					{
808 						Entity *entity = nullptr;
809 						if ( multiplayer == SERVER )
810 						{
811 							entity = spawnGib(my);
812 						}
813 						else
814 						{
815 							entity = spawnGibClient(my->x, my->y, my->z, 5);
816 						}
817 						if ( entity )
818 						{
819 							entity->x = ((int)(my->x / 16)) * 16 + rand() % 16;
820 							entity->y = ((int)(my->y / 16)) * 16 + rand() % 16;
821 							entity->z = -8;
822 							entity->flags[PASSABLE] = true;
823 							entity->flags[INVISIBLE] = false;
824 							entity->flags[NOUPDATE] = true;
825 							entity->flags[UPDATENEEDED] = false;
826 							entity->sprite = items[GEM_ROCK].index;
827 							entity->yaw = rand() % 360 * PI / 180;
828 							entity->pitch = rand() % 360 * PI / 180;
829 							entity->roll = rand() % 360 * PI / 180;
830 							entity->vel_x = (rand() % 20 - 10) / 10.0;
831 							entity->vel_y = (rand() % 20 - 10) / 10.0;
832 							entity->vel_z = -.25;
833 							entity->fskill[3] = 0.03;
834 						}
835 					}
836 				}
837 				node_t* node, *nextnode;
838 				std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
839 				for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
840 				{
841 					list_t* currentList = *it;
842 					for ( node = currentList->first; node != nullptr; node = nextnode )
843 					{
844 						nextnode = node->next;
845 						Entity* entity = (Entity*)node->element;
846 						if ( (int)(x / 16) == (int)(entity->x / 16) && (int)(y / 16) == (int)(entity->y / 16) )
847 						{
848 							if ( entity->behavior == &actDoorFrame )
849 							{
850 								// spawn several rock items
851 								int c, i = 8 + rand() % 4;
852 								for ( c = 0; c < i; c++ )
853 								{
854 									Entity *entity = nullptr;
855 									if ( multiplayer == SERVER )
856 									{
857 										entity = spawnGib(my);
858 									}
859 									else
860 									{
861 										entity = spawnGibClient(my->x, my->y, my->z, 5);
862 									}
863 									if ( entity )
864 									{
865 										entity->x = ((int)(my->x / 16)) * 16 + rand() % 16;
866 										entity->y = ((int)(my->y / 16)) * 16 + rand() % 16;
867 										entity->z = -8;
868 										entity->flags[PASSABLE] = true;
869 										entity->flags[INVISIBLE] = false;
870 										entity->flags[NOUPDATE] = true;
871 										entity->flags[UPDATENEEDED] = false;
872 										entity->sprite = items[GEM_ROCK].index;
873 										entity->yaw = rand() % 360 * PI / 180;
874 										entity->pitch = rand() % 360 * PI / 180;
875 										entity->roll = rand() % 360 * PI / 180;
876 										entity->vel_x = (rand() % 20 - 10) / 10.0;
877 										entity->vel_y = (rand() % 20 - 10) / 10.0;
878 										entity->vel_z = -.25;
879 										entity->fskill[3] = 0.03;
880 									}
881 								}
882 								list_RemoveNode(entity->mynode);
883 							}
884 							else if ( entity->behavior == &actDoor )
885 							{
886 								if ( multiplayer != CLIENT )
887 								{
888 									entity->skill[4] = 0; // destroy the door
889 								}
890 							}
891 							else if ( entity->behavior == &actGate )
892 							{
893 								if ( multiplayer != CLIENT )
894 								{
895 									playSoundEntity(entity, 76, 64);
896 									list_RemoveNode(entity->mynode);
897 								}
898 							}
899 							else if (	entity->behavior == &actStalagCeiling	||
900 										entity->behavior == &actStalagFloor		||
901 										entity->behavior == &actStalagColumn
902 									)
903 							{
904 								// spawn several rock items
905 								int c, i = rand() % 4;
906 								for ( c = 0; c < i; ++c )
907 								{
908 									//Entity* childEntity = spawnGib(my);
909 									Entity *childEntity = nullptr;
910 									if ( multiplayer == SERVER )
911 									{
912 										childEntity = spawnGib(my);
913 									}
914 									else
915 									{
916 										childEntity = spawnGibClient(my->x, my->y, my->z, 5);
917 									}
918 									if ( entity )
919 									{
920 										childEntity->x = ((int)(my->x / 16)) * 16 + rand() % 16;
921 										childEntity->y = ((int)(my->y / 16)) * 16 + rand() % 16;
922 										childEntity->z = -8;
923 										childEntity->flags[PASSABLE] = true;
924 										childEntity->flags[INVISIBLE] = false;
925 										childEntity->flags[NOUPDATE] = true;
926 										childEntity->flags[UPDATENEEDED] = false;
927 										childEntity->sprite = items[GEM_ROCK].index;
928 										childEntity->yaw = rand() % 360 * PI / 180;
929 										childEntity->pitch = rand() % 360 * PI / 180;
930 										childEntity->roll = rand() % 360 * PI / 180;
931 										childEntity->vel_x = (rand() % 20 - 10) / 10.0;
932 										childEntity->vel_y = (rand() % 20 - 10) / 10.0;
933 										childEntity->vel_z = -.25;
934 										childEntity->fskill[3] = 0.03;
935 									}
936 								}
937 								list_RemoveNode(entity->mynode);
938 							}
939 						}
940 					}
941 				}
942 			}
943 		}
944 	}
945 }
946 
createMinotaurTimer(Entity * entity,map_t * map)947 void createMinotaurTimer(Entity* entity, map_t* map)
948 {
949 	Entity* childEntity = newEntity(37, 0, map->entities, nullptr); //Timer entity.
950 	childEntity->sizex = 2;
951 	childEntity->sizey = 2;
952 	childEntity->x = entity->x;
953 	childEntity->y = entity->y;
954 	childEntity->behavior = &actMinotaurTimer;
955 	childEntity->flags[SPRITE] = true;
956 	childEntity->flags[INVISIBLE] = true;
957 	childEntity->flags[PASSABLE] = true;
958 	childEntity->flags[NOUPDATE] = true;
959 	childEntity->setUID(-3);
960 	entity_uids--;
961 }
962