1 /*
2 C-Dogs SDL
3 A port of the legendary (and fun) action/arcade cdogs.
4 Copyright (C) 1995 Ronny Wester
5 Copyright (C) 2003 Jeremy Chin
6 Copyright (C) 2003-2007 Lucas Martin-King
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
22 This file incorporates work covered by the following copyright and
23 permission notice:
24
25 Copyright (c) 2013-2017, 2019-2021 Cong Xu
26 All rights reserved.
27
28 Redistribution and use in source and binary forms, with or without
29 modification, are permitted provided that the following conditions are met:
30
31 Redistributions of source code must retain the above copyright notice, this
32 list of conditions and the following disclaimer.
33 Redistributions in binary form must reproduce the above copyright notice,
34 this list of conditions and the following disclaimer in the documentation
35 and/or other materials provided with the distribution.
36
37 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
38 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
39 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
40 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
41 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
42 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
43 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
44 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
45 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
46 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
47 POSSIBILITY OF SUCH DAMAGE.
48 */
49 #include "mission.h"
50
51 #include <assert.h>
52 #include <stdlib.h>
53 #include <string.h>
54
55 #include "actors.h"
56 #include "color.h"
57 #include "defs.h"
58 #include "door.h"
59 #include "files.h"
60 #include "game_events.h"
61 #include "gamedata.h"
62 #include "map.h"
63 #include "map_new.h"
64 #include "music.h"
65 #include "net_util.h"
66 #include "objs.h"
67 #include "palette.h"
68 #include "particle.h"
69 #include "pic_manager.h"
70 #include "pickup.h"
71 #include "triggers.h"
72
73 color_t colorDoor = {172, 172, 172, 255};
74 color_t colorYellowDoor = {252, 224, 0, 255};
75 color_t colorGreenDoor = {0, 252, 0, 255};
76 color_t colorBlueDoor = {0, 252, 252, 255};
77 color_t colorRedDoor = {132, 0, 0, 255};
78
StrKeycard(const char * s)79 int StrKeycard(const char *s)
80 {
81 S2T(FLAGS_KEYCARD_YELLOW, "yellow");
82 S2T(FLAGS_KEYCARD_GREEN, "green");
83 S2T(FLAGS_KEYCARD_BLUE, "blue");
84 S2T(FLAGS_KEYCARD_RED, "red");
85 return 0;
86 }
KeyColor(const int flags)87 color_t KeyColor(const int flags)
88 {
89 switch (flags)
90 {
91 case FLAGS_KEYCARD_YELLOW:
92 return colorYellowDoor;
93 case FLAGS_KEYCARD_GREEN:
94 return colorGreenDoor;
95 case FLAGS_KEYCARD_BLUE:
96 return colorBlueDoor;
97 case FLAGS_KEYCARD_RED:
98 return colorRedDoor;
99 default:
100 return colorDoor;
101 }
102 }
103
MapTypeStr(MapType t)104 const char *MapTypeStr(MapType t)
105 {
106 switch (t)
107 {
108 T2S(MAPTYPE_CLASSIC, "Classic");
109 T2S(MAPTYPE_STATIC, "Static");
110 T2S(MAPTYPE_CAVE, "Cave");
111 T2S(MAPTYPE_INTERIOR, "Interior");
112 default:
113 return "";
114 }
115 }
StrMapType(const char * s)116 MapType StrMapType(const char *s)
117 {
118 S2T(MAPTYPE_CLASSIC, "Classic");
119 S2T(MAPTYPE_STATIC, "Static");
120 S2T(MAPTYPE_CAVE, "Cave");
121 S2T(MAPTYPE_INTERIOR, "Interior");
122 return MAPTYPE_CLASSIC;
123 }
124
MissionInit(Mission * m)125 void MissionInit(Mission *m)
126 {
127 memset(m, 0, sizeof *m);
128 // Initialise with default styles
129 strcpy(m->ExitStyle, IntExitStyle(0));
130 strcpy(m->KeyStyle, IntKeyStyle(0));
131 CArrayInit(&m->Objectives, sizeof(Objective));
132 CArrayInit(&m->Enemies, sizeof(int));
133 CArrayInit(&m->SpecialChars, sizeof(int));
134 CArrayInit(&m->MapObjectDensities, sizeof(MapObjectDensity));
135 CArrayInit(&m->Weapons, sizeof(const WeaponClass *));
136 m->Type = MAPTYPE_CLASSIC;
137 m->u.Classic.ExitEnabled = true;
138 MissionTileClassesInitDefault(&m->u.Classic.TileClasses);
139 }
140
MissionGetTileClassesC(const Mission * m)141 static const MissionTileClasses *MissionGetTileClassesC(const Mission *m)
142 {
143 switch (m->Type)
144 {
145 case MAPTYPE_CLASSIC:
146 return &m->u.Classic.TileClasses;
147 case MAPTYPE_STATIC:
148 return NULL;
149 case MAPTYPE_CAVE:
150 return &m->u.Cave.TileClasses;
151 case MAPTYPE_INTERIOR:
152 return &m->u.Interior.TileClasses;
153 default:
154 return NULL;
155 }
156 }
MissionCopy(Mission * dst,const Mission * src)157 void MissionCopy(Mission *dst, const Mission *src)
158 {
159 if (src == NULL)
160 {
161 return;
162 }
163 MissionTerminate(dst);
164 MissionInit(dst);
165 if (src->Title)
166 {
167 CSTRDUP(dst->Title, src->Title);
168 }
169 if (src->Description)
170 {
171 CSTRDUP(dst->Description, src->Description);
172 }
173 dst->Size = src->Size;
174
175 strcpy(dst->ExitStyle, src->ExitStyle);
176 strcpy(dst->KeyStyle, src->KeyStyle);
177
178 CA_FOREACH(const Objective, srco, src->Objectives)
179 Objective dsto;
180 ObjectiveCopy(&dsto, srco);
181 CArrayPushBack(&dst->Objectives, &dsto);
182 CA_FOREACH_END()
183 CArrayCopy(&dst->Enemies, &src->Enemies);
184 CArrayCopy(&dst->SpecialChars, &src->SpecialChars);
185 CArrayCopy(&dst->MapObjectDensities, &src->MapObjectDensities);
186
187 dst->EnemyDensity = src->EnemyDensity;
188 CArrayCopy(&dst->Weapons, &src->Weapons);
189
190 dst->Music = src->Music;
191 switch (src->Music.Type)
192 {
193 case MUSIC_SRC_GENERAL:
194 break;
195 case MUSIC_SRC_DYNAMIC:
196 if (src->Music.Data.Filename)
197 {
198 CSTRDUP(dst->Music.Data.Filename, src->Music.Data.Filename);
199 }
200 break;
201 case MUSIC_SRC_CHUNK:
202 // TODO: can't copy music chunks, only used by editor anyway
203 memset(&dst->Music.Data.Chunk, 0, sizeof dst->Music.Data.Chunk);
204 break;
205 default:
206 CASSERT(false, "unsupported music type");
207 break;
208 }
209
210 memcpy(&dst->u, &src->u, sizeof dst->u);
211 switch (src->Type)
212 {
213 case MAPTYPE_CLASSIC:
214 MissionTileClassesCopy(
215 MissionGetTileClasses(dst), MissionGetTileClassesC(src));
216 break;
217 case MAPTYPE_STATIC:
218 MissionStaticCopy(&dst->u.Static, &src->u.Static);
219 break;
220 case MAPTYPE_CAVE:
221 MissionTileClassesCopy(
222 MissionGetTileClasses(dst), MissionGetTileClassesC(src));
223 break;
224 case MAPTYPE_INTERIOR:
225 MissionTileClassesCopy(
226 MissionGetTileClasses(dst), MissionGetTileClassesC(src));
227 break;
228 default:
229 break;
230 }
231
232 // Copy type at the end so we can do type-specific conversions before this
233 dst->Type = src->Type;
234 }
235
MissionTerminate(Mission * m)236 void MissionTerminate(Mission *m)
237 {
238 if (m == NULL)
239 return;
240 CFREE(m->Title);
241 CFREE(m->Description);
242 CA_FOREACH(Objective, o, m->Objectives)
243 ObjectiveTerminate(o);
244 CA_FOREACH_END()
245 CArrayTerminate(&m->Objectives);
246 CArrayTerminate(&m->Enemies);
247 CArrayTerminate(&m->SpecialChars);
248 CArrayTerminate(&m->MapObjectDensities);
249 CArrayTerminate(&m->Weapons);
250 switch (m->Type)
251 {
252 case MAPTYPE_CLASSIC:
253 MissionTileClassesTerminate(MissionGetTileClasses(m));
254 break;
255 case MAPTYPE_STATIC:
256 MissionStaticTerminate(&m->u.Static);
257 break;
258 case MAPTYPE_CAVE:
259 MissionTileClassesTerminate(MissionGetTileClasses(m));
260 break;
261 case MAPTYPE_INTERIOR:
262 MissionTileClassesTerminate(MissionGetTileClasses(m));
263 break;
264 default:
265 CASSERT(false, "unknown map type");
266 break;
267 }
268 switch (m->Music.Type)
269 {
270 case MUSIC_SRC_DYNAMIC:
271 CFREE(m->Music.Data.Filename);
272 break;
273 case MUSIC_SRC_CHUNK:
274 MusicChunkTerminate(&m->Music.Data.Chunk);
275 break;
276 default:
277 break;
278 }
279 memset(m, 0, sizeof *m);
280 }
281
MissionGetTileClasses(Mission * m)282 MissionTileClasses *MissionGetTileClasses(Mission *m)
283 {
284 switch (m->Type)
285 {
286 case MAPTYPE_CLASSIC:
287 return &m->u.Classic.TileClasses;
288 case MAPTYPE_STATIC:
289 return NULL;
290 case MAPTYPE_CAVE:
291 return &m->u.Cave.TileClasses;
292 case MAPTYPE_INTERIOR:
293 return &m->u.Interior.TileClasses;
294 default:
295 return NULL;
296 }
297 }
298
299 // +-----------------------+
300 // | And now the code... |
301 // +-----------------------+
302
SetupBadguysForMission(Mission * mission)303 static void SetupBadguysForMission(Mission *mission)
304 {
305 CharacterStore *s = &gCampaign.Setting.characters;
306
307 CharacterStoreResetOthers(s);
308
309 if (s->OtherChars.size == 0)
310 {
311 return;
312 }
313
314 CA_FOREACH(const Objective, o, mission->Objectives)
315 if (o->Type == OBJECTIVE_RESCUE)
316 {
317 CharacterStoreAddPrisoner(s, o->u.Index);
318 break; // TODO: multiple prisoners
319 }
320 CA_FOREACH_END()
321
322 CA_FOREACH(int, e, mission->Enemies)
323 CharacterStoreAddBaddie(s, *e);
324 CA_FOREACH_END()
325
326 CA_FOREACH(int, sc, mission->SpecialChars)
327 CharacterStoreAddSpecial(s, *sc);
328 CA_FOREACH_END()
329 }
330
SetupObjectives(Mission * m)331 static void SetupObjectives(Mission *m)
332 {
333 CA_FOREACH(Objective, o, m->Objectives)
334 ObjectiveSetup(o);
335 CASSERT(_ca_index < OBJECTIVE_MAX_OLD, "too many objectives");
336 CA_FOREACH_END()
337 }
338
SetupWeapons(CArray * to,CArray * from)339 static void SetupWeapons(CArray *to, CArray *from)
340 {
341 CArrayCopy(to, from);
342 }
343
SetupMission(Mission * m,struct MissionOptions * mo,int missionIndex)344 void SetupMission(Mission *m, struct MissionOptions *mo, int missionIndex)
345 {
346 MissionOptionsInit(mo);
347 mo->index = missionIndex;
348 mo->missionData = m;
349
350 ActorsInit();
351 ObjsInit();
352 MobObjsInit();
353 PickupsInit();
354 ParticlesInit(&gParticles);
355 WatchesInit();
356 SetupObjectives(m);
357 SetupBadguysForMission(m);
358 SetupWeapons(&mo->Weapons, &m->Weapons);
359 }
MissionSetupTileClasses(PicManager * pm,const MissionTileClasses * mtc)360 void MissionSetupTileClasses(PicManager *pm, const MissionTileClasses *mtc)
361 {
362 SetupWallTileClasses(pm, &mtc->Wall);
363 SetupFloorTileClasses(pm, &mtc->Floor);
364 SetupFloorTileClasses(pm, &mtc->Room);
365 SetupDoorTileClasses(pm, &mtc->Door);
366 }
MissionTileClassesInitDefault(MissionTileClasses * mtc)367 void MissionTileClassesInitDefault(MissionTileClasses *mtc)
368 {
369 TileClassInit(
370 &mtc->Wall, &gPicManager, &gTileWall, IntWallStyle(0),
371 TileClassBaseStyleType(TILE_CLASS_WALL), colorBattleshipGrey,
372 colorOfficeGreen);
373 TileClassInit(
374 &mtc->Floor, &gPicManager, &gTileFloor, IntFloorStyle(0),
375 TileClassBaseStyleType(TILE_CLASS_FLOOR), colorGravel,
376 colorOfficeGreen);
377 TileClassInit(
378 &mtc->Room, &gPicManager, &gTileRoom, IntRoomStyle(0),
379 TileClassBaseStyleType(TILE_CLASS_FLOOR), colorDoveGray,
380 colorOfficeGreen);
381 TileClassInit(
382 &mtc->Door, &gPicManager, &gTileDoor, IntDoorStyle(0),
383 TileClassBaseStyleType(TILE_CLASS_DOOR), colorWhite, colorWhite);
384 }
MissionTileClassesCopy(MissionTileClasses * dst,const MissionTileClasses * src)385 void MissionTileClassesCopy(
386 MissionTileClasses *dst, const MissionTileClasses *src)
387 {
388 if (dst == NULL || src == NULL)
389 return;
390 TileClassCopy(&dst->Door, &src->Door);
391 TileClassCopy(&dst->Floor, &src->Floor);
392 TileClassCopy(&dst->Wall, &src->Wall);
393 TileClassCopy(&dst->Room, &src->Room);
394 }
MissionTileClassesTerminate(MissionTileClasses * mtc)395 void MissionTileClassesTerminate(MissionTileClasses *mtc)
396 {
397 TileClassTerminate(&mtc->Wall);
398 TileClassTerminate(&mtc->Floor);
399 TileClassTerminate(&mtc->Room);
400 TileClassTerminate(&mtc->Door);
401 }
402
403 static int ObjectiveActorsAlive(const int objective);
MissionSetMessageIfComplete(struct MissionOptions * options)404 void MissionSetMessageIfComplete(struct MissionOptions *options)
405 {
406 if (!gCampaign.IsClient)
407 {
408 if (CanCompleteMission(options) && !gMission.MissionCompleted)
409 {
410 GameEvent msg = GameEventNew(GAME_EVENT_MISSION_COMPLETE);
411 msg.u.MissionComplete = NMakeMissionComplete(options);
412 GameEventsEnqueue(&gGameEvents, msg);
413 }
414 else if (options->HasBegun && gCampaign.Entry.Mode == GAME_MODE_NORMAL)
415 {
416 // Check if the game is impossible to end
417 // i.e. not enough rescue objectives left alive
418 CA_FOREACH(const Objective, o, options->missionData->Objectives)
419 if (o->Type == OBJECTIVE_RESCUE)
420 {
421 if (ObjectiveActorsAlive(_ca_index) < o->Required)
422 {
423 GameEvent e = GameEventNew(GAME_EVENT_MISSION_END);
424 e.u.MissionEnd.Delay = GAME_OVER_DELAY;
425 strcpy(e.u.MissionEnd.Msg, "Mission failed");
426 GameEventsEnqueue(&gGameEvents, e);
427 }
428 }
429 CA_FOREACH_END()
430 }
431 }
432 }
433 // Get the number of actors alive for an objective
ObjectiveActorsAlive(const int objective)434 static int ObjectiveActorsAlive(const int objective)
435 {
436 int count = 0;
437 CA_FOREACH(const TActor, a, gActors)
438 if (a->isInUse && a->health > 0 &&
439 ObjectiveFromThing(a->thing.flags) == objective)
440 {
441 count++;
442 }
443 CA_FOREACH_END()
444 return count;
445 }
446
MissionHasRequiredObjectives(const struct MissionOptions * mo)447 bool MissionHasRequiredObjectives(const struct MissionOptions *mo)
448 {
449 CA_FOREACH(const Objective, o, mo->missionData->Objectives)
450 if (ObjectiveIsRequired(o))
451 return true;
452 CA_FOREACH_END()
453 return false;
454 }
455
UpdateMissionObjective(const struct MissionOptions * options,const int flags,const ObjectiveType type,const int count)456 void UpdateMissionObjective(
457 const struct MissionOptions *options, const int flags,
458 const ObjectiveType type, const int count)
459 {
460 if (!(flags & THING_OBJECTIVE))
461 {
462 return;
463 }
464 const int idx = ObjectiveFromThing(flags);
465 const Objective *o = CArrayGet(&options->missionData->Objectives, idx);
466 if (o->Type != type)
467 {
468 return;
469 }
470 if (!gCampaign.IsClient)
471 {
472 GameEvent e = GameEventNew(GAME_EVENT_OBJECTIVE_UPDATE);
473 e.u.ObjectiveUpdate.ObjectiveId = idx;
474 e.u.ObjectiveUpdate.Count = count;
475 GameEventsEnqueue(&gGameEvents, e);
476 }
477 }
478
MissionCanBegin(void)479 bool MissionCanBegin(void)
480 {
481 // Need at least two players to begin PVP
482 if (IsPVP(gCampaign.Entry.Mode))
483 {
484 return GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) > 1;
485 }
486 // Otherwise, just one player will do
487 return GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) > 0;
488 }
489
MissionBegin(struct MissionOptions * m,const NGameBegin gb)490 void MissionBegin(struct MissionOptions *m, const NGameBegin gb)
491 {
492 m->HasBegun = true;
493 m->state = MISSION_STATE_PLAY;
494 switch (m->missionData->Music.Type)
495 {
496 case MUSIC_SRC_GENERAL:
497 MusicPlayGeneral(&gSoundDevice.music, MUSIC_GAME);
498 break;
499 case MUSIC_SRC_DYNAMIC:
500 MusicPlayFile(
501 &gSoundDevice.music, MUSIC_GAME, gCampaign.Entry.Path,
502 m->missionData->Music.Data.Filename);
503 break;
504 case MUSIC_SRC_CHUNK:
505 MusicPlayFromChunk(
506 &gSoundDevice.music, MUSIC_GAME,
507 &m->missionData->Music.Data.Chunk);
508 break;
509 default:
510 CASSERT(false, "unsupported music type");
511 break;
512 }
513 const char *musicErrorMsg = MusicGetErrorMessage(&gSoundDevice.music);
514 if (strlen(musicErrorMsg) > 0)
515 {
516 // Display music error message for 2 seconds
517 GameEvent e = GameEventNew(GAME_EVENT_SET_MESSAGE);
518 strncat(
519 e.u.SetMessage.Message, musicErrorMsg,
520 sizeof e.u.SetMessage.Message - 1);
521 e.u.SetMessage.Ticks = 2000;
522 GameEventsEnqueue(&gGameEvents, e);
523 }
524 m->time = gb.MissionTime;
525 m->pickupTime = 0;
526 }
527
CanCompleteMission(const struct MissionOptions * options)528 bool CanCompleteMission(const struct MissionOptions *options)
529 {
530 // Can't complete if not started yet
531 if (!options->HasBegun)
532 {
533 return false;
534 }
535
536 // Death is the only escape from PVP and quick play
537 if (IsPVP(gCampaign.Entry.Mode))
538 {
539 // If we're in deathmatch with 1 player only, never complete the game
540 // Instead we'll be showing a "waiting for players..." message
541 return GetNumPlayers(PLAYER_ANY, false, false) > 1 &&
542 GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) <= 1;
543 }
544
545 return MissionAllObjectivesComplete(options);
546 }
547
MissionAllObjectivesComplete(const struct MissionOptions * mo)548 bool MissionAllObjectivesComplete(const struct MissionOptions *mo)
549 {
550 // Check all objective counts are enough
551 CA_FOREACH(const Objective, o, mo->missionData->Objectives)
552 if (!ObjectiveIsComplete(o))
553 return false;
554 CA_FOREACH_END()
555 return true;
556 }
557
558 static bool MoreRescuesNeeded(const struct MissionOptions *mo, const int exit);
559
IsMissionComplete(const struct MissionOptions * mo)560 bool IsMissionComplete(const struct MissionOptions *mo)
561 {
562 if (!CanCompleteMission(mo))
563 {
564 return false;
565 }
566
567 // Check if dogfight is complete
568 if (IsPVP(gCampaign.Entry.Mode) &&
569 GetNumPlayers(PLAYER_ALIVE_OR_DYING, false, false) <= 1)
570 {
571 // Also check that only one player has lives left
572 int numPlayersWithLives = 0;
573 CA_FOREACH(const PlayerData, p, gPlayerDatas)
574 if (p->Lives > 0)
575 numPlayersWithLives++;
576 CA_FOREACH_END()
577 if (numPlayersWithLives <= 1)
578 {
579 return true;
580 }
581 }
582
583 const int exit = AllSurvivingPlayersInSameExit();
584 if (MoreRescuesNeeded(mo, exit))
585 {
586 return false;
587 }
588
589 return true;
590 }
591
MissionNeedsMoreRescuesInExit(const struct MissionOptions * mo)592 bool MissionNeedsMoreRescuesInExit(const struct MissionOptions *mo)
593 {
594 const int exit = AllSurvivingPlayersInSameExit();
595 return CanCompleteMission(mo) && exit != -1 && MoreRescuesNeeded(mo, exit);
596 }
597
AllSurvivingPlayersInSameExit(void)598 int AllSurvivingPlayersInSameExit(void)
599 {
600 // Check that all surviving players are in same exit, and return that exit
601 // Return -1 otherwise
602 // Note: players are still in the exit area if they are dying there;
603 // this is the basis for the "resurrection penalty"
604 int exit = -1;
605 CA_FOREACH(const PlayerData, p, gPlayerDatas)
606 if (!IsPlayerAliveOrDying(p))
607 continue;
608 const TActor *player = ActorGetByUID(p->ActorUID);
609 const int playerExit = MapIsTileInExit(&gMap, &player->thing, exit);
610 if (playerExit == -1)
611 return -1;
612 exit = playerExit;
613 CA_FOREACH_END()
614 return exit;
615 }
616
MoreRescuesNeeded(const struct MissionOptions * mo,const int exit)617 static bool MoreRescuesNeeded(const struct MissionOptions *mo, const int exit)
618 {
619 int rescuesRequired = 0;
620 // Find number of rescues required
621 // TODO: support multiple rescue objectives
622 CA_FOREACH(const Objective, o, mo->missionData->Objectives)
623 if (o->Type == OBJECTIVE_RESCUE)
624 {
625 rescuesRequired = o->Required;
626 break;
627 }
628 CA_FOREACH_END()
629 // Check that enough prisoners are in exit zone
630 if (rescuesRequired > 0)
631 {
632 int prisonersRescued = 0;
633 CA_FOREACH(const TActor, a, gActors)
634 if (!a->isInUse)
635 continue;
636 if (CharacterIsPrisoner(
637 &gCampaign.Setting.characters, ActorGetCharacter(a)) &&
638 MapIsTileInExit(&gMap, &a->thing, exit) == exit)
639 {
640 prisonersRescued++;
641 }
642 CA_FOREACH_END()
643 if (prisonersRescued < rescuesRequired)
644 {
645 return true;
646 }
647 }
648 return false;
649 }
650
MissionDone(struct MissionOptions * mo,const NMissionEnd end)651 void MissionDone(struct MissionOptions *mo, const NMissionEnd end)
652 {
653 mo->isDone = true;
654 mo->DoneCounter = end.Delay;
655 gCampaign.IsQuit = end.IsQuit;
656 mo->NextMission = end.Mission;
657 }
658
KeycardCount(int flags)659 int KeycardCount(int flags)
660 {
661 int count = 0;
662 if (flags & FLAGS_KEYCARD_RED)
663 count++;
664 if (flags & FLAGS_KEYCARD_BLUE)
665 count++;
666 if (flags & FLAGS_KEYCARD_GREEN)
667 count++;
668 if (flags & FLAGS_KEYCARD_YELLOW)
669 count++;
670 return count;
671 }
672
MissionStaticAddObjective(Mission * m,MissionStatic * ms,const int idx,const int idx2,const struct vec2i pos,const bool force)673 void MissionStaticAddObjective(
674 Mission *m, MissionStatic *ms, const int idx, const int idx2,
675 const struct vec2i pos, const bool force)
676 {
677 CASSERT(m->Type == MAPTYPE_STATIC, "mission is not static type");
678 if (!force)
679 {
680 // Remove any objectives already there
681 MissionStaticTryRemoveObjective(m, ms, pos);
682 }
683
684 // Check if the objective already has an entry, and add to its list
685 // of positions
686 bool hasAdded = false;
687 PositionIndex pi = {pos, idx2};
688 for (int i = 0; i < (int)ms->Objectives.size; i++)
689 {
690 ObjectivePositions *op = CArrayGet(&ms->Objectives, i);
691 if (op->Index == idx)
692 {
693 CArrayPushBack(&op->PositionIndices, &pi);
694 hasAdded = true;
695 break;
696 }
697 }
698 // If not, create a new entry
699 if (!hasAdded)
700 {
701 ObjectivePositions newOp;
702 newOp.Index = idx;
703 CArrayInit(&newOp.PositionIndices, sizeof(PositionIndex));
704 CArrayPushBack(&newOp.PositionIndices, &pi);
705 CArrayPushBack(&ms->Objectives, &newOp);
706 }
707 // Increase number of objectives
708 Objective *o = CArrayGet(&m->Objectives, idx);
709 o->Count++;
710 }
MissionStaticTryRemoveObjective(Mission * m,MissionStatic * ms,const struct vec2i pos)711 bool MissionStaticTryRemoveObjective(
712 Mission *m, MissionStatic *ms, const struct vec2i pos)
713 {
714 CA_FOREACH(ObjectivePositions, op, ms->Objectives)
715 for (int j = 0; j < (int)op->PositionIndices.size; j++)
716 {
717 PositionIndex *pi = CArrayGet(&op->PositionIndices, j);
718 if (svec2i_is_equal(pi->Position, pos))
719 {
720 CArrayDelete(&op->PositionIndices, j);
721 // Decrease number of objectives
722 Objective *o = CArrayGet(&m->Objectives, op->Index);
723 o->Count--;
724 CASSERT(o->Count >= 0, "removing unknown objective");
725 if (op->PositionIndices.size == 0)
726 {
727 CArrayTerminate(&op->PositionIndices);
728 CArrayDelete(&ms->Objectives, _ca_index);
729 }
730 return true;
731 }
732 }
733 CA_FOREACH_END()
734 return false;
735 }
736