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 }