1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: actboulder.cpp
5 	Desc: implements boulder and boulder trap 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 "sound.hpp"
17 #include "items.hpp"
18 #include "net.hpp"
19 #include "monster.hpp"
20 #include "collision.hpp"
21 #include "player.hpp"
22 #include "magic/magic.hpp"
23 #include "paths.hpp"
24 #include "scores.hpp"
25 #include "mod_tools.hpp"
26 
27 #define BOULDER_STOPPED my->skill[0]
28 #define BOULDER_AMBIENCE my->skill[1]
29 #define BOULDER_NOGROUND my->skill[3]
30 #define BOULDER_ROLLING my->skill[4]
31 #define BOULDER_ROLLDIR my->skill[5]
32 #define BOULDER_DESTX my->skill[6]
33 #define BOULDER_DESTY my->skill[7]
34 #define BOULDER_PLAYERPUSHED my->skill[8]
35 #define BOULDER_SPAWNBLOOD my->skill[9]
36 #define BOULDER_BLOODTIME my->skill[10]
37 #define BOULDER_INIT my->skill[11]
38 #define BOULDER_LAVA_EXPLODE my->skill[12]
39 #define BOULDER_SOUND_ON_PUSH my->skill[13]
40 
41 const int BOULDER_LAVA_SPRITE = 989;
42 const int BOULDER_ARCANE_SPRITE = 990;
43 
boulderCheckIfBlockedExit(Entity * my)44 bool boulderCheckIfBlockedExit(Entity* my)
45 {
46 	if ( conductGameChallenges[CONDUCT_MODDED] )
47 	{
48 		return true; // ignore for custom maps.
49 	}
50 	if ( gameModeManager.getMode() != GameModeManager_t::GAME_MODE_DEFAULT )
51 	{
52 		return true; // ignore for custom modes.
53 	}
54 
55 	bool playerAlive = false;
56 	for ( int c = 0; c < MAXPLAYERS; ++c )
57 	{
58 		if ( players[c] && players[c]->entity )
59 		{
60 			playerAlive = true;
61 		}
62 	}
63 	if ( !playerAlive )
64 	{
65 		return true;
66 	}
67 	// check if this blocked the exit.
68 	for ( node_t* node = map.entities->first; node != nullptr; node = node->next )
69 	{
70 		Entity* ladder = (Entity*)node->element;
71 		if ( ladder && (ladder->behavior == &actLadder || ladder->behavior == &actPortal) )
72 		{
73 			//if ( ladder->behavior == &actPortal && (ladder->portalNotSecret == 0) )
74 			//{
75 			//	continue; // secret exit, don't care.
76 			//}
77 			for ( int c = 0; c < MAXPLAYERS; ++c )
78 			{
79 				if ( players[c] && players[c]->entity )
80 				{
81 					list_t* path = generatePath(players[c]->entity->x / 16, players[c]->entity->y / 16, ladder->x / 16, ladder->y / 16,
82 						players[c]->entity, ladder, true);
83 					if ( path != NULL )
84 					{
85 						list_FreeAll(path);
86 						free(path);
87 						//messagePlayer(0, "found path to exit");
88 						return true;
89 					}
90 				}
91 			}
92 		}
93 	}
94 	return false;
95 }
96 
97 /*-------------------------------------------------------------------------------
98 
99 doesEntityStopBoulder
100 
101 checks which objects the boulder breaks when it hits.
102 
103 -------------------------------------------------------------------------------*/
104 
doesEntityStopBoulder(Entity * entity)105 bool doesEntityStopBoulder(Entity* entity)
106 {
107 	if ( !entity )
108 	{
109 		return false;
110 	}
111 	if ( entity->behavior == &actGate )
112 	{
113 		return true;
114 	}
115 	else if ( entity->behavior == &actBoulder )
116 	{
117 		return true;
118 	}
119 	else if ( entity->behavior == &actChest )
120 	{
121 		return true;
122 	}
123 	else if ( entity->behavior == &actHeadstone )
124 	{
125 		return true;
126 	}
127 	else if ( entity->behavior == &actFountain )
128 	{
129 		return true;
130 	}
131 	else if ( entity->behavior == &actSink )
132 	{
133 		return true;
134 	}
135 	else if ( entity->behavior == &actStalagCeiling )
136 	{
137 		if ( entity->z > -8 )
138 		{
139 			// not on ceiling layer
140 			return true;
141 		}
142 	}
143 	else if ( entity->behavior == &actStalagFloor )
144 	{
145 		return true;
146 	}
147 	else if ( entity->behavior == &actStalagColumn )
148 	{
149 		return true;
150 	}
151 	else if ( entity->behavior == &actPedestalBase )
152 	{
153 		return true;
154 	}
155 	else if ( entity->behavior == &actColumn )
156 	{
157 		return true;
158 	}
159 	return false;
160 }
161 
162 /*-------------------------------------------------------------------------------
163 
164 	boulderCheckAgainstEntity
165 
166 	causes the boulder given in my to crush the object given in entity
167 	or vice versa
168 
169 -------------------------------------------------------------------------------*/
170 
boulderCheckAgainstEntity(Entity * my,Entity * entity,bool ignoreInsideEntity)171 int boulderCheckAgainstEntity(Entity* my, Entity* entity, bool ignoreInsideEntity)
172 {
173 	if (!my || !entity)
174 	{
175 		return 0;
176 	}
177 
178 	if ( entity->behavior == &actPlayer || entity->behavior == &actMonster )
179 	{
180 		if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
181 		{
182 			Stat* stats = entity->getStats();
183 			if ( stats )
184 			{
185 				if ( entity->behavior == &actPlayer )
186 				{
187 					Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 0);
188 					messagePlayerColor(entity->skill[2], color, language[455]);
189 					if ( entity->skill[2] == clientnum || splitscreen )
190 					{
191 						cameravars[entity->skill[2]].shakex += .1;
192 						cameravars[entity->skill[2]].shakey += 10;
193 					}
194 					else
195 					{
196 						if ( entity->skill[2] > 0 )
197 						{
198 							strcpy((char*)net_packet->data, "SHAK");
199 							net_packet->data[4] = 10; // turns into .1
200 							net_packet->data[5] = 10;
201 							net_packet->address.host = net_clients[entity->skill[2] - 1].host;
202 							net_packet->address.port = net_clients[entity->skill[2] - 1].port;
203 							net_packet->len = 6;
204 							sendPacketSafe(net_sock, -1, net_packet, entity->skill[2] - 1);
205 						}
206 					}
207 				}
208 				playSoundEntity(my, 181, 128);
209 				playSoundEntity(entity, 28, 64);
210 				Entity* gib = spawnGib(entity);
211 				if ( my->sprite == BOULDER_LAVA_SPRITE )
212 				{
213 					entity->modHP(-50);
214 					entity->setObituary(language[3898]);
215 				}
216 				else if ( my->sprite == BOULDER_ARCANE_SPRITE )
217 				{
218 					entity->modHP(-50);
219 					entity->setObituary(language[3899]);
220 				}
221 				else
222 				{
223 					entity->modHP(-80);
224 					entity->setObituary(language[1505]);
225 				}
226 				if ( entity->behavior == &actPlayer )
227 				{
228 					if ( stats->HP <= 0 )
229 					{
230 						if ( stats->type == AUTOMATON )
231 						{
232 							entity->playerAutomatonDeathCounter = TICKS_PER_SECOND * 5; // set the death timer to immediately pop for players.
233 						}
234 						steamAchievementClient(entity->skill[2], "BARONY_ACH_THROW_ME_THE_WHIP");
235 						if ( BOULDER_PLAYERPUSHED >= 0 && entity->skill[2] != BOULDER_PLAYERPUSHED )
236 						{
237 							steamAchievementClient(BOULDER_PLAYERPUSHED, "BARONY_ACH_MOVED_ITSELF");
238 						}
239 						achievementObserver.updateGlobalStat(STEAM_GSTAT_BOULDER_DEATHS);
240 					}
241 				}
242 
243 				bool lifeSaving = (stats->HP <= 0 && stats->amulet && stats->amulet->type == AMULET_LIFESAVING);
244 				if ( !lifeSaving )
245 				{
246 					if ( stats->HP <= 0 && entity->behavior == &actPlayer
247 						&& ((stats->playerRace == RACE_SKELETON && stats->appearance == 0) || stats->type == SKELETON) )
248 					{
249 						if ( stats->MP >= 75 )
250 						{
251 							lifeSaving = true;
252 						}
253 						else
254 						{
255 							int spellCost = getCostOfSpell(&spell_summon, entity);
256 							int numSummonedAllies = 0;
257 							int firstManaToRefund = 0;
258 							int secondManaToRefund = 0;
259 							for ( node_t* node = stats->FOLLOWERS.first; node != nullptr; node = node->next )
260 							{
261 								Uint32* c = (Uint32*)node->element;
262 								Entity* mySummon = nullptr;
263 								if ( c )
264 								{
265 									mySummon = uidToEntity(*c);
266 								}
267 								if ( mySummon && mySummon->monsterAllySummonRank != 0 )
268 								{
269 									Stat* mySummonStats = mySummon->getStats();
270 									if ( mySummonStats )
271 									{
272 										int mp = (mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
273 										if ( numSummonedAllies == 0 )
274 										{
275 											firstManaToRefund += std::min(spellCost, static_cast<int>((mp / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
276 											++numSummonedAllies;
277 										}
278 										else if ( numSummonedAllies == 1 )
279 										{
280 											mySummon->setMP(mySummonStats->MAXMP * (mySummonStats->HP / static_cast<float>(mySummonStats->MAXHP)));
281 											secondManaToRefund += std::min(spellCost, static_cast<int>((mp / static_cast<float>(mySummonStats->MAXMP)) * spellCost)); // MP to restore
282 											++numSummonedAllies;
283 											break;
284 										}
285 									}
286 								}
287 							}
288 
289 							if ( numSummonedAllies == 2 )
290 							{
291 								firstManaToRefund /= 2;
292 								secondManaToRefund /= 2;
293 							}
294 
295 							int manaTotal = stats->MP + firstManaToRefund + secondManaToRefund;
296 							if ( manaTotal >= 75 )
297 							{
298 								lifeSaving = true;
299 							}
300 						}
301 					}
302 				}
303 
304 				if ( stats->HP > 0 || lifeSaving )
305 				{
306 					// spawn several rock items
307 					int i = 8 + rand() % 4;
308 					if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
309 					{
310 						i = 0;
311 					}
312 					int c;
313 					for ( c = 0; c < i; c++ )
314 					{
315 						Entity* entity = newEntity(-1, 1, map.entities, nullptr); //Rock/item entity.
316 						entity->flags[INVISIBLE] = true;
317 						entity->flags[UPDATENEEDED] = true;
318 						entity->x = my->x - 4 + rand() % 8;
319 						entity->y = my->y - 4 + rand() % 8;
320 						entity->z = -6 + rand() % 12;
321 						entity->sizex = 4;
322 						entity->sizey = 4;
323 						entity->yaw = rand() % 360 * PI / 180;
324 						entity->vel_x = (rand() % 20 - 10) / 10.0;
325 						entity->vel_y = (rand() % 20 - 10) / 10.0;
326 						entity->vel_z = -.25 - (rand() % 5) / 10.0;
327 						entity->flags[PASSABLE] = true;
328 						entity->behavior = &actItem;
329 						entity->flags[USERFLAG1] = true; // no collision: helps performance
330 						entity->skill[10] = GEM_ROCK;    // type
331 						entity->skill[11] = WORN;        // status
332 						entity->skill[12] = 0;           // beatitude
333 						entity->skill[13] = 1;           // count
334 						entity->skill[14] = 0;           // appearance
335 						entity->skill[15] = false;       // identified
336 					}
337 
338 					double ox = my->x;
339 					double oy = my->y;
340 
341 					boulderLavaOrArcaneOnDestroy(my, my->sprite, entity);
342 
343 					// destroy the boulder
344 					playSoundEntity(my, 67, 128);
345 					list_RemoveNode(my->mynode);
346 
347 					// on sokoban, destroying boulders spawns scorpions / insectoids
348 					if ( !strcmp(map.name, "Sokoban") )
349 					{
350 						Entity* monster = nullptr;
351 						if ( rand() % 2 == 0 )
352 						{
353 							monster = summonMonster(INSECTOID, ox, oy);
354 						}
355 						else
356 						{
357 							monster = summonMonster(SCORPION, ox, oy);
358 						}
359 						if ( monster )
360 						{
361 							int c;
362 							for ( c = 0; c < MAXPLAYERS; c++ )
363 							{
364 								Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
365 								messagePlayerColor(c, color, language[406]);
366 							}
367 						}
368 						boulderSokobanOnDestroy(false);
369 					}
370 
371 					return 1;
372 				}
373 				else
374 				{
375 					if ( stats->type == GYROBOT )
376 					{
377 						Entity* leader = entity->monsterAllyGetPlayerLeader();
378 						if ( leader )
379 						{
380 							real_t tangent = atan2(leader->y - entity->y, leader->x - entity->x);
381 							Entity* ohitentity = hit.entity;
382 							lineTraceTarget(entity, entity->x, entity->y, tangent, 1024, 0, false, leader);
383 							if ( hit.entity == leader )
384 							{
385 								steamAchievementClient(entity->monsterAllyIndex, "BARONY_ACH_GOODNIGHT_SWEET_PRINCE");
386 							}
387 							hit.entity = ohitentity;
388 						}
389 					}
390 					if ( gibtype[stats->type] > 0 )
391 					{
392 						if ( gibtype[stats->type] == 1 )
393 						{
394 							BOULDER_SPAWNBLOOD = 203; //Blood entity.
395 						}
396 						else if ( gibtype[stats->type] == 2 )
397 						{
398 							BOULDER_SPAWNBLOOD = 213; //Blood entity.
399 						}
400 						else if ( gibtype[stats->type] == 4 )
401 						{
402 							BOULDER_SPAWNBLOOD = 682; //Blood entity.
403 						}
404 						if ( BOULDER_SPAWNBLOOD > 0 )
405 						{
406 							BOULDER_BLOODTIME = TICKS_PER_SECOND * 3;
407 						}
408 					}
409 				}
410 			}
411 		}
412 	}
413 	else if ( doesEntityStopBoulder(entity) )
414 	{
415 		if ( !entity->flags[PASSABLE] )
416 		{
417 			if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
418 			{
419 				// stop the boulder
420 				BOULDER_STOPPED = 1;
421 				my->vel_x = 0.0; // TODOR: Anywhere this is could possible be changed to be a static 'if( BOULDER_ROLLING == 0 ) { vel = 0 }' instead of duplicating code everywhere
422 				my->vel_y = 0.0;
423 				BOULDER_ROLLING = 0;
424 				playSoundEntity(my, 181, 128);
425 				if ( my->flags[PASSABLE] )
426 				{
427 					my->flags[PASSABLE] = false;
428 					if ( multiplayer == SERVER )
429 					{
430 						serverUpdateEntityFlag(my, PASSABLE);
431 					}
432 				}
433 			}
434 		}
435 	}
436 	else if ( entity->behavior == &actDoor )
437 	{
438 		if ( ignoreInsideEntity || entityInsideEntity( my, entity ) )
439 		{
440 			playSoundEntity(entity, 28, 64);
441 			entity->skill[4] = 0;
442 			if ( !entity->skill[0] )
443 			{
444 				entity->skill[6] = (my->x > entity->x);
445 			}
446 			else
447 			{
448 				entity->skill[6] = (my->y < entity->y);
449 			}
450 			playSoundEntity(my, 181, 128);
451 		}
452 	}
453 	else if ( entity->behavior == &actFurniture )
454 	{
455 		if ( ignoreInsideEntity || entityInsideEntity(my, entity) )
456 		{
457 			playSoundEntity(entity, 28, 64);
458 			entity->furnitureHealth = 0;
459 			playSoundEntity(my, 181, 128);
460 		}
461 	}
462 	return 0;
463 }
464 
465 /*-------------------------------------------------------------------------------
466 
467 	act*
468 
469 	The following function describes an entity behavior. The function
470 	takes a pointer to the entity that uses it as an argument.
471 
472 -------------------------------------------------------------------------------*/
473 
actBoulder(Entity * my)474 void actBoulder(Entity* my)
475 {
476 	int i;
477 
478 	if ( multiplayer == CLIENT )
479 	{
480 		if ( my->sprite == 989 ) // boulder_lava.vox
481 		{
482 			my->flags[BURNABLE] = true;
483 		}
484 		return;
485 	}
486 	my->skill[2] = -16; // invokes actBoulder() on clients
487 	my->flags[UPDATENEEDED] = true;
488 
489 	bool noground = false;
490 	int x = std::min<int>(std::max(0, (int)(my->x / 16)), map.width);
491 	int y = std::min<int>(std::max(0, (int)(my->y / 16)), map.height);
492 	Uint32 index = y * MAPLAYERS + x * MAPLAYERS * map.height;
493 	if ( !map.tiles[index] || swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]] )
494 	{
495 		if ( (swimmingtiles[map.tiles[index]] || lavatiles[map.tiles[index]])
496 			&& (my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE) )
497 		{
498 			// lava/arcane balls, roll over lava.
499 			noground = false;
500 		}
501 		else
502 		{
503 			noground = true;
504 		}
505 	}
506 
507 	if ( !BOULDER_INIT )
508 	{
509 		BOULDER_LAVA_EXPLODE = -1;
510 		if ( my->sprite == BOULDER_LAVA_SPRITE )
511 		{
512 			if ( rand() % 4 == 0 )
513 			{
514 				BOULDER_LAVA_EXPLODE = 100 + rand() % 150;
515 			}
516 		}
517 		BOULDER_INIT = 1;
518 		BOULDER_PLAYERPUSHED = -1;
519 	}
520 
521 	if ( BOULDER_LAVA_EXPLODE > 0 )
522 	{
523 		--BOULDER_LAVA_EXPLODE;
524 		if ( BOULDER_LAVA_EXPLODE == 0 )
525 		{
526 			spawnExplosion(my->x, my->y, my->z);
527 			boulderLavaOrArcaneOnDestroy(my, my->sprite, nullptr);
528 			for ( int c = 0; c < 8; ++c )
529 			{
530 				my->yaw = ((double)c + ((rand() % 100) / 100.f)) * (PI * 2) / 8.f;
531 				castSpell(my->getUID(), &spell_fireball, true, true);
532 			}
533 			list_RemoveNode(my->mynode);
534 			return;
535 		}
536 	}
537 
538 	// gravity
539 	bool nobounce = true;
540 	if ( !BOULDER_NOGROUND )
541 	{
542 		if ( noground )
543 		{
544 			BOULDER_NOGROUND = true;
545 		}
546 	}
547 	if ( my->z < 0 || BOULDER_NOGROUND )
548 	{
549 		my->vel_z = std::min<real_t>(my->vel_z + .1, 3.0);
550 		my->vel_x *= 0.85f;
551 		my->vel_y *= 0.85f;
552 		nobounce = true;
553 		if ( my->z >= 128 )
554 		{
555 			list_RemoveNode(my->mynode);
556 			if ( multiplayer != CLIENT && !strncmp(map.name, "Sokoban", 7) )
557 			{
558 				boulderSokobanOnDestroy(true);
559 			}
560 			return;
561 		}
562 		if ( !BOULDER_NOGROUND )
563 		{
564 			if ( my->z >= -8 && fabs(my->vel_z) > 2 )
565 			{
566 				std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
567 				for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
568 				{
569 					list_t* currentList = *it;
570 					node_t* node;
571 					for ( node = currentList->first; node != nullptr; node = node->next )
572 					{
573 						Entity* entity = (Entity*)node->element;
574 						if ( entity == my )
575 						{
576 							continue;
577 						}
578 						if ( boulderCheckAgainstEntity(my, entity, false) )
579 						{
580 							return;
581 						}
582 						else
583 						{
584 							if ( entity->behavior == &actBoulder && entityInsideEntity(my, entity) )
585 							{
586 								// destroy this boulder if falling on another boulder.
587 								Entity* ohitentity = hit.entity;
588 								hit.entity = my;
589 								if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
590 								{
591 									magicDig(nullptr, nullptr, 0, 1);
592 								}
593 								else
594 								{
595 									magicDig(nullptr, nullptr, 2, 4);
596 								}
597 								hit.entity = ohitentity;
598 								return;
599 							}
600 						}
601 					}
602 				}
603 			}
604 		}
605 	}
606 	else
607 	{
608 		if ( fabs(my->vel_z) > 1 )
609 		{
610 			playSoundEntity(my, 182, 128);
611 			my->vel_z = -(my->vel_z / 2);
612 			nobounce = true;
613 		}
614 		else
615 		{
616 			if ( my->vel_z )
617 			{
618 				playSoundEntity(my, 182, 128);
619 			}
620 			my->vel_z = 0;
621 			nobounce = false;
622 		}
623 		my->z = 0;
624 	}
625 	my->z += my->vel_z;
626 	if ( nobounce )
627 	{
628 		if ( !my->flags[PASSABLE] )
629 		{
630 			my->flags[PASSABLE] = true;
631 			if ( multiplayer == SERVER )
632 			{
633 				serverUpdateEntityFlag(my, PASSABLE);
634 			}
635 		}
636 		if ( !BOULDER_STOPPED )
637 		{
638 			my->x += my->vel_x;
639 			my->y += my->vel_y;
640 			double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
641 			my->pitch += dist * .06;
642 			my->roll = PI / 2;
643 		}
644 	}
645 	else if ( !BOULDER_STOPPED )
646 	{
647 		if ( my->flags[PASSABLE] )
648 		{
649 			my->flags[PASSABLE] = false;
650 			if ( multiplayer == SERVER )
651 			{
652 				serverUpdateEntityFlag(my, PASSABLE);
653 			}
654 		}
655 
656 		// horizontal velocity
657 		my->vel_x += cos(my->yaw) * .1;
658 		my->vel_y += sin(my->yaw) * .1;
659 		real_t maxSpeed = 1.5;
660 		if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
661 		{
662 			maxSpeed = 2.5;
663 		}
664 		if ( my->vel_x > maxSpeed )
665 		{
666 			my->vel_x = maxSpeed;
667 		}
668 		if ( my->vel_x < -maxSpeed )
669 		{
670 			my->vel_x = -maxSpeed;
671 		}
672 		if ( my->vel_y > maxSpeed )
673 		{
674 			my->vel_y = maxSpeed;
675 		}
676 		if ( my->vel_y < -maxSpeed )
677 		{
678 			my->vel_y = -maxSpeed;
679 		}
680 
681 		/*int x = std::min<int>(std::max<int>(0, (my->x + my->vel_x + 8) / 16), map.width - 1);
682 		int y = std::min<int>(std::max<int>(0, (my->y + my->vel_y + 8) / 16), map.height - 1);*/
683 		//int x = std::min<int>(std::max<int>(0, (my->x + my->vel_x * 8) / 16), map.width - 1);
684 		//int y = std::min<int>(std::max<int>(0, (my->y + my->vel_y * 8) / 16), map.height - 1);
685 
686 		real_t clipDist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
687 		double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
688 		if ( clipDist != dist && !hit.entity/*map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height]*/ )
689 		{
690 			playSoundEntity(my, 181, 128);
691 			BOULDER_STOPPED = 1;
692 			TileEntityList.updateEntity(*my);
693 			bool foundPathToExit = boulderCheckIfBlockedExit(my);
694 
695 			if ( !foundPathToExit )
696 			{
697 				hit.entity = my; // for magicDig
698 
699 				// spawn luckstone
700 				Entity* rock = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
701 				rock->flags[INVISIBLE] = true;
702 				rock->flags[UPDATENEEDED] = true;
703 				rock->x = my->x;
704 				rock->y = my->y;
705 				rock->z = -6 + rand() % 12;
706 				rock->sizex = 4;
707 				rock->sizey = 4;
708 				rock->yaw = rand() % 360 * PI / 180;
709 				rock->vel_z = -.25 - (rand() % 5) / 10.0;
710 				rock->flags[PASSABLE] = true;
711 				rock->behavior = &actItem;
712 				rock->flags[USERFLAG1] = true; // no collision: helps performance
713 				rock->skill[10] = GEM_LUCK;    // type
714 				rock->skill[11] = WORN;        // status
715 				rock->skill[12] = 0;           // beatitude
716 				rock->skill[13] = 1;           // count
717 				rock->skill[14] = 0;           // appearance
718 				rock->skill[15] = false;       // identified
719 
720 				for ( int c = 0; c < MAXPLAYERS; ++c )
721 				{
722 					Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
723 					if ( !client_disconnected[c] )
724 					{
725 						messagePlayerColor(c, color, language[3401]);
726 					}
727 				}
728 
729 				if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
730 				{
731 					magicDig(nullptr, nullptr, 0, 1);
732 				}
733 				else
734 				{
735 					magicDig(nullptr, nullptr, 2, 4);
736 				}
737 				printlog("notice: boulder stopped path to exit, removed.");
738 				return;
739 			}
740 		}
741 		else
742 		{
743 			my->pitch += dist * .06;
744 			my->roll = PI / 2;
745 
746 			// crush objects
747 			if ( dist && !BOULDER_NOGROUND )
748 			{
749 				std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
750 				for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
751 				{
752 					list_t* currentList = *it;
753 					node_t* node;
754 					for ( node = currentList->first; node != nullptr; node = node->next )
755 					{
756 						Entity* entity = (Entity*)node->element;
757 						if ( entity == my )
758 						{
759 							continue;
760 						}
761 						bool wasStopped = (BOULDER_STOPPED == 1);
762 						if ( clipDist != dist )
763 						{
764 							if ( hit.entity )
765 							{
766 								if ( boulderCheckAgainstEntity(my, hit.entity, true) )
767 								{
768 									return;
769 								}
770 								hit.entity = nullptr;
771 							}
772 						}
773 						else
774 						{
775 							if ( boulderCheckAgainstEntity(my, entity, false) )
776 							{
777 								return;
778 							}
779 						}
780 						if ( BOULDER_STOPPED == 1 && !wasStopped )
781 						{
782 							TileEntityList.updateEntity(*my);
783 							bool foundPathToExit = boulderCheckIfBlockedExit(my);
784 
785 							if ( !foundPathToExit )
786 							{
787 								hit.entity = my;
788 
789 								// spawn luckstone
790 								Entity* rock = newEntity(-1, 1, map.entities, nullptr); //Rock entity.
791 								rock->flags[INVISIBLE] = true;
792 								rock->flags[UPDATENEEDED] = true;
793 								rock->x = my->x;
794 								rock->y = my->y;
795 								rock->z = -6 + rand() % 12;
796 								rock->sizex = 4;
797 								rock->sizey = 4;
798 								rock->yaw = rand() % 360 * PI / 180;
799 								rock->vel_z = -.25 - (rand() % 5) / 10.0;
800 								rock->flags[PASSABLE] = true;
801 								rock->behavior = &actItem;
802 								rock->flags[USERFLAG1] = true; // no collision: helps performance
803 								rock->skill[10] = GEM_LUCK;    // type
804 								rock->skill[11] = WORN;        // status
805 								rock->skill[12] = 0;           // beatitude
806 								rock->skill[13] = 1;           // count
807 								rock->skill[14] = 0;           // appearance
808 								rock->skill[15] = false;       // identified
809 
810 								for ( int c = 0; c < MAXPLAYERS; ++c )
811 								{
812 									Uint32 color = SDL_MapRGB(mainsurface->format, 255, 0, 255);
813 									if ( !client_disconnected[c] )
814 									{
815 										messagePlayerColor(c, color, language[3401]);
816 									}
817 								}
818 
819 								if ( my->sprite == BOULDER_LAVA_SPRITE || my->sprite == BOULDER_ARCANE_SPRITE )
820 								{
821 									magicDig(nullptr, nullptr, 0, 1);
822 								}
823 								else
824 								{
825 									magicDig(nullptr, nullptr, 2, 4);
826 								}
827 								printlog("notice: boulder stopped path to exit, removed.");
828 								return;
829 							}
830 						}
831 					}
832 				}
833 			}
834 		}
835 	}
836 
837 	// pushing boulders
838 	if ( BOULDER_STOPPED )
839 	{
840 		if ( !BOULDER_ROLLING )
841 		{
842 			BOULDER_PLAYERPUSHED = -1;
843 			for (i = 0; i < MAXPLAYERS; i++)
844 			{
845 				if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
846 				{
847 					if (inrange[i])
848 					{
849 						int playerSTR = 0;
850 						if ( players[i] )
851 						{
852 							playerSTR = statGetSTR(stats[i], players[i]->entity);
853 						}
854 						if ( playerSTR < 5 )
855 						{
856 							messagePlayer(i, language[456]);
857 						}
858 						else
859 						{
860 							if (players[i] && players[i]->entity)
861 							{
862 								//playSoundEntity(my, 151, 128);
863 								BOULDER_SOUND_ON_PUSH = i + 1;
864 								BOULDER_ROLLING = 1;
865 								/*my->x = floor(my->x / 16) * 16 + 8;
866 								my->y = floor(my->y / 16) * 16 + 8;*/
867 
868 								BOULDER_DESTX = (int)(my->x / 16) * 16 + 8;
869 								BOULDER_DESTY = (int)(my->y / 16) * 16 + 8;
870 								if ( (int)(players[i]->entity->x / 16) < (int)(my->x / 16) )
871 								{
872 									BOULDER_ROLLDIR = 0; // east
873 								}
874 								else if ( (int)(players[i]->entity->y / 16) < (int)(my->y / 16) )
875 								{
876 									BOULDER_ROLLDIR = 1; // south
877 								}
878 								else if ( (int)(players[i]->entity->x / 16) > (int)(my->x / 16) )
879 								{
880 									BOULDER_ROLLDIR = 2; // west
881 								}
882 								else if ( (int)(players[i]->entity->y / 16) > (int)(my->y / 16) )
883 								{
884 									BOULDER_ROLLDIR = 3; // north
885 								}
886 								switch ( BOULDER_ROLLDIR )
887 								{
888 									case 0:
889 										BOULDER_DESTX += 16;
890 										break;
891 									case 1:
892 										BOULDER_DESTY += 16;
893 										break;
894 									case 2:
895 										BOULDER_DESTX -= 16;
896 										break;
897 									case 3:
898 										BOULDER_DESTY -= 16;
899 										break;
900 								}
901 								BOULDER_PLAYERPUSHED = i;
902 							}
903 						}
904 					}
905 				}
906 			}
907 		}
908 		else
909 		{
910 			switch ( BOULDER_ROLLDIR )
911 			{
912 				case 0:
913 					my->vel_x = 1;
914 					my->vel_y = 0;
915 					break;
916 				case 1:
917 					my->vel_x = 0;
918 					my->vel_y = 1;
919 					break;
920 				case 2:
921 					my->vel_x = -1;
922 					my->vel_y = 0;
923 					break;
924 				case 3:
925 					my->vel_x = 0;
926 					my->vel_y = -1;
927 					break;
928 			}
929 			int x = (my->x + my->vel_x * 8) / 16;
930 			int y = (my->y + my->vel_y * 8) / 16;
931 			x = std::min<unsigned int>(std::max<int>(0, x), map.width - 1);
932 			y = std::min<unsigned int>(std::max<int>(0, y), map.height - 1);
933 			if ( map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
934 			{
935 				my->vel_x = 0.0;
936 				my->vel_y = 0.0;
937 				BOULDER_ROLLING = 0;
938 				if ( BOULDER_SOUND_ON_PUSH > 0 )
939 				{
940 					messagePlayer(BOULDER_SOUND_ON_PUSH - 1, language[3974]);
941 					BOULDER_SOUND_ON_PUSH = 0;
942 				}
943 			}
944 			else
945 			{
946 				real_t clipDist = clipMove(&my->x, &my->y, my->vel_x, my->vel_y, my);
947 				/*my->x += my->vel_x;
948 				my->y += my->vel_y;*/
949 				double dist = sqrt(pow(my->vel_x, 2) + pow(my->vel_y, 2));
950 				if ( clipDist > 0.001 )
951 				{
952 					my->pitch += dist * .06;
953 				}
954 
955 				if ( BOULDER_ROLLDIR == 0 )
956 				{
957 					if ( my->x >= BOULDER_DESTX )
958 					{
959 						my->x = BOULDER_DESTX;
960 						my->vel_x = 0.0;
961 						my->vel_y = 0.0;
962 						BOULDER_ROLLING = 0;
963 					}
964 				}
965 				else if ( BOULDER_ROLLDIR == 1 )
966 				{
967 					if ( my->y >= BOULDER_DESTY )
968 					{
969 						my->y = BOULDER_DESTY;
970 						my->vel_x = 0.0;
971 						my->vel_y = 0.0;
972 						BOULDER_ROLLING = 0;
973 					}
974 				}
975 				else if ( BOULDER_ROLLDIR == 2 )
976 				{
977 					if ( my->x <= BOULDER_DESTX )
978 					{
979 						my->x = BOULDER_DESTX;
980 						my->vel_x = 0.0;
981 						my->vel_y = 0.0;
982 						BOULDER_ROLLING = 0;
983 					}
984 				}
985 				else if ( BOULDER_ROLLDIR == 3 )
986 				{
987 					if ( my->y <= BOULDER_DESTY )
988 					{
989 						my->y = BOULDER_DESTY;
990 						my->vel_x = 0.0;
991 						my->vel_y = 0.0;
992 						BOULDER_ROLLING = 0;
993 					}
994 				}
995 				double dir = my->yaw - BOULDER_ROLLDIR * PI / 2;
996 				while ( dir >= PI )
997 				{
998 					dir -= PI * 2;
999 				}
1000 				while ( dir < -PI )
1001 				{
1002 					dir += PI * 2;
1003 				}
1004 
1005 				if ( clipDist > 0.001 )
1006 				{
1007 					my->yaw -= dir / 16;
1008 				}
1009 
1010 				while ( my->yaw < 0 )
1011 				{
1012 					my->yaw += 2 * PI;
1013 				}
1014 				while ( my->yaw >= 2 * PI )
1015 				{
1016 					my->yaw -= 2 * PI;
1017 				}
1018 
1019 				// crush objects
1020 				if ( !BOULDER_NOGROUND )
1021 				{
1022 					if ( clipDist != dist )
1023 					{
1024 						if ( BOULDER_SOUND_ON_PUSH > 0 )
1025 						{
1026 							if ( clipDist > 0.001 )
1027 							{
1028 								playSoundEntity(my, 151, 128);
1029 							}
1030 							else
1031 							{
1032 								messagePlayer(BOULDER_SOUND_ON_PUSH - 1, language[3974]);
1033 							}
1034 							BOULDER_SOUND_ON_PUSH = 0;
1035 						}
1036 						if ( hit.entity && boulderCheckAgainstEntity(my, hit.entity, true) )
1037 						{
1038 							return;
1039 						}
1040 					}
1041 					/*std::vector<list_t*> entLists = TileEntityList.getEntitiesWithinRadiusAroundEntity(my, 2);
1042 					for ( std::vector<list_t*>::iterator it = entLists.begin(); it != entLists.end(); ++it )
1043 					{
1044 						list_t* currentList = *it;
1045 						node_t* node;
1046 						for ( node = currentList->first; node != nullptr; node = node->next )
1047 						{
1048 							Entity* entity = (Entity*)node->element;
1049 							if ( entity == my )
1050 							{
1051 								continue;
1052 							}
1053 							if ( boulderCheckAgainstEntity(my, entity) )
1054 							{
1055 								return;
1056 							}
1057 						}
1058 					}*/
1059 				}
1060 
1061 				if ( BOULDER_SOUND_ON_PUSH > 0 )
1062 				{
1063 					playSoundEntity(my, 151, 128);
1064 					BOULDER_SOUND_ON_PUSH = 0;
1065 				}
1066 			}
1067 		}
1068 	}
1069 
1070 	// wrap around angles
1071 	while ( my->pitch >= PI * 2 )
1072 	{
1073 		my->pitch -= PI * 2;
1074 	}
1075 	while ( my->pitch < 0 )
1076 	{
1077 		my->pitch += PI * 2;
1078 	}
1079 	while ( my->roll >= PI * 2 )
1080 	{
1081 		my->roll -= PI * 2;
1082 	}
1083 	while ( my->roll < 0 )
1084 	{
1085 		my->roll += PI * 2;
1086 	}
1087 
1088 	// rolling sound
1089 	if ( !BOULDER_STOPPED && (fabs(my->vel_x) > 0 || fabs(my->vel_y) > 0) )
1090 	{
1091 		BOULDER_AMBIENCE++;
1092 		if ( !strncmp(map.name, "Hell Boss", 9) )
1093 		{
1094 			if ( BOULDER_AMBIENCE >= TICKS_PER_SECOND / 2)
1095 			{
1096 				BOULDER_AMBIENCE = 0;
1097 				playSoundEntity(my, 151, 64);
1098 			}
1099 		}
1100 		else if ( BOULDER_AMBIENCE >= TICKS_PER_SECOND / 3 )
1101 		{
1102 			BOULDER_AMBIENCE = 0;
1103 			playSoundEntity(my, 151, 128);
1104 		}
1105 
1106 		if ( my->sprite == BOULDER_LAVA_SPRITE )
1107 		{
1108 			if ( !my->flags[BURNING] && BOULDER_LAVA_EXPLODE >= 0 )
1109 			{
1110 				my->flags[BURNABLE] = true;
1111 				my->flags[BURNING] = true;
1112 				serverUpdateEntityFlag(my, BURNING);
1113 			}
1114 		}
1115 	}
1116 	if ( (!BOULDER_STOPPED || BOULDER_ROLLING) && (fabs(my->vel_x) > 0 || fabs(my->vel_y) > 0) )
1117 	{
1118 		if ( multiplayer != CLIENT && map.tiles[static_cast<int>(my->y / 16) * MAPLAYERS + static_cast<int>(my->x / 16) * MAPLAYERS * map.height] )
1119 		{
1120 			// spawn blood only if there's a floor!
1121 			if ( BOULDER_SPAWNBLOOD != 0 && BOULDER_BLOODTIME > 0 )
1122 			{
1123 				int rate = 20;
1124 				if ( BOULDER_BLOODTIME > 2 * TICKS_PER_SECOND )
1125 				{
1126 					rate = 7;
1127 				}
1128 				else if ( BOULDER_BLOODTIME > 1 * TICKS_PER_SECOND )
1129 				{
1130 					rate = 15;
1131 				}
1132 				if ( spawn_blood && my->ticks % (rate + rand() % 3) == 0 )
1133 				{
1134 					Entity* blood = newEntity(BOULDER_SPAWNBLOOD, 1, map.entities, nullptr); //Gib entity.;
1135 					if ( blood != NULL )
1136 					{
1137 						blood->x = my->x - 4 + rand() % 9;
1138 						blood->y = my->y - 4 + rand() % 9;
1139 						blood->z = 8.0 + (rand() % 20) / 100.0;
1140 						blood->parent = my->getUID();
1141 						blood->sizex = 2;
1142 						blood->sizey = 2;
1143 						int randomScale = rand() % 10;
1144 						blood->scalex = (100 - randomScale) / 100.f;
1145 						blood->scaley = blood->scalex;
1146 						blood->yaw = (rand() % 360) * PI / 180.0;
1147 						blood->flags[UPDATENEEDED] = true;
1148 						blood->flags[PASSABLE] = true;
1149 					}
1150 				}
1151 			}
1152 		}
1153 	}
1154 	if ( BOULDER_BLOODTIME > 0 )
1155 	{
1156 		--BOULDER_BLOODTIME;
1157 	}
1158 }
1159 
1160 #define BOULDERTRAP_FIRED my->skill[0]
1161 #define BOULDERTRAP_AMBIENCE my->skill[6]
1162 
actBoulderTrap(Entity * my)1163 void actBoulderTrap(Entity* my)
1164 {
1165 	int x, y;
1166 	int c;
1167 
1168 	if ( !BOULDERTRAP_FIRED )
1169 	{
1170 		BOULDERTRAP_AMBIENCE--;
1171 		if ( BOULDERTRAP_AMBIENCE <= 0 )
1172 		{
1173 			BOULDERTRAP_AMBIENCE = TICKS_PER_SECOND * 30;
1174 			playSoundEntity(my, 149, 64);
1175 		}
1176 	}
1177 
1178 	if ( !my->skill[28] )
1179 	{
1180 		return;
1181 	}
1182 
1183 	// received on signal
1184 	if ( my->skill[28] == 2 )
1185 	{
1186 		if ( !BOULDERTRAP_FIRED )
1187 		{
1188 			int foundTrapdoor = -1;
1189 			BOULDERTRAP_FIRED = 1;
1190 			for ( c = 0; c < 4; c++ )
1191 			{
1192 				if ( my->boulderTrapRocksToSpawn & (1 << c) )
1193 				{
1194 					switch ( c )
1195 					{
1196 						case 0:
1197 							x = 16;
1198 							y = 0;
1199 							break;
1200 						case 1:
1201 							x = 0;
1202 							y = 16;
1203 							break;
1204 						case 2:
1205 							x = -16;
1206 							y = 0;
1207 							break;
1208 						case 3:
1209 							x = 0;
1210 							y = -16;
1211 							break;
1212 					}
1213 					x = ((int)(x + my->x)) >> 4;
1214 					y = ((int)(y + my->y)) >> 4;
1215 					if ( x >= 0 && y >= 0 && x < map.width && y < map.height )
1216 					{
1217 						if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1218 						{
1219 							list_t* trapdoors = TileEntityList.getTileList(x, y);
1220 							for ( node_t* trapNode = trapdoors->first; trapNode != nullptr; trapNode = trapNode->next )
1221 							{
1222 								Entity* trapEntity = (Entity*)trapNode->element;
1223 								if ( trapEntity && trapEntity->sprite == 252 && trapEntity->z <= -10 )
1224 								{
1225 									foundTrapdoor = c;
1226 									break;
1227 								}
1228 							}
1229 							if ( foundTrapdoor == c )
1230 							{
1231 								Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1232 								entity->parent = my->getUID();
1233 								entity->x = (x << 4) + 8;
1234 								entity->y = (y << 4) + 8;
1235 								entity->z = -64;
1236 								entity->yaw = c * (PI / 2.f);
1237 								entity->sizex = 7;
1238 								entity->sizey = 7;
1239 								if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1240 								{
1241 									entity->yaw += PI * (rand() % 2) - PI / 2;
1242 									if ( entity->yaw >= PI * 2 )
1243 									{
1244 										entity->yaw -= PI * 2;
1245 									}
1246 									else if ( entity->yaw < 0 )
1247 									{
1248 										entity->yaw += PI * 2;
1249 									}
1250 								}
1251 								entity->behavior = &actBoulder;
1252 								entity->flags[UPDATENEEDED] = true;
1253 								entity->flags[PASSABLE] = true;
1254 							}
1255 						}
1256 					}
1257 				}
1258 			}
1259 			if ( foundTrapdoor >= 0 )
1260 			{
1261 				playSoundEntity(my, 150, 128);
1262 				for ( c = 0; c < MAXPLAYERS; c++ )
1263 				{
1264 					playSoundPlayer(c, 150, 64);
1265 				}
1266 			}
1267 		}
1268 	}
1269 }
1270 
actBoulderTrapEast(Entity * my)1271 void actBoulderTrapEast(Entity* my)
1272 {
1273 	int x, y;
1274 	int c;
1275 
1276 	if ( !my->boulderTrapFired )
1277 	{
1278 		my->boulderTrapAmbience--;
1279 		if ( my->boulderTrapAmbience <= 0 )
1280 		{
1281 			my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1282 			playSoundEntity(my, 149, 64);
1283 		}
1284 	}
1285 
1286 	if ( my->boulderTrapRefireCounter > 0 )
1287 	{
1288 		--my->boulderTrapRefireCounter;
1289 		if ( my->boulderTrapRefireCounter <= 0 )
1290 		{
1291 			my->boulderTrapFired = 0;
1292 			my->boulderTrapRefireCounter = 0;
1293 		}
1294 	}
1295 
1296 	if ( !my->skill[28] )
1297 	{
1298 		return;
1299 	}
1300 
1301 	// received on signal
1302 	if ( my->skill[28] == 2 )
1303 	{
1304 		if ( !my->boulderTrapFired )
1305 		{
1306 			if ( my->boulderTrapPreDelay > 0 )
1307 			{
1308 				--my->boulderTrapPreDelay;
1309 				return;
1310 			}
1311 			playSoundEntity(my, 150, 128);
1312 			for ( c = 0; c < MAXPLAYERS; c++ )
1313 			{
1314 				playSoundPlayer(c, 150, 64);
1315 			}
1316 			my->boulderTrapFired = 1;
1317 
1318 			c = 0; // direction
1319 			x = ((int)(my->x)) >> 4;
1320 			y = ((int)(my->y)) >> 4;
1321 			if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1322 			{
1323 				Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1324 				entity->parent = my->getUID();
1325 				entity->x = (x << 4) + 8;
1326 				entity->y = (y << 4) + 8;
1327 				entity->z = -64;
1328 				entity->yaw = c * (PI / 2.f);
1329 				entity->sizex = 7;
1330 				entity->sizey = 7;
1331 				/*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1332 				{
1333 					entity->yaw += PI * (rand() % 2) - PI / 2;
1334 					if ( entity->yaw >= PI * 2 )
1335 					{
1336 						entity->yaw -= PI * 2;
1337 					}
1338 					else if ( entity->yaw < 0 )
1339 					{
1340 						entity->yaw += PI * 2;
1341 					}
1342 				}*/
1343 				entity->behavior = &actBoulder;
1344 				entity->flags[UPDATENEEDED] = true;
1345 				entity->flags[PASSABLE] = true;
1346 			}
1347 
1348 			if ( my->boulderTrapRefireAmount > 0 )
1349 			{
1350 				--my->boulderTrapRefireAmount;
1351 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1352 			}
1353 			else if ( my->boulderTrapRefireAmount == -1 )
1354 			{
1355 				// infinite boulders.
1356 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1357 			}
1358 		}
1359 	}
1360 }
1361 
actBoulderTrapSouth(Entity * my)1362 void actBoulderTrapSouth(Entity* my)
1363 {
1364 	int x, y;
1365 	int c;
1366 
1367 	if ( !my->boulderTrapFired )
1368 	{
1369 		my->boulderTrapAmbience--;
1370 		if ( my->boulderTrapAmbience <= 0 )
1371 		{
1372 			my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1373 			playSoundEntity(my, 149, 64);
1374 		}
1375 	}
1376 
1377 	if ( my->boulderTrapRefireCounter > 0 )
1378 	{
1379 		--my->boulderTrapRefireCounter;
1380 		if ( my->boulderTrapRefireCounter <= 0 )
1381 		{
1382 			my->boulderTrapFired = 0;
1383 			my->boulderTrapRefireCounter = 0;
1384 		}
1385 	}
1386 
1387 	if ( !my->skill[28] )
1388 	{
1389 		return;
1390 	}
1391 
1392 	// received on signal
1393 	if ( my->skill[28] == 2 )
1394 	{
1395 		if ( !my->boulderTrapFired )
1396 		{
1397 			if ( my->boulderTrapPreDelay > 0 )
1398 			{
1399 				--my->boulderTrapPreDelay;
1400 				return;
1401 			}
1402 			playSoundEntity(my, 150, 128);
1403 			for ( c = 0; c < MAXPLAYERS; c++ )
1404 			{
1405 				playSoundPlayer(c, 150, 64);
1406 			}
1407 			my->boulderTrapFired = 1;
1408 
1409 			c = 1; // direction
1410 			x = ((int)(my->x)) >> 4;
1411 			y = ((int)(my->y)) >> 4;
1412 			if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1413 			{
1414 				Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1415 				entity->parent = my->getUID();
1416 				entity->x = (x << 4) + 8;
1417 				entity->y = (y << 4) + 8;
1418 				entity->z = -64;
1419 				entity->yaw = c * (PI / 2.f);
1420 				entity->sizex = 7;
1421 				entity->sizey = 7;
1422 				/*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1423 				{
1424 					entity->yaw += PI * (rand() % 2) - PI / 2;
1425 					if ( entity->yaw >= PI * 2 )
1426 					{
1427 						entity->yaw -= PI * 2;
1428 					}
1429 					else if ( entity->yaw < 0 )
1430 					{
1431 						entity->yaw += PI * 2;
1432 					}
1433 				}*/
1434 				entity->behavior = &actBoulder;
1435 				entity->flags[UPDATENEEDED] = true;
1436 				entity->flags[PASSABLE] = true;
1437 			}
1438 
1439 			if ( my->boulderTrapRefireAmount > 0 )
1440 			{
1441 				--my->boulderTrapRefireAmount;
1442 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1443 			}
1444 			else if ( my->boulderTrapRefireAmount == -1 )
1445 			{
1446 				// infinite boulders.
1447 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1448 			}
1449 		}
1450 	}
1451 }
1452 
actBoulderTrapWest(Entity * my)1453 void actBoulderTrapWest(Entity* my)
1454 {
1455 	int x, y;
1456 	int c;
1457 
1458 	if ( !my->boulderTrapFired )
1459 	{
1460 		my->boulderTrapAmbience--;
1461 		if ( my->boulderTrapAmbience <= 0 )
1462 		{
1463 			my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1464 			playSoundEntity(my, 149, 64);
1465 		}
1466 	}
1467 
1468 	if ( my->boulderTrapRefireCounter > 0 )
1469 	{
1470 		--my->boulderTrapRefireCounter;
1471 		if ( my->boulderTrapRefireCounter <= 0 )
1472 		{
1473 			my->boulderTrapFired = 0;
1474 			my->boulderTrapRefireCounter = 0;
1475 		}
1476 	}
1477 
1478 	if ( !my->skill[28] )
1479 	{
1480 		return;
1481 	}
1482 
1483 	// received on signal
1484 	if ( my->skill[28] == 2 )
1485 	{
1486 		if ( !my->boulderTrapFired )
1487 		{
1488 			if ( my->boulderTrapPreDelay > 0 )
1489 			{
1490 				--my->boulderTrapPreDelay;
1491 				return;
1492 			}
1493 			playSoundEntity(my, 150, 128);
1494 			for ( c = 0; c < MAXPLAYERS; c++ )
1495 			{
1496 				playSoundPlayer(c, 150, 64);
1497 			}
1498 
1499 			my->boulderTrapFired = 1;
1500 
1501 			c = 2; // direction
1502 			x = ((int)(my->x)) >> 4;
1503 			y = ((int)(my->y)) >> 4;
1504 			if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1505 			{
1506 				Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1507 				entity->parent = my->getUID();
1508 				entity->x = (x << 4) + 8;
1509 				entity->y = (y << 4) + 8;
1510 				entity->z = -64;
1511 				entity->yaw = c * (PI / 2.f);
1512 				entity->sizex = 7;
1513 				entity->sizey = 7;
1514 				/*if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1515 				{
1516 					entity->yaw += PI * (rand() % 2) - PI / 2;
1517 					if ( entity->yaw >= PI * 2 )
1518 					{
1519 						entity->yaw -= PI * 2;
1520 					}
1521 					else if ( entity->yaw < 0 )
1522 					{
1523 						entity->yaw += PI * 2;
1524 					}
1525 				}*/
1526 				entity->behavior = &actBoulder;
1527 				entity->flags[UPDATENEEDED] = true;
1528 				entity->flags[PASSABLE] = true;
1529 			}
1530 
1531 			if ( my->boulderTrapRefireAmount > 0 )
1532 			{
1533 				--my->boulderTrapRefireAmount;
1534 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1535 			}
1536 			else if ( my->boulderTrapRefireAmount == -1 )
1537 			{
1538 				// infinite boulders.
1539 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1540 			}
1541 		}
1542 	}
1543 }
1544 
actBoulderTrapNorth(Entity * my)1545 void actBoulderTrapNorth(Entity* my)
1546 {
1547 	int x, y;
1548 	int c;
1549 
1550 	if ( !my->boulderTrapFired )
1551 	{
1552 		my->boulderTrapAmbience--;
1553 		if ( my->boulderTrapAmbience <= 0 )
1554 		{
1555 			my->boulderTrapAmbience = TICKS_PER_SECOND * 30;
1556 			playSoundEntity(my, 149, 64);
1557 		}
1558 	}
1559 
1560 	if ( my->boulderTrapRefireCounter > 0 )
1561 	{
1562 		--my->boulderTrapRefireCounter;
1563 		if ( my->boulderTrapRefireCounter <= 0 )
1564 		{
1565 			my->boulderTrapFired = 0;
1566 			my->boulderTrapRefireCounter = 0;
1567 		}
1568 	}
1569 
1570 	if ( !my->skill[28] )
1571 	{
1572 		return;
1573 	}
1574 
1575 	// received on signal
1576 	if ( my->skill[28] == 2 )
1577 	{
1578 		if ( !my->boulderTrapFired )
1579 		{
1580 			if ( my->boulderTrapPreDelay > 0 )
1581 			{
1582 				--my->boulderTrapPreDelay;
1583 				return;
1584 			}
1585 			playSoundEntity(my, 150, 128);
1586 			for ( c = 0; c < MAXPLAYERS; c++ )
1587 			{
1588 				playSoundPlayer(c, 150, 64);
1589 			}
1590 			my->boulderTrapFired = 1;
1591 
1592 			c = 3; // direction
1593 			x = ((int)(my->x)) >> 4;
1594 			y = ((int)(my->y)) >> 4;
1595 			if ( !map.tiles[OBSTACLELAYER + y * MAPLAYERS + x * MAPLAYERS * map.height] )
1596 			{
1597 				Entity* entity = newEntity(245, 1, map.entities, nullptr); // boulder
1598 				entity->parent = my->getUID();
1599 				entity->x = (x << 4) + 8;
1600 				entity->y = (y << 4) + 8;
1601 				entity->z = -64;
1602 				entity->yaw = c * (PI / 2.f);
1603 				entity->sizex = 7;
1604 				entity->sizey = 7;
1605 			/*	if ( checkObstacle(entity->x + cos(entity->yaw) * 16, entity->y + sin(entity->yaw) * 16, entity, NULL) )
1606 				{
1607 					entity->yaw += PI * (rand() % 2) - PI / 2;
1608 					if ( entity->yaw >= PI * 2 )
1609 					{
1610 						entity->yaw -= PI * 2;
1611 					}
1612 					else if ( entity->yaw < 0 )
1613 					{
1614 						entity->yaw += PI * 2;
1615 					}
1616 				}*/
1617 				entity->behavior = &actBoulder;
1618 				entity->flags[UPDATENEEDED] = true;
1619 				entity->flags[PASSABLE] = true;
1620 			}
1621 
1622 			if ( my->boulderTrapRefireAmount > 0 )
1623 			{
1624 				--my->boulderTrapRefireAmount;
1625 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1626 			}
1627 			else if ( my->boulderTrapRefireAmount == -1 )
1628 			{
1629 				// infinite boulders.
1630 				my->boulderTrapRefireCounter = my->boulderTrapRefireDelay * TICKS_PER_SECOND;
1631 			}
1632 		}
1633 	}
1634 }
1635 
boulderSokobanOnDestroy(bool pushedOffLedge)1636 void boulderSokobanOnDestroy(bool pushedOffLedge)
1637 {
1638 	if ( multiplayer == CLIENT || strcmp(map.name, "Sokoban") )
1639 	{
1640 		return; // return for client and if map not sokoban.
1641 	}
1642 
1643 	int goldToDestroy = 5 + rand() % 4; // 5-8 bags destroy
1644 	bool bouldersAround = false;
1645 	node_t* node = nullptr;
1646 
1647 	if ( !pushedOffLedge ) // destroy some gold
1648 	{
1649 		for ( node_t* node = map.entities->first; node != nullptr; )
1650 		{
1651 			Entity* entity = (Entity*)node->element;
1652 			node = node->next;
1653 			if ( entity )
1654 			{
1655 				if ( entity->behavior == &actGoldBag && entity->goldSokoban == 1 && goldToDestroy > 0 )
1656 				{
1657 					if ( entity->mynode )
1658 					{
1659 						list_RemoveNode(entity->mynode);
1660 					}
1661 					--goldToDestroy;
1662 				}
1663 			}
1664 		}
1665 	}
1666 
1667 	for ( node_t* node = map.entities->first; node != nullptr; node = node->next )
1668 	{
1669 		Entity* entity = (Entity*)node->element;
1670 		if ( entity )
1671 		{
1672 			if ( !bouldersAround && entity->behavior == &actBoulder )
1673 			{
1674 				bouldersAround = true;
1675 				break;
1676 			}
1677 		}
1678 	}
1679 
1680 	if ( !bouldersAround )
1681 	{
1682 		int goldCount = 0;
1683 		Entity* sokobanItemReward = nullptr;
1684 		for ( node = map.entities->first; node != nullptr; node = node->next )
1685 		{
1686 			Entity* entity = (Entity*)node->element;
1687 			if ( entity )
1688 			{
1689 				if ( entity->behavior == &actGoldBag && entity->goldSokoban == 1 )
1690 				{
1691 					++goldCount;
1692 				}
1693 				if ( entity->behavior == &actItem && entity->itemSokobanReward == 1 ) // artifact gloves.
1694 				{
1695 					sokobanItemReward = entity;
1696 				}
1697 			}
1698 		}
1699 		//messagePlayer(0, "Solved it!");
1700 		for ( int c = 0; c < MAXPLAYERS; c++ )
1701 		{
1702 			Uint32 color = SDL_MapRGB(mainsurface->format, 255, 128, 0);
1703 			if ( goldCount >= 39 )
1704 			{
1705 				playSoundPlayer(c, 393, 128);
1706 				messagePlayerColor(c, color, language[2969]);
1707 			}
1708 			else
1709 			{
1710 				playSoundPlayer(c, 395, 128);
1711 				if ( goldCount < 25 )
1712 				{
1713 					messagePlayerColor(c, color, language[2971]); // less than impressed.
1714 				}
1715 				else
1716 				{
1717 					messagePlayerColor(c, color, language[2970]); // mildly entertained.
1718 				}
1719 			}
1720 		}
1721 		if ( goldCount >= 25 && sokobanItemReward )
1722 		{
1723 			sokobanItemReward->flags[INVISIBLE] = false;
1724 			serverUpdateEntityFlag(sokobanItemReward, INVISIBLE);
1725 		}
1726 	}
1727 }
1728 
isBoulderSprite()1729 bool Entity::isBoulderSprite()
1730 {
1731 	if ( sprite == 245 || sprite == 989 || sprite == 990 )
1732 	{
1733 		return true;
1734 	}
1735 	return false;
1736 }
1737 
boulderLavaOrArcaneOnDestroy(Entity * my,int sprite,Entity * boulderHitEntity)1738 void boulderLavaOrArcaneOnDestroy(Entity* my, int sprite, Entity* boulderHitEntity)
1739 {
1740 	if ( !boulderHitEntity && !my )
1741 	{
1742 		return;
1743 	}
1744 	if ( sprite != BOULDER_LAVA_SPRITE && sprite != BOULDER_ARCANE_SPRITE )
1745 	{
1746 		return;
1747 	}
1748 
1749 	if ( !boulderHitEntity )
1750 	{
1751 		if ( my )
1752 		{
1753 			if ( sprite == BOULDER_LAVA_SPRITE )
1754 			{
1755 				spawnMagicTower(my, my->x, my->y, SPELL_FIREBALL, nullptr);
1756 			}
1757 			else if ( sprite == BOULDER_ARCANE_SPRITE )
1758 			{
1759 				switch ( rand() % 4 )
1760 				{
1761 					case 0:
1762 						spawnMagicTower(my, my->x, my->y, SPELL_LIGHTNING, nullptr);
1763 						break;
1764 					case 1:
1765 						spawnMagicTower(my, my->x, my->y, SPELL_COLD, nullptr);
1766 						break;
1767 					case 2:
1768 						spawnMagicTower(my, my->x, my->y, SPELL_FIREBALL, nullptr);
1769 						break;
1770 					case 3:
1771 						spawnMagicTower(my, my->x, my->y, SPELL_MAGICMISSILE, nullptr);
1772 						break;
1773 					default:
1774 						spawnMagicTower(my, my->x, my->y, SPELL_MAGICMISSILE, nullptr);
1775 						break;
1776 				}
1777 			}
1778 		}
1779 	}
1780 	else
1781 	{
1782 		boulderHitEntity->SetEntityOnFire();
1783 		boulderHitEntity->setObituary(language[3898]);
1784 		if ( sprite == BOULDER_LAVA_SPRITE )
1785 		{
1786 			spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_FIREBALL, boulderHitEntity);
1787 		}
1788 		else if ( sprite == BOULDER_ARCANE_SPRITE )
1789 		{
1790 			switch ( rand() % 4 )
1791 			{
1792 				case 0:
1793 					spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_LIGHTNING, boulderHitEntity);
1794 					break;
1795 				case 1:
1796 					spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_COLD, boulderHitEntity);
1797 					break;
1798 				case 2:
1799 					spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_FIREBALL, boulderHitEntity);
1800 					break;
1801 				case 3:
1802 					spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_MAGICMISSILE, boulderHitEntity);
1803 					break;
1804 				default:
1805 					spawnMagicTower(nullptr, boulderHitEntity->x, boulderHitEntity->y, SPELL_MAGICMISSILE, boulderHitEntity);
1806 					break;
1807 			}
1808 		}
1809 	}
1810 }