1 /*-------------------------------------------------------------------------------
2 
3 	BARONY
4 	File: actladder.cpp
5 	Desc: behavior function for ladders
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 "sound.hpp"
16 #include "entity.hpp"
17 #include "scores.hpp"
18 #include "net.hpp"
19 #include "collision.hpp"
20 #include "player.hpp"
21 #include "magic/magic.hpp"
22 #include "menu.hpp"
23 #include "files.hpp"
24 #include "items.hpp"
25 #include "mod_tools.hpp"
26 
27 /*-------------------------------------------------------------------------------
28 
29 	act*
30 
31 	The following function describes an entity behavior. The function
32 	takes a pointer to the entity that uses it as an argument.
33 
34 -------------------------------------------------------------------------------*/
35 
36 #define LADDER_AMBIENCE my->skill[1]
37 #define LADDER_SECRET_ENTRANCE my->skill[3]
38 
actLadder(Entity * my)39 void actLadder(Entity* my)
40 {
41 	int playercount = 0;
42 	double dist;
43 	int i, c;
44 
45 	LADDER_AMBIENCE--;
46 	if (LADDER_AMBIENCE <= 0)
47 	{
48 		LADDER_AMBIENCE = TICKS_PER_SECOND * 30;
49 		playSoundEntityLocal(my, 149, 64);
50 	}
51 
52 	// use ladder (climb)
53 	if (multiplayer != CLIENT)
54 	{
55 		for (i = 0; i < MAXPLAYERS; i++)
56 		{
57 			if ((i == 0 && selectedEntity == my) || (client_selected[i] == my))
58 			{
59 				if (inrange[i])
60 				{
61 					for (c = 0; c < MAXPLAYERS; c++)
62 					{
63 						if (client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr)
64 						{
65 							continue;
66 						}
67 						else
68 						{
69 							playercount++;
70 						}
71 						dist = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
72 						if (dist > TOUCHRANGE)
73 						{
74 							messagePlayer(i, language[505]);
75 							return;
76 						}
77 					}
78 					if (playercount == 1)
79 					{
80 						messagePlayer(i, language[506]);
81 					}
82 					else
83 					{
84 						messagePlayer(i, language[507]);
85 					}
86 					loadnextlevel = true;
87 					if (secretlevel)
88 					{
89 						switch (currentlevel)
90 						{
91 							case 3:
92 								for (c = 0; c < MAXPLAYERS; c++)
93 								{
94 									steamAchievementClient(c, "BARONY_ACH_THUNDERGNOME");
95 								}
96 								break;
97 						}
98 						if ( LADDER_SECRET_ENTRANCE )
99 						{
100 							skipLevelsOnLoad = -1; // don't skip a regular level anymore. still skip if in underworld.
101 						}
102 					}
103 					if ( LADDER_SECRET_ENTRANCE )
104 					{
105 						secretlevel = (secretlevel == false);    // toggle level lists
106 					}
107 					return;
108 				}
109 			}
110 		}
111 	}
112 }
113 
actLadderUp(Entity * my)114 void actLadderUp(Entity* my)
115 {
116 	/*LADDER_AMBIENCE--;
117 	if ( LADDER_AMBIENCE <= 0 )
118 	{
119 		LADDER_AMBIENCE = TICKS_PER_SECOND * 30;
120 		playSoundEntityLocal( my, 149, 64 );
121 	}*/
122 
123 	// use ladder
124 	if ( multiplayer != CLIENT )
125 	{
126 		for (int i = 0; i < MAXPLAYERS; i++)
127 		{
128 			if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
129 			{
130 				if (inrange[i])
131 				{
132 					messagePlayer(i, language[508]);
133 					return;
134 				}
135 			}
136 		}
137 	}
138 }
139 
actPortal(Entity * my)140 void actPortal(Entity* my)
141 {
142 	int playercount = 0;
143 	double dist;
144 	int i, c;
145 
146 	if ( !my->portalInit )
147 	{
148 		my->portalInit = 1;
149 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 255);
150 		if ( !strncmp(map.name, "Cockatrice Lair", 15) )
151 		{
152 			my->flags[INVISIBLE] = true;
153 		}
154 		else if ( !strncmp(map.name, "Bram's Castle", 13) )
155 		{
156 			my->flags[INVISIBLE] = true;
157 		}
158 	}
159 
160 	my->portalAmbience--;
161 	if ( my->portalAmbience <= 0 )
162 	{
163 		my->portalAmbience = TICKS_PER_SECOND * 2;
164 		if ( !my->flags[INVISIBLE] )
165 		{
166 			playSoundEntityLocal( my, 154, 128 );
167 		}
168 	}
169 
170 	my->yaw += 0.01; // rotate slowly on my axis
171 	my->sprite = 254 + (my->ticks / 20) % 4; // animate
172 
173 	if ( multiplayer == CLIENT )
174 	{
175 		return;
176 	}
177 
178 	if ( my->flags[INVISIBLE] && ticks % 50 == 0 && !strncmp(map.name, "Cockatrice Lair", 15) )
179 	{
180 		node_t* node = nullptr;
181 		Entity* entity = nullptr;
182 		bool bossAlive = false;
183 		for ( node = map.entities->first; node != nullptr; )
184 		{
185 			entity = (Entity*)node->element;
186 			node = node->next;
187 			if ( entity && entity->behavior == &actMonster
188 				&& entity->getMonsterTypeFromSprite() == COCKATRICE
189 				&& !entity->monsterAllyGetPlayerLeader() )
190 			{
191 				bossAlive = true;
192 			}
193 		}
194 		if ( !bossAlive )
195 		{
196 			for ( node = map.entities->first; node != nullptr; )
197 			{
198 				entity = (Entity*)node->element;
199 				node = node->next;
200 				if ( entity->behavior == &actMagicTrap )
201 				{
202 					list_RemoveNode(entity->mynode);
203 				}
204 			}
205 			my->flags[INVISIBLE] = false;
206 			serverUpdateEntityFlag(my, INVISIBLE);
207 		}
208 	}
209 	else if ( my->flags[INVISIBLE] && ticks % 50 == 0 && !strncmp(map.name, "Bram's Castle", 13) )
210 	{
211 		node_t* node = nullptr;
212 		Entity* entity = nullptr;
213 		bool bossAlive = false;
214 		for ( node = map.entities->first; node != nullptr; )
215 		{
216 			entity = (Entity*)node->element;
217 			node = node->next;
218 			if ( entity && entity->behavior == &actMonster
219 				&& entity->getMonsterTypeFromSprite() == VAMPIRE
220 				&& !entity->monsterAllyGetPlayerLeader() )
221 			{
222 				Stat* stats = entity->getStats();
223 				if ( stats && !strncmp(stats->name, "Bram Kindly", 11) )
224 				{
225 					bossAlive = true;
226 				}
227 			}
228 		}
229 		if ( !bossAlive )
230 		{
231 			for ( node = map.entities->first; node != nullptr; )
232 			{
233 				entity = (Entity*)node->element;
234 				node = node->next;
235 				if ( entity->behavior == &actMagicTrap )
236 				{
237 					list_RemoveNode(entity->mynode);
238 				}
239 			}
240 			my->flags[INVISIBLE] = false;
241 			serverUpdateEntityFlag(my, INVISIBLE);
242 		}
243 	}
244 
245 	// step through portal
246 	for (i = 0; i < MAXPLAYERS; i++)
247 	{
248 		if ((i == 0 && selectedEntity == my) || (client_selected[i] == my))
249 		{
250 			if (inrange[i])
251 			{
252 				for (c = 0; c < MAXPLAYERS; c++)
253 				{
254 					if (client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr)
255 					{
256 						continue;
257 					}
258 					else
259 					{
260 						playercount++;
261 					}
262 					dist = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
263 					if (dist > TOUCHRANGE)
264 					{
265 						messagePlayer(i, language[505]);
266 						return;
267 					}
268 				}
269 				if (playercount == 1)
270 				{
271 					messagePlayer(i, language[510]);
272 				}
273 				else
274 				{
275 					messagePlayer(i, language[511]);
276 				}
277 				loadnextlevel = true;
278 				if ( secretlevel )
279 				{
280 					switch ( currentlevel )
281 					{
282 						case 9:
283 						{
284 							; //lol
285 							bool visiblegrave = false;
286 							node_t* node;
287 							for ( node = map.entities->first; node != nullptr; node = node->next )
288 							{
289 								Entity* entity = (Entity*)node->element;
290 								if ( entity->sprite == 224 && !entity->flags[INVISIBLE] )
291 								{
292 									visiblegrave = true;
293 									break;
294 								}
295 							}
296 							if ( visiblegrave )
297 							{
298 								for ( c = 0; c < MAXPLAYERS; ++c )
299 								{
300 									steamAchievementClient(c, "BARONY_ACH_ROBBING_THE_CRADLE");
301 								}
302 							}
303 							break;
304 						}
305 						case 14:
306 							for ( c = 0; c < MAXPLAYERS; ++c )
307 							{
308 								steamAchievementClient(c, "BARONY_ACH_THESEUS_LEGACY");
309 							}
310 							break;
311 						case 29:
312 							for ( c = 0; c < MAXPLAYERS; c++ )
313 							{
314 								steamAchievementClient(c, "BARONY_ACH_CULT_FOLLOWING");
315 							}
316 							break;
317 						case 34:
318 							for ( c = 0; c < MAXPLAYERS; c++ )
319 							{
320 								steamAchievementClient(c, "BARONY_ACH_DESPAIR_CALMS");
321 							}
322 							break;
323 						default:
324 							break;
325 					}
326 					if ( strncmp(map.name, "Underworld", 10) )
327 					{
328 						skipLevelsOnLoad = -1; // don't skip a regular level anymore. still skip if in underworld.
329 					}
330 					else
331 					{
332 						// underworld - don't skip on the early sections.
333 						if ( currentlevel == 6 || currentlevel == 7 )
334 						{
335 							skipLevelsOnLoad = -1;
336 						}
337 					}
338 				}
339 				if ( !my->portalNotSecret )
340 				{
341 					secretlevel = (secretlevel == false);  // toggle level lists
342 				}
343 				return;
344 			}
345 		}
346 	}
347 }
348 
actWinningPortal(Entity * my)349 void actWinningPortal(Entity* my)
350 {
351 	int playercount = 0;
352 	double dist;
353 	int i, c;
354 
355 	if ( multiplayer != CLIENT )
356 	{
357 		if ( my->flags[INVISIBLE] )
358 		{
359 			if ( !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9) )
360 			{
361 				if ( !(svFlags & SV_FLAG_CLASSIC) )
362 				{
363 					return; // classic mode disabled.
364 				}
365 			}
366 			node_t* node;
367 			for ( node = map.creatures->first; node != nullptr; node = node->next )
368 			{
369 				Entity* entity = (Entity*)node->element;
370 				if ( entity->behavior == &actMonster )
371 				{
372 					Stat* stats = entity->getStats();
373 					if ( stats )
374 					{
375 						if ( stats->type == LICH || stats->type == DEVIL )
376 						{
377 							return;
378 						}
379 					}
380 				}
381 			}
382 			if ( my->skill[28] != 0 )
383 			{
384 				if ( my->skill[28] == 2 )
385 				{
386 					// powered on.
387 					if ( !my->portalFireAnimation )
388 					{
389 						Entity* timer = createParticleTimer(my, 100, 174);
390 						timer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SPAWN_PORTAL;
391 						timer->particleTimerCountdownSprite = 174;
392 						timer->particleTimerEndAction = PARTICLE_EFFECT_PORTAL_SPAWN;
393 						serverSpawnMiscParticles(my, PARTICLE_EFFECT_PORTAL_SPAWN, 174);
394 						my->portalFireAnimation = 1;
395 					}
396 				}
397 			}
398 		}
399 		else
400 		{
401 			if ( !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9) )
402 			{
403 				if ( !(svFlags & SV_FLAG_CLASSIC) )
404 				{
405 					my->flags[INVISIBLE] = true; // classic mode disabled, hide.
406 					serverUpdateEntityFlag(my, INVISIBLE);
407 					my->portalFireAnimation = 0;
408 				}
409 			}
410 		}
411 	}
412 	else
413 	{
414 		if ( my->flags[INVISIBLE] )
415 		{
416 			return;
417 		}
418 	}
419 
420 	if ( !my->portalInit )
421 	{
422 		my->portalInit = 1;
423 		my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 255);
424 	}
425 
426 	my->portalAmbience--;
427 	if ( my->portalAmbience <= 0 )
428 	{
429 		my->portalAmbience = TICKS_PER_SECOND * 2;
430 		playSoundEntityLocal( my, 154, 128 );
431 	}
432 
433 	my->yaw += 0.01; // rotate slowly on my axis
434 	my->sprite = 278 + (my->ticks / 20) % 4; // animate
435 
436 	if ( multiplayer == CLIENT )
437 	{
438 		return;
439 	}
440 
441 	// step through portal
442 	for (i = 0; i < MAXPLAYERS; i++)
443 	{
444 		if ((i == 0 && selectedEntity == my) || (client_selected[i] == my))
445 		{
446 			if (inrange[i])
447 			{
448 				for (c = 0; c < MAXPLAYERS; c++)
449 				{
450 					if (client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr)
451 					{
452 						continue;
453 					}
454 					else
455 					{
456 						playercount++;
457 					}
458 					dist = sqrt( pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
459 					if (dist > TOUCHRANGE)
460 					{
461 						messagePlayer(i, language[509]);
462 						return;
463 					}
464 				}
465 				victory = my->portalVictoryType;
466 				if ( multiplayer == SERVER )
467 				{
468 					for ( c = 1; c < MAXPLAYERS; c++ )
469 					{
470 						if ( client_disconnected[c] == true )
471 						{
472 							continue;
473 						}
474 						strcpy((char*)net_packet->data, "WING");
475 						net_packet->data[4] = victory;
476 						net_packet->address.host = net_clients[c - 1].host;
477 						net_packet->address.port = net_clients[c - 1].port;
478 						net_packet->len = 8;
479 						sendPacketSafe(net_sock, -1, net_packet, c - 1);
480 					}
481 				}
482 				subwindow = 0;
483 				introstage = 5; // prepares win game sequence
484 				fadeout = true;
485 				if ( !intro )
486 				{
487 					pauseGame(2, false);
488 				}
489 				return;
490 			}
491 		}
492 	}
493 }
494 
actExpansionEndGamePortal(Entity * my)495 void actExpansionEndGamePortal(Entity* my)
496 {
497 	if ( !my )
498 	{
499 		return;
500 	}
501 
502 	my->actExpansionEndGamePortal();
503 }
504 
actExpansionEndGamePortal()505 void Entity::actExpansionEndGamePortal()
506 {
507 	int playercount = 0;
508 	double dist;
509 	int i, c;
510 
511 	if ( multiplayer != CLIENT )
512 	{
513 		if ( flags[INVISIBLE] )
514 		{
515 			node_t* node;
516 			for ( node = map.creatures->first; node != nullptr; node = node->next )
517 			{
518 				Entity* entity = (Entity*)node->element;
519 				if ( entity )
520 				{
521 					if ( entity->behavior == &actMonster )
522 					{
523 						Stat* stats = entity->getStats();
524 						if ( stats )
525 						{
526 							if ( stats->type == LICH_FIRE || stats->type == LICH_ICE )
527 							{
528 								return;
529 							}
530 						}
531 					}
532 				}
533 			}
534 			if ( circuit_status != 0 )
535 			{
536 				if ( circuit_status == CIRCUIT_ON )
537 				{
538 					// powered on.
539 					if ( !portalFireAnimation )
540 					{
541 						Entity* timer = createParticleTimer(this, 100, 174);
542 						timer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SPAWN_PORTAL;
543 						timer->particleTimerCountdownSprite = 174;
544 						timer->particleTimerEndAction = PARTICLE_EFFECT_PORTAL_SPAWN;
545 						serverSpawnMiscParticles(this, PARTICLE_EFFECT_PORTAL_SPAWN, 174);
546 						portalFireAnimation = 1;
547 					}
548 				}
549 			}
550 		}
551 	}
552 	else
553 	{
554 		if ( flags[INVISIBLE] )
555 		{
556 			return;
557 		}
558 	}
559 
560 	if ( !portalInit )
561 	{
562 		portalInit = 1;
563 		light = lightSphereShadow(x / 16, y / 16, 3, 255);
564 	}
565 
566 	portalAmbience--;
567 	if ( portalAmbience <= 0 )
568 	{
569 		portalAmbience = TICKS_PER_SECOND * 2;
570 		playSoundEntityLocal(this, 154, 128);
571 	}
572 
573 	yaw += 0.01; // rotate slowly on my axis
574 	sprite = 614 + (this->ticks / 20) % 4; // animate
575 
576 	if ( multiplayer == CLIENT )
577 	{
578 		return;
579 	}
580 
581 	// step through portal
582 	for ( i = 0; i < MAXPLAYERS; i++ )
583 	{
584 		if ( (i == 0 && selectedEntity == this) || (client_selected[i] == this) )
585 		{
586 			if ( inrange[i] )
587 			{
588 				for ( c = 0; c < MAXPLAYERS; c++ )
589 				{
590 					if ( client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr )
591 					{
592 						continue;
593 					}
594 					else
595 					{
596 						playercount++;
597 					}
598 					dist = sqrt(pow(x - players[c]->entity->x, 2) + pow(y - players[c]->entity->y, 2));
599 					if ( dist > TOUCHRANGE )
600 					{
601 						messagePlayer(i, language[509]);
602 						return;
603 					}
604 				}
605 				victory = portalVictoryType;
606 				if ( multiplayer == SERVER )
607 				{
608 					for ( c = 1; c < MAXPLAYERS; c++ )
609 					{
610 						if ( client_disconnected[c] == true )
611 						{
612 							continue;
613 						}
614 						strcpy((char*)net_packet->data, "WING");
615 						net_packet->data[4] = victory;
616 						net_packet->address.host = net_clients[c - 1].host;
617 						net_packet->address.port = net_clients[c - 1].port;
618 						net_packet->len = 8;
619 						sendPacketSafe(net_sock, -1, net_packet, c - 1);
620 					}
621 				}
622 				subwindow = 0;
623 				introstage = 5; // prepares win game sequence
624 				fadeout = true;
625 				if ( !intro )
626 				{
627 					pauseGame(2, false);
628 				}
629 				return;
630 			}
631 		}
632 	}
633 }
634 
actMidGamePortal(Entity * my)635 void actMidGamePortal(Entity* my)
636 {
637 	if ( !my )
638 	{
639 		return;
640 	}
641 
642 	my->actMidGamePortal();
643 }
644 
actMidGamePortal()645 void Entity::actMidGamePortal()
646 {
647 	int playercount = 0;
648 	double dist;
649 	int i, c;
650 
651 	if ( multiplayer != CLIENT )
652 	{
653 		if ( flags[INVISIBLE] )
654 		{
655 			if ( !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9) )
656 			{
657 				if ( (svFlags & SV_FLAG_CLASSIC) )
658 				{
659 					return; // classic mode enabled, don't process.
660 				}
661 			}
662 			node_t* node;
663 			for ( node = map.creatures->first; node != nullptr; node = node->next )
664 			{
665 				Entity* entity = (Entity*)node->element;
666 				if ( entity )
667 				{
668 					if ( entity->behavior == &actMonster )
669 					{
670 						Stat* stats = entity->getStats();
671 						if ( stats )
672 						{
673 							if ( stats->type == LICH || stats->type == DEVIL )
674 							{
675 								return;
676 							}
677 						}
678 					}
679 				}
680 			}
681 			if ( circuit_status != 0 )
682 			{
683 				if ( circuit_status == CIRCUIT_ON )
684 				{
685 					// powered on.
686 					if ( !portalFireAnimation )
687 					{
688 						Entity* timer = createParticleTimer(this, 100, 174);
689 						timer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SPAWN_PORTAL;
690 						timer->particleTimerCountdownSprite = 174;
691 						timer->particleTimerEndAction = PARTICLE_EFFECT_PORTAL_SPAWN;
692 						serverSpawnMiscParticles(this, PARTICLE_EFFECT_PORTAL_SPAWN, 174);
693 						portalFireAnimation = 1;
694 					}
695 				}
696 			}
697 		}
698 		else
699 		{
700 			if ( !strncmp(map.name, "Boss", 4) || !strncmp(map.name, "Hell Boss", 9) )
701 			{
702 				if ( (svFlags & SV_FLAG_CLASSIC) )
703 				{
704 					flags[INVISIBLE] = true; // classic mode enabled, hide.
705 					serverUpdateEntityFlag(this, INVISIBLE);
706 					portalFireAnimation = 0;
707 				}
708 			}
709 		}
710 	}
711 	else
712 	{
713 		if ( flags[INVISIBLE] )
714 		{
715 			return;
716 		}
717 	}
718 
719 	if ( !portalInit )
720 	{
721 		portalInit = 1;
722 		light = lightSphereShadow(x / 16, y / 16, 3, 255);
723 	}
724 
725 	portalAmbience--;
726 	if ( portalAmbience <= 0 )
727 	{
728 		portalAmbience = TICKS_PER_SECOND * 2;
729 		playSoundEntityLocal(this, 154, 128);
730 	}
731 
732 	yaw += 0.01; // rotate slowly on my axis
733 	sprite = 614 + (this->ticks / 20) % 4; // animate
734 
735 	if ( multiplayer == CLIENT )
736 	{
737 		return;
738 	}
739 
740 	// step through portal
741 	for ( i = 0; i < MAXPLAYERS; i++ )
742 	{
743 		if ( (i == 0 && selectedEntity == this) || (client_selected[i] == this) )
744 		{
745 			if ( inrange[i] )
746 			{
747 				for ( c = 0; c < MAXPLAYERS; c++ )
748 				{
749 					if ( client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr )
750 					{
751 						continue;
752 					}
753 					else
754 					{
755 						playercount++;
756 					}
757 					dist = sqrt(pow(x - players[c]->entity->x, 2) + pow(y - players[c]->entity->y, 2));
758 					if ( dist > TOUCHRANGE )
759 					{
760 						messagePlayer(i, language[509]);
761 						return;
762 					}
763 				}
764 				//victory = portalVictoryType;
765 				int movieCrawlType = -1;
766 				if ( !strncmp(map.name, "Hell Boss", 9) )
767 				{
768 					movieCrawlType = MOVIE_MIDGAME_BAPHOMET_HUMAN_AUTOMATON;
769 					if ( stats[0] && stats[0]->playerRace > 0 && stats[0]->playerRace != RACE_AUTOMATON )
770 					{
771 						movieCrawlType = MOVIE_MIDGAME_BAPHOMET_MONSTERS;
772 					}
773 				}
774 				else if ( !strncmp(map.name, "Boss", 4) )
775 				{
776 					if ( stats[0] && stats[0]->playerRace > 0 && stats[0]->playerRace != RACE_AUTOMATON )
777 					{
778 						movieCrawlType = MOVIE_MIDGAME_HERX_MONSTERS;
779 					}
780 				}
781 				int introstageToChangeTo = 9;
782 				if ( movieCrawlType >= 0 )
783 				{
784 					introstageToChangeTo = 11 + movieCrawlType;
785 				}
786 
787 				if ( multiplayer == SERVER )
788 				{
789 					for ( c = 1; c < MAXPLAYERS; c++ )
790 					{
791 						if ( client_disconnected[c] == true )
792 						{
793 							continue;
794 						}
795 						strcpy((char*)net_packet->data, "MIDG");
796 						net_packet->data[4] = introstageToChangeTo;
797 						net_packet->address.host = net_clients[c - 1].host;
798 						net_packet->address.port = net_clients[c - 1].port;
799 						net_packet->len = 8;
800 						sendPacketSafe(net_sock, -1, net_packet, c - 1);
801 					}
802 				}
803 				subwindow = 0;
804 				fadeout = true;
805 				if ( !intro )
806 				{
807 					pauseGame(2, false);
808 				}
809 				introstage = introstageToChangeTo; // prepares mid game sequence
810 				return;
811 			}
812 		}
813 	}
814 }
815 
customPortalLookForMapWithName(char * mapToSearch,bool isSecretLevel,int levelOffset)816 int customPortalLookForMapWithName(char* mapToSearch, bool isSecretLevel, int levelOffset)
817 {
818 	if ( !mapToSearch )
819 	{
820 		return -1000;
821 	}
822 	std::string mapsDirectory; // store the full file path here.
823 	if ( !isSecretLevel )
824 	{
825 		mapsDirectory = PHYSFS_getRealDir(LEVELSFILE);
826 		mapsDirectory.append(PHYSFS_getDirSeparator()).append(LEVELSFILE);
827 	}
828 	else
829 	{
830 		mapsDirectory = PHYSFS_getRealDir(SECRETLEVELSFILE);
831 		mapsDirectory.append(PHYSFS_getDirSeparator()).append(SECRETLEVELSFILE);
832 	}
833 	printlog("Maps directory: %s", mapsDirectory.c_str());
834 	std::vector<std::string> levelsList = getLinesFromDataFile(mapsDirectory);
835 	std::string line = levelsList.front();
836 	int levelsCounted = 0;
837 
838 	std::vector<int> eligibleLevels;
839 
840 	for ( auto it = levelsList.begin(); it != levelsList.end(); ++it )
841 	{
842 		line = *it;
843 		if ( line[0] == '\n' )
844 		{
845 			continue;
846 		}
847 
848 		// find the actual map name, ignoring gen: or map: in the line.
849 		std::size_t found = line.find(' ');
850 		std::string mapName;
851 		if ( found != std::string::npos )
852 		{
853 			std::string mapType = line.substr(0, found);
854 			mapName = line.substr(found + 1, line.find('\n'));
855 			std::size_t carriageReturn = mapName.find('\r');
856 			if ( carriageReturn != std::string::npos )
857 			{
858 				mapName.erase(carriageReturn);
859 			}
860 			if ( mapType.compare("map:") == 0 )
861 			{
862 				/*strncpy(tempstr, mapName.c_str(), mapName.length());
863 				tempstr[mapName.length()] = '\0';*/
864 			}
865 			else if ( mapType.compare("gen:") == 0 )
866 			{
867 				mapName = mapName.substr(0, mapName.find_first_of(" \0"));
868 				/*strncpy(tempstr, mapName.c_str(), mapName.length());
869 				tempstr[mapName.length()] = '\0';*/
870 			}
871 		}
872 		if ( !strcmp(mapToSearch, mapName.c_str()) )
873 		{
874 			// found an entry matching our map name.
875 			eligibleLevels.push_back(levelsCounted);
876 		}
877 		++levelsCounted;
878 	}
879 
880 	if ( eligibleLevels.empty() )
881 	{
882 		std::string mapPath = "maps/";
883 		mapPath.append(mapToSearch);
884 		if ( mapPath.find(".lmp") == std::string::npos )
885 		{
886 			mapPath.append(".lmp");
887 		}
888 		if ( !PHYSFS_getRealDir(mapPath.c_str()) )
889 		{
890 			// could not find the map.
891 			return -998;
892 		}
893 		else
894 		{
895 			loadCustomNextMap = mapToSearch;
896 			return -999;
897 		}
898 	}
899 
900 	int min = eligibleLevels.front();
901 	int max = eligibleLevels.back();
902 
903 	if ( eligibleLevels.size() == 1 )
904 	{
905 		return eligibleLevels.front();
906 	}
907 	else if ( currentlevel > max )
908 	{
909 		// all the maps are behind us.
910 		if ( levelOffset >= 0 )
911 		{
912 			return max; // going backwards to the nearest element.
913 		}
914 		else
915 		{
916 			auto it = eligibleLevels.rbegin();
917 			int lastGoodMapIndex = *it;
918 			for ( ; it != eligibleLevels.rend() && levelOffset != 0; ++it )
919 			{
920 				++levelOffset;
921 				lastGoodMapIndex = *it;
922 			}
923 			return lastGoodMapIndex;
924 		}
925 	}
926 	else if ( currentlevel < min )
927 	{
928 		// all the maps are ahead of us.
929 		if ( levelOffset <= 0 )
930 		{
931 			return min; // going forwards to the nearest element.
932 		}
933 		else
934 		{
935 			auto it = eligibleLevels.begin();
936 			int lastGoodMapIndex = *it;
937 			for ( ; it != eligibleLevels.end() && levelOffset != 0; ++it )
938 			{
939 				--levelOffset;
940 				lastGoodMapIndex = *it;
941 			}
942 			return lastGoodMapIndex;
943 		}
944 	}
945 	else
946 	{
947 		// we're in the middle of the range.
948 		// find our position, then move the offset amount.
949 		if ( levelOffset >= 0 )
950 		{
951 			auto find = eligibleLevels.begin();
952 			for ( auto it = eligibleLevels.begin(); it != eligibleLevels.end(); ++it )
953 			{
954 				if ( *find > currentlevel )
955 				{
956 					break;
957 				}
958 				find = it;
959 			}
960 
961 			if ( levelOffset == 0 )
962 			{
963 				return *find;
964 			}
965 
966 			int lastGoodMapIndex = *find;
967 			for ( auto it = find; it != eligibleLevels.end() && levelOffset != 0; ++it )
968 			{
969 				lastGoodMapIndex = *it;
970 				--levelOffset;
971 			}
972 			return lastGoodMapIndex;
973 		}
974 		else if ( levelOffset < 0 )
975 		{
976 			auto find = eligibleLevels.rbegin();
977 			for ( auto it = eligibleLevels.rbegin(); it != eligibleLevels.rend(); ++it )
978 			{
979 				if ( *find < currentlevel )
980 				{
981 					break;
982 				}
983 				find = it;
984 			}
985 
986 			int lastGoodMapIndex = *find;
987 			for ( auto it = find; it != eligibleLevels.rend() && levelOffset != 0; ++it )
988 			{
989 				lastGoodMapIndex = *it;
990 				++levelOffset;
991 			}
992 			return lastGoodMapIndex;
993 		}
994 	}
995 
996 	return -1000;
997 }
998 
actCustomPortal(Entity * my)999 void actCustomPortal(Entity* my)
1000 {
1001 	int playercount = 0;
1002 	double dist;
1003 	int i, c;
1004 
1005 	if ( multiplayer != CLIENT )
1006 	{
1007 		if ( my->flags[INVISIBLE] )
1008 		{
1009 			if ( my->skill[28] != 0 )
1010 			{
1011 				if ( my->skill[28] == 2 )
1012 				{
1013 					// powered on.
1014 					if ( !my->portalFireAnimation && my->portalCustomSpriteAnimationFrames > 0 )
1015 					{
1016 						Entity* timer = createParticleTimer(my, 100, 174);
1017 						timer->particleTimerCountdownAction = PARTICLE_TIMER_ACTION_SPAWN_PORTAL;
1018 						timer->particleTimerCountdownSprite = 174;
1019 						timer->particleTimerEndAction = PARTICLE_EFFECT_PORTAL_SPAWN;
1020 						serverSpawnMiscParticles(my, PARTICLE_EFFECT_PORTAL_SPAWN, 174);
1021 						my->portalFireAnimation = 1;
1022 					}
1023 				}
1024 			}
1025 		}
1026 	}
1027 	else
1028 	{
1029 		if ( my->flags[INVISIBLE] )
1030 		{
1031 			return;
1032 		}
1033 	}
1034 
1035 	if ( !my->portalInit )
1036 	{
1037 		my->portalInit = 1;
1038 		if ( my->portalCustomSpriteAnimationFrames > 0 )
1039 		{
1040 			my->light = lightSphereShadow(my->x / 16, my->y / 16, 3, 255);
1041 		}
1042 	}
1043 
1044 	my->portalAmbience--;
1045 	if ( my->portalAmbience <= 0 )
1046 	{
1047 		if ( my->portalCustomSpriteAnimationFrames > 0 )
1048 		{
1049 			my->portalAmbience = TICKS_PER_SECOND * 2; // portal whirr
1050 			playSoundEntityLocal(my, 154, 128);
1051 		}
1052 		else
1053 		{
1054 			my->portalAmbience = TICKS_PER_SECOND * 30; // trap hum
1055 			playSoundEntityLocal(my, 149, 64);
1056 		}
1057 	}
1058 
1059 	if ( my->portalCustomSpriteAnimationFrames > 0 )
1060 	{
1061 		my->yaw += 0.01; // rotate slowly on my axis
1062 		my->sprite = my->portalCustomSprite + (my->ticks / 20) % my->portalCustomSpriteAnimationFrames; // animate
1063 	}
1064 	else
1065 	{
1066 		my->sprite = my->portalCustomSprite;
1067 	}
1068 
1069 	if ( multiplayer == CLIENT )
1070 	{
1071 		return;
1072 	}
1073 
1074 	// step through portal
1075 	for ( i = 0; i < MAXPLAYERS; i++ )
1076 	{
1077 		if ( (i == 0 && selectedEntity == my) || (client_selected[i] == my) )
1078 		{
1079 			if ( inrange[i] )
1080 			{
1081 				for ( c = 0; c < MAXPLAYERS; c++ )
1082 				{
1083 					if ( client_disconnected[c] || players[c] == nullptr || players[c]->entity == nullptr )
1084 					{
1085 						continue;
1086 					}
1087 					else
1088 					{
1089 						playercount++;
1090 					}
1091 					dist = sqrt(pow(my->x - players[c]->entity->x, 2) + pow(my->y - players[c]->entity->y, 2));
1092 					if ( dist > TOUCHRANGE )
1093 					{
1094 						messagePlayer(i, language[509]);
1095 						return;
1096 					}
1097 				}
1098 				if ( playercount == 1 )
1099 				{
1100 					messagePlayer(i, language[506]);
1101 				}
1102 				else
1103 				{
1104 					messagePlayer(i, language[507]);
1105 				}
1106 				loadnextlevel = true;
1107 				skipLevelsOnLoad = 0;
1108 
1109 				if ( gameModeManager.getMode() == GameModeManager_t::GAME_MODE_TUTORIAL )
1110 				{
1111 					std::string mapname = map.name;
1112 					if ( mapname.find("Tutorial Hub") == std::string::npos
1113 						&& mapname.find("Tutorial ") != std::string::npos )
1114 					{
1115 						achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_DIPLOMA, AchievementObserver::DIPLOMA_LEVEL_COMPLETE);
1116 						achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_BACK_TO_BASICS, AchievementObserver::BACK_TO_BASICS_LEVEL_COMPLETE);
1117 						int number = stoi(mapname.substr(mapname.find("Tutorial ") + strlen("Tutorial "), 2));
1118 						auto& tutorialLevels = gameModeManager.Tutorial.levels;
1119 						if ( number >= 1 && number < tutorialLevels.size() )
1120 						{
1121 							if ( tutorialLevels.at(number).completionTime == 0 )
1122 							{
1123 								tutorialLevels.at(number).completionTime = completionTime; // first time score.
1124 							}
1125 							else
1126 							{
1127 								tutorialLevels.at(number).completionTime = std::min(tutorialLevels.at(number).completionTime, completionTime);
1128 							}
1129 							achievementObserver.updateGlobalStat(
1130 								std::min(STEAM_GSTAT_TUTORIAL1_COMPLETED - 1 + number, static_cast<int>(STEAM_GSTAT_TUTORIAL10_COMPLETED)));
1131 							achievementObserver.updateGlobalStat(
1132 								std::min(STEAM_GSTAT_TUTORIAL1_ATTEMPTS - 1 + number, static_cast<int>(STEAM_GSTAT_TUTORIAL10_ATTEMPTS)));
1133 						}
1134 						completionTime = 0;
1135 						gameModeManager.Tutorial.writeToDocument();
1136 						achievementObserver.updatePlayerAchievement(clientnum, AchievementObserver::BARONY_ACH_FAST_LEARNER, AchievementObserver::FAST_LEARNER_TIME_UPDATE);
1137 					}
1138 					else if ( mapname.find("Tutorial Hub") != std::string::npos )
1139 					{
1140 						completionTime = 0;
1141 					}
1142 				}
1143 
1144 				if ( my->portalCustomLevelText1 != 0 )
1145 				{
1146 					// we're looking for a specific map name.
1147 					char mapName[64] = "";
1148 					int totalChars = 0;
1149 					for ( int i = 11; i <= 18; ++i ) // starting from my->portalCustomMapText1 to my->portalCustomMapText8, 32 chars
1150 					{
1151 						if ( my->skill[i] != 0 && i != 28 ) // skill[28] is circuit status.
1152 						{
1153 							for ( int c = 0; c < 4; ++c )
1154 							{
1155 								if ( static_cast<char>((my->skill[i] >> (c * 8)) & 0xFF) == '\0'
1156 									&& i != 18 && my->skill[i + 1] != 0 )
1157 								{
1158 									// don't add '\0' termination unless the next skill slot is empty as we have more data to read.
1159 								}
1160 								else
1161 								{
1162 									mapName[totalChars] = static_cast<char>((my->skill[i] >> (c * 8)) & 0xFF);
1163 									++totalChars;
1164 								}
1165 							}
1166 						}
1167 					}
1168 					if ( mapName[totalChars] != '\0' )
1169 					{
1170 						mapName[totalChars] = '\0';
1171 					}
1172 					int levelToJumpTo = customPortalLookForMapWithName(mapName, my->portalNotSecret ? false : true, my->portalCustomLevelsToJump);
1173 					if ( levelToJumpTo == -1000 )
1174 					{
1175 						// error.
1176 						printlog("Warning: Error in map teleport!");
1177 						return;
1178 					}
1179 					else if ( levelToJumpTo == -999 )
1180 					{
1181 						// custom level not in the levels list, but was found in the maps folder.
1182 						// we've set the next map to warp to.
1183 						if ( my->portalCustomLevelsToJump - currentlevel > 0 )
1184 						{
1185 							skipLevelsOnLoad = my->portalCustomLevelsToJump - currentlevel;
1186 						}
1187 						else
1188 						{
1189 							skipLevelsOnLoad = my->portalCustomLevelsToJump - currentlevel - 1;
1190 						}
1191 						if ( skipLevelsOnLoad == -1 )
1192 						{
1193 							loadingSameLevelAsCurrent = true;
1194 						}
1195 						if ( my->portalNotSecret )
1196 						{
1197 							secretlevel = false;
1198 						}
1199 						else
1200 						{
1201 							secretlevel = true;
1202 						}
1203 						return;
1204 					}
1205 					else if ( levelToJumpTo == -998 )
1206 					{
1207 						// could not find the map name anywhere.
1208 						loadnextlevel = false;
1209 						skipLevelsOnLoad = 0;
1210 						messagePlayer(i, "Error: Map %s was not found in the maps folder!", mapName);
1211 						return;
1212 					}
1213 					int levelDifference = currentlevel - levelToJumpTo;
1214 					if ( levelDifference == 0 && ((my->portalNotSecret && !secretlevel) || (!my->portalNotSecret && secretlevel)) )
1215 					{
1216 						//// error, we're reloading the same position, will glitch out clients.
1217 						//loadnextlevel = false;
1218 						//skipLevelsOnLoad = 0;
1219 						//messagePlayer(i, "Error: Map to teleport to (%s) is the same position as current!", mapName);
1220 						//return;
1221 						loadingSameLevelAsCurrent = true; // update - can handle this now.
1222 					}
1223 
1224 					if ( levelToJumpTo - currentlevel > 0 )
1225 					{
1226 						skipLevelsOnLoad = levelToJumpTo - currentlevel;
1227 					}
1228 					else
1229 					{
1230 						skipLevelsOnLoad = levelToJumpTo - currentlevel - 1;
1231 					}
1232 					if ( my->portalNotSecret )
1233 					{
1234 						secretlevel = false;
1235 					}
1236 					else
1237 					{
1238 						secretlevel = true;
1239 					}
1240 				}
1241 				else
1242 				{
1243 					if ( !my->portalNotSecret )
1244 					{
1245 						secretlevel = (secretlevel == false);    // toggle level lists
1246 						skipLevelsOnLoad = -1; // don't skip levels when toggling.
1247 					}
1248 					skipLevelsOnLoad += my->portalCustomLevelsToJump;
1249 				}
1250 				return;
1251 			}
1252 		}
1253 	}
1254 }