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