1 /*
2 	C-Dogs SDL
3 	A port of the legendary (and fun) action/arcade cdogs.
4 	Copyright (c) 2020-2021 Cong Xu
5 	All rights reserved.
6 
7 	Redistribution and use in source and binary forms, with or without
8 	modification, are permitted provided that the following conditions are met:
9 
10 	Redistributions of source code must retain the above copyright notice, this
11 	list of conditions and the following disclaimer.
12 	Redistributions in binary form must reproduce the above copyright notice,
13 	this list of conditions and the following disclaimer in the documentation
14 	and/or other materials provided with the distribution.
15 
16 	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 	IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 	POSSIBILITY OF SUCH DAMAGE.
27 */
28 #include "map_wolf.h"
29 
30 #include <find_steam_game.h>
31 
32 #include "log.h"
33 
34 #include "cwolfmap/audio.h"
35 #include "cwolfmap/cwolfmap.h"
36 #include "map_archive.h"
37 #include "player_template.h"
38 
39 #define WOLF_STEAM_NAME "Wolfenstein 3D"
40 #define SPEAR_STEAM_NAME "Spear of Destiny"
41 #define WOLF_GOG_ID "1441705046"
42 #define SPEAR_GOG_ID "1441705126"
43 
44 #define TILE_CLASS_WALL_OFFSET 62
45 
GetCampaignPath(const CWMapType type,char * buf)46 static void GetCampaignPath(const CWMapType type, char *buf)
47 {
48 	switch (type)
49 	{
50 	case CWMAPTYPE_WL1:
51 		GetDataFilePath(buf, "missions/.wolf3d/WL1.cdogscpn");
52 		break;
53 	case CWMAPTYPE_WL6:
54 		GetDataFilePath(buf, "missions/.wolf3d/WL6.cdogscpn");
55 		break;
56 	case CWMAPTYPE_SOD:
57 		GetDataFilePath(buf, "missions/.wolf3d/SOD.cdogscpn");
58 		break;
59 	default:
60 		CASSERT(false, "unknown map type");
61 		break;
62 	}
63 }
64 static const char *soundsW1[] = {
65 	// 0-9
66 	"chars/alert/guard", "chars/alert/dog", "door_close", "door",
67 	"machine_gun", "pistol", "chain_gun", "chars/alert/ss", "chars/alert/hans",
68 	"chars/die/hans",
69 	// 10-15
70 	"dual_chain_gun", "machine_gun_burst", "chars/die/guard/",
71 	"chars/die/guard/", "chars/die/guard/", "secret_door", NULL, NULL, NULL,
72 	NULL,
73 	// 20-29
74 	NULL, NULL, NULL, NULL, NULL, NULL, "chars/die/ss", "pistol_guard",
75 	"gurgle", NULL,
76 	// 30-39
77 	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
78 	// 40-49
79 	NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
80 	// 50-57
81 	NULL, NULL, NULL, NULL, NULL, NULL, NULL, "victory"};
82 static const char *soundsW6[] = {
83 	// 0-9
84 	"chars/alert/guard", "chars/alert/dog/", "door_close", "door",
85 	"machine_gun", "pistol", "chain_gun", "chars/alert/ss", "chars/alert/hans",
86 	"chars/die/hans",
87 	// 10-19
88 	"dual_chain_gun", "machine_gun_burst", "chars/die/guard/",
89 	"chars/die/guard/", "chars/die/guard/", "secret_door", "chars/die/dog",
90 	"chars/die/mutant", "chars/alert/mecha_hitler", "chars/die/hitler",
91 	// 20-29
92 	"chars/die/ss", "pistol_guard", "gurgle", "chars/alert/fake_hitler",
93 	"chars/die/schabbs", "chars/alert/schabbs", "chars/die/fake_hitler",
94 	"chars/alert/officer", "chars/die/officer", "chars/alert/dog/",
95 	// 30-39
96 	"whistle", "footsteps/mech", "victory", "chars/die/mecha_hitler",
97 	"chars/die/guard/", "chars/die/guard/", "chars/die/otto",
98 	"chars/alert/otto", "chars/alert/fettgesicht", "fart",
99 	// 40-49
100 	"chars/die/guard/", "chars/die/guard/", "chars/die/guard/",
101 	"chars/alert/gretel", "chars/die/gretel", "chars/die/fettgesicht"};
102 static const char *soundsSOD[] = {
103 	// 0-9
104 	"chars/alert/guard", "chars/alert/dog/", "door_close", "door",
105 	"machine_gun", "pistol", "chain_gun", "chars/alert/ss", "dual_chain_gun",
106 	"machine_gun_burst",
107 	// 10-19
108 	"chars/die/guard/", "chars/die/guard/", "chars/die/guard/", "secret_door",
109 	"chars/die/dog", "chars/die/mutant", "chars/die/ss", "pistol_guard",
110 	"gurgle", "chars/alert/officer",
111 	// 20-29
112 	"chars/die/officer", "chars/alert/dog/", "whistle", "chars/die/guard/",
113 	"chars/die/guard/", "fart", "chars/die/guard/", "chars/die/guard/",
114 	"chars/die/guard/", "chars/alert/trans",
115 	// 30-39
116 	"chars/die/trans", "chars/alert/bill", "chars/die/bill",
117 	"chars/die/ubermutant", "chars/alert/knight", "chars/die/knight",
118 	"chars/alert/angel", "chars/die/angel", "chaingun_pickup", "spear"};
GetSound(const CWMapType type,const int i)119 static const char *GetSound(const CWMapType type, const int i)
120 {
121 	// Map sound index to string
122 	switch (type)
123 	{
124 	case CWMAPTYPE_WL1:
125 		return soundsW1[i];
126 	case CWMAPTYPE_WL6:
127 		return soundsW6[i];
128 	case CWMAPTYPE_SOD:
129 		return soundsSOD[i];
130 	default:
131 		CASSERT(false, "unknown map type");
132 		return NULL;
133 	}
134 }
135 
136 static const char *adlibSoundsW1[] = {
137 	NULL,		  // hit wall
138 	"menu_start", // select weapon
139 	NULL,		  // select item
140 	NULL,		  // heartbeat
141 	"menu_switch",
142 	NULL, // move gun 1
143 	"menu_error",
144 	NULL, // nazi hit player
145 	NULL, // nazi miss player
146 	NULL, // player death (unused because of some corruption at end of sound)
147 	NULL, // dog death (digi sound)
148 	NULL, // gatling (digi sound)
149 	"key",
150 	NULL, // no item
151 	NULL, // walk1
152 	NULL, // walk2
153 	NULL, // take damage
154 	NULL, // game over
155 	NULL, // open door (digi sound)
156 	NULL, // close door (digi sound)
157 	NULL, // do nothing (not used in C-Dogs)
158 	NULL, // guard alert (digi sound)
159 	NULL, // death 2 (digi sound)
160 	"hits/knife_flesh/",
161 	NULL, // pistol (digi sound)
162 	NULL, // death 3 (digi sound)
163 	NULL, // machine gun (digi sound)
164 	NULL, // hit enemy
165 	NULL, // shoot door
166 	NULL, // death 1 (digi sound)
167 	"machine_gun_switch",
168 	"ammo_pickup",
169 	"menu_enter",
170 	"health_small",
171 	"health_big",
172 	"pickup_cross",
173 	"pickup_chalice",
174 	"pickup_chest",
175 	"chaingun_pickup",
176 	"menu_back",
177 	"whistle",
178 	NULL,				// dog alert (digi sound)
179 	NULL,				// end bonus 1 (not used in C-Dogs)
180 	"mission_complete", // end bonus 2
181 	"1up",
182 	"pickup_crown",
183 	NULL, // push wall (digi sound)
184 	NULL, // no bonus (not used in C-Dogs)
185 	NULL, // 100% (not used in C-Dogs)
186 	NULL, // boss active
187 	NULL, // boss die
188 	NULL, // SS alert (digi sound)
189 	NULL, // aah (digi sound)
190 	NULL, // mecha hitler die (digi sound)
191 	NULL, // hitler die (digi sound)
192 	NULL, // hans alert (digi sound)
193 	NULL, // SS die (digi sound)
194 	NULL, // hans die (digi sound)
195 	NULL, // guard fire (digi sound)
196 	NULL, // boss chain gun (digi sound)
197 	NULL, // SS fire (digi sound)
198 	NULL, // slurpie (digi sound)
199 	NULL, // fake hitler alert (digi sound)
200 	NULL, // schabbs die (digi sound)
201 	NULL, // schabbs alert (digi sound)
202 	NULL, // hitler alert (digi sound)
203 	NULL, // officer alert (digi sound)
204 	NULL, // officer die (digi sound)
205 	NULL, // dog attack (digi sound)
206 };
207 static const char *adlibSoundsW6[] = {
208 	NULL,		  // hit wall
209 	"menu_start", // select weapon
210 	NULL,		  // select item
211 	NULL,		  // heartbeat
212 	"menu_switch",
213 	NULL, // move gun 1
214 	"menu_error",
215 	NULL, // nazi hit player
216 	"syringe",
217 	NULL, // player death (unused because of some corruption at end of sound)
218 	NULL, // dog death (digi sound)
219 	NULL, // gatling (digi sound)
220 	"key",
221 	NULL, // no item
222 	NULL, // walk1
223 	NULL, // walk2
224 	NULL, // take damage
225 	NULL, // game over
226 	NULL, // open door (digi sound)
227 	NULL, // close door (digi sound)
228 	NULL, // do nothing (not used in C-Dogs)
229 	NULL, // guard alert (digi sound)
230 	NULL, // death 2 (digi sound)
231 	"hits/knife_flesh/",
232 	NULL, // pistol (digi sound)
233 	NULL, // death 3 (digi sound)
234 	NULL, // machine gun (digi sound)
235 	NULL, // hit enemy
236 	NULL, // shoot door
237 	NULL, // death 1 (digi sound)
238 	"machine_gun_switch",
239 	"ammo_pickup",
240 	"menu_enter",
241 	"health_small",
242 	"health_big",
243 	"pickup_cross",
244 	"pickup_chalice",
245 	"pickup_chest",
246 	"chaingun_pickup",
247 	"menu_back",
248 	NULL,				// level end (digi sound)
249 	NULL,				// dog alert (digi sound)
250 	NULL,				// end bonus 1 (not used in C-Dogs)
251 	"mission_complete", // end bonus 2
252 	"1up",
253 	"pickup_crown",
254 	NULL, // push wall (digi sound)
255 	NULL, // no bonus (not used in C-Dogs)
256 	NULL, // 100% (not used in C-Dogs)
257 	NULL, // boss active
258 	NULL, // boss die
259 	NULL, // SS alert (digi sound)
260 	NULL, // aah (digi sound)
261 	NULL, // mecha hitler die (digi sound)
262 	NULL, // hitler die (digi sound)
263 	NULL, // hans alert (digi sound)
264 	NULL, // SS die (digi sound)
265 	NULL, // hans die (digi sound)
266 	NULL, // guard fire (digi sound)
267 	NULL, // boss chain gun (digi sound)
268 	NULL, // SS fire (digi sound)
269 	NULL, // slurpie (digi sound)
270 	NULL, // fake hitler alert (digi sound)
271 	NULL, // schabbs die (digi sound)
272 	NULL, // schabbs alert (digi sound)
273 	NULL, // hitler alert (digi sound)
274 	NULL, // officer alert (digi sound)
275 	NULL, // officer die (digi sound)
276 	NULL, // dog attack (digi sound)
277 	"flamethrower",
278 	NULL, // mech step (digi sound)
279 	NULL, // goobs
280 	NULL, // yeah (digi sound)
281 	NULL, // guard die 4 (digi sound)
282 	NULL, // guard die 5 (digi sound)
283 	NULL, // guard die 6 (digi sound)
284 	NULL, // guard die 7 (digi sound)
285 	NULL, // guard die 8 (digi sound)
286 	NULL, // guard die 9 (digi sound)
287 	NULL, // otto die (digi sound)
288 	NULL, // otto alert (digi sound)
289 	NULL, // fettgesicht alert (digi sound)
290 	NULL, // gretel alert (digi sound)
291 	NULL, // gretel die (digi sound)
292 	NULL, // fettgesicht die (digi sound)
293 	"rocket",
294 	"hits/rocket/",
295 };
296 static const char *adlibSoundsSOD[] = {
297 	NULL, // hit wall
298 	"hits/rocket/",
299 	"menu_start", // select item
300 	"chars/alert/ghost",
301 	"menu_switch",
302 	NULL, // move gun 1
303 	"menu_error",
304 	NULL, // nazi hit player
305 	"rocket",
306 	NULL, // player death (unused because of some corruption at end of sound)
307 	NULL, // dog death (digi sound)
308 	NULL, // gatling (digi sound)
309 	"key",
310 	NULL, // no item
311 	NULL, // walk1
312 	NULL, // walk2
313 	NULL, // take damage
314 	NULL, // game over
315 	NULL, // open door (digi sound)
316 	NULL, // close door (digi sound)
317 	NULL, // do nothing (not used in C-Dogs)
318 	NULL, // guard alert (digi sound)
319 	NULL, // death 2 (digi sound)
320 	"hits/knife_flesh/",
321 	NULL, // pistol (digi sound)
322 	NULL, // death 3 (digi sound)
323 	NULL, // machine gun (digi sound)
324 	NULL, // hit enemy
325 	NULL, // shoot door
326 	NULL, // death 1 (digi sound)
327 	"machine_gun_switch",
328 	"ammo_pickup",
329 	"menu_enter",
330 	"health_small",
331 	"health_big",
332 	"pickup_cross",
333 	"pickup_chalice",
334 	"pickup_chest",
335 	NULL, // chain gun pickup (digi sound)
336 	"menu_back",
337 	NULL,				// level end (digi sound)
338 	NULL,				// dog alert (digi sound)
339 	NULL,				// end bonus 1 (not used in C-Dogs)
340 	"mission_complete", // end bonus 2
341 	"1up",
342 	"pickup_crown",
343 	NULL, // push wall (digi sound)
344 	NULL, // no bonus (not used in C-Dogs)
345 	NULL, // 100% (not used in C-Dogs)
346 	NULL, // boss active
347 	NULL, // guard die 4 (digi sound)
348 	NULL, // SS alert (digi sound)
349 	NULL, // aah (digi sound)
350 	NULL, // guard die 5 (digi sound)
351 	NULL, // guard die 7 (digi sound)
352 	NULL, // guard die 8 (digi sound)
353 	NULL, // SS die (digi sound)
354 	NULL, // guard die 6 (digi sound)
355 	NULL, // guard fire (digi sound)
356 	NULL, // boss chain gun (digi sound)
357 	NULL, // SS fire (digi sound)
358 	NULL, // slurpie (digi sound)
359 	"chars/ghost/die",
360 	NULL, // guard die 9 (digi sound)
361 	"ammo_box",
362 	NULL, // angel alert (digi sound)
363 	NULL, // officer alert (digi sound)
364 	NULL, // officer die (digi sound)
365 	NULL, // dog attack (digi sound)
366 	"flamethrower",
367 	NULL,			 // trans alert (digi sound)
368 	NULL,			 // trans die (digi sound)
369 	NULL,			 // wilhelm alert (digi sound)
370 	NULL,			 // wilhelm die (digi sound)
371 	NULL,			 // uber die (digi sound)
372 	NULL,			 // knight alert (digi sound)
373 	NULL,			 // knight die (digi sound)
374 	NULL,			 // angel die (digi sound)
375 	"knight_rocket",
376 	NULL,			 // spear (digi sound)
377 	NULL,			 // angel tired (not used in C-Dogs)
378 };
GetAdlibSound(const CWMapType type,const int i)379 static const char *GetAdlibSound(const CWMapType type, const int i)
380 {
381 	// Map sound index to string
382 	switch (type)
383 	{
384 	case CWMAPTYPE_WL1:
385 		return adlibSoundsW1[i];
386 	case CWMAPTYPE_WL6:
387 		return adlibSoundsW6[i];
388 	case CWMAPTYPE_SOD:
389 		return adlibSoundsSOD[i];
390 	default:
391 		CASSERT(false, "unknown map type");
392 		return NULL;
393 	}
394 }
395 
396 static const CWSongType songsCampaign[] = {
397 	SONG_INTRO,	  // menu
398 	SONG_MENU,	  // briefing
399 	0,			  // game
400 	SONG_END,	  // end
401 	SONG_ROSTER,  // lose
402 	SONG_VICTORY, // victory
403 };
LoadMusic(const CWolfMap * map,const int i)404 static Mix_Chunk *LoadMusic(const CWolfMap *map, const int i)
405 {
406 	char *data;
407 	size_t len;
408 	const int err = CWAudioGetMusic(&map->audio, i, &data, &len);
409 	if (err != 0)
410 	{
411 		goto bail;
412 	}
413 	return Mix_QuickLoad_RAW((Uint8 *)data, (Uint32)len);
414 
415 bail:
416 	return NULL;
417 }
418 
MapWolfScan(const char * filename,char ** title,int * numMissions)419 int MapWolfScan(const char *filename, char **title, int *numMissions)
420 {
421 	int err = 0;
422 	CWolfMap map;
423 	err = CWLoad(&map, filename);
424 	if (err != 0)
425 	{
426 		goto bail;
427 	}
428 	char buf[CDOGS_PATH_MAX];
429 	GetCampaignPath(map.type, buf);
430 	err = MapNewScanArchive(buf, title, NULL);
431 	if (err != 0)
432 	{
433 		goto bail;
434 	}
435 	if (numMissions)
436 	{
437 		*numMissions = map.nLevels;
438 	}
439 
440 bail:
441 	CWFree(&map);
442 	return err;
443 }
444 
445 static void LoadSounds(const SoundDevice *s, const CWolfMap *map);
446 static void LoadMission(
447 	CampaignSetting *c, const map_t tileClasses, const CWolfMap *map,
448 	const int missionIndex);
449 typedef struct
450 {
451 	const CWolfMap *Map;
452 	MusicType Type;
453 } CampaignSongData;
GetCampaignSong(void * data)454 static Mix_Chunk *GetCampaignSong(void *data)
455 {
456 	CampaignSongData *csd = data;
457 	const int songIndex = songsCampaign[csd->Type];
458 	return LoadMusic(csd->Map, CWAudioGetSong(csd->Map->type, songIndex));
459 }
MapWolfLoad(const char * filename,CampaignSetting * c)460 int MapWolfLoad(const char *filename, CampaignSetting *c)
461 {
462 	int err = 0;
463 	CWolfMap *map = NULL;
464 	CMALLOC(map, sizeof *map);
465 	c->CustomData = map;
466 	c->CustomDataTerminate = (void (*)(void *))CWFree;
467 	map_t tileClasses = NULL;
468 	err = CWLoad(map, filename);
469 	if (err != 0)
470 	{
471 		goto bail;
472 	}
473 
474 	LoadSounds(&gSoundDevice, map);
475 	for (int i = 0; i < MUSIC_COUNT; i++)
476 	{
477 		CampaignSongData *csd;
478 		CMALLOC(csd, sizeof *csd);
479 		csd->Map = map;
480 		csd->Type = i;
481 		c->CustomSongs[i].Data = csd;
482 		c->CustomSongs[i].GetData = GetCampaignSong;
483 		c->CustomSongs[i].Chunk = NULL;
484 	}
485 
486 	char buf[CDOGS_PATH_MAX];
487 	// Copy data from common campaign and use them for every mission
488 	GetDataFilePath(buf, "missions/.wolf3d/common.cdogscpn");
489 	CampaignSetting cCommon;
490 	CampaignSettingInit(&cCommon);
491 	err = MapNewLoadArchive(buf, &cCommon);
492 	if (err != 0)
493 	{
494 		goto bail;
495 	}
496 	Mission *m = CArrayGet(&cCommon.Missions, 0);
497 	tileClasses = hashmap_copy(m->u.Static.TileClasses, TileClassCopyHashMap);
498 	CharacterStore cs;
499 	memset(&cs, 0, sizeof cs);
500 	CharacterStoreCopy(
501 		&cs, &cCommon.characters, &gPlayerTemplates.CustomClasses);
502 	CampaignSettingTerminate(&cCommon);
503 	// Create walk-through copies of all the walls
504 	for (int i = 3; i <= 64; i++)
505 	{
506 		TileClass *orig;
507 		sprintf(buf, "%d", i);
508 		if (hashmap_get(tileClasses, buf, (any_t *)&orig) != MAP_OK)
509 		{
510 			LOG(LM_MAP, LL_ERROR, "failed to get tile class for copying");
511 			break;
512 		}
513 		TileClass *tc;
514 		CMALLOC(tc, sizeof *tc);
515 		TileClassCopy(tc, orig);
516 		tc->canWalk = true;
517 		// Slightly modify tile color because they are referenced by mask
518 		tc->Mask.a--;
519 		sprintf(buf, "%d", i + TILE_CLASS_WALL_OFFSET);
520 		if (hashmap_put(tileClasses, buf, tc) != MAP_OK)
521 		{
522 			LOG(LM_MAP, LL_ERROR, "failed to save tile class copy");
523 			break;
524 		}
525 	}
526 
527 	GetCampaignPath(map->type, buf);
528 	err = MapNewLoadArchive(buf, c);
529 	if (err != 0)
530 	{
531 		goto bail;
532 	}
533 
534 	CharacterStoreCopy(&c->characters, &cs, &gPlayerTemplates.CustomClasses);
535 
536 	for (int i = 0; i < map->nLevels; i++)
537 	{
538 		LoadMission(c, tileClasses, map, i);
539 	}
540 
541 bail:
542 	hashmap_destroy(tileClasses, TileClassDestroy);
543 	CharacterStoreTerminate(&cs);
544 	return err;
545 }
546 
547 static Mix_Chunk *LoadSoundData(const CWolfMap *map, const int i);
548 static Mix_Chunk *LoadAdlibSoundData(const CWolfMap *map, const int i);
549 static void AddNormalSound(
550 	const SoundDevice *s, const char *name, Mix_Chunk *data);
551 static void AddRandomSound(
552 	const SoundDevice *s, const char *name, Mix_Chunk *data);
LoadSounds(const SoundDevice * s,const CWolfMap * map)553 static void LoadSounds(const SoundDevice *s, const CWolfMap *map)
554 {
555 	if (!s->isInitialised)
556 	{
557 		return;
558 	}
559 
560 	// Load adlib sounds
561 	for (int i = 0; i < map->audio.nSound; i++)
562 	{
563 		const char *name = GetAdlibSound(map->type, i);
564 		if (name == NULL)
565 		{
566 			continue;
567 		}
568 		Mix_Chunk *data = LoadAdlibSoundData(map, i);
569 		if (name[strlen(name) - 1] == '/')
570 		{
571 
572 			AddRandomSound(s, name, data);
573 		}
574 		else
575 		{
576 			AddNormalSound(s, name, data);
577 		}
578 	}
579 
580 	// Load digi sounds
581 	for (int i = 0; i < map->vswap.nSounds; i++)
582 	{
583 		const char *name = GetSound(map->type, i);
584 		if (name == NULL)
585 		{
586 			continue;
587 		}
588 		if (name[strlen(name) - 1] == '/')
589 			continue;
590 		Mix_Chunk *data = LoadSoundData(map, i);
591 		if (data == NULL)
592 		{
593 			continue;
594 		}
595 		if (name[strlen(name) - 1] == '/')
596 		{
597 
598 			AddRandomSound(s, name, data);
599 		}
600 		else
601 		{
602 			AddNormalSound(s, name, data);
603 		}
604 	}
605 }
LoadSoundData(const CWolfMap * map,const int i)606 static Mix_Chunk *LoadSoundData(const CWolfMap *map, const int i)
607 {
608 	const char *data;
609 	size_t len;
610 	const int err = CWVSwapGetSound(&map->vswap, i, &data, &len);
611 	if (err != 0)
612 	{
613 		LOG(LM_MAP, LL_ERROR, "Failed to load wolf sound %d: %d\n", i, err);
614 		return NULL;
615 	}
616 	if (len == 0)
617 	{
618 		LOG(LM_MAP, LL_ERROR, "Wolf sound %d has 0 len\n", i);
619 		return NULL;
620 	}
621 	SDL_AudioCVT cvt;
622 	SDL_BuildAudioCVT(
623 		&cvt, AUDIO_U8, 1, SND_RATE, CDOGS_SND_FMT, CDOGS_SND_CHANNELS,
624 		CDOGS_SND_RATE);
625 	cvt.len = (int)len;
626 	cvt.buf = (Uint8 *)SDL_malloc(cvt.len * cvt.len_mult);
627 	memcpy(cvt.buf, data, len);
628 	SDL_ConvertAudio(&cvt);
629 	return Mix_QuickLoad_RAW(cvt.buf, cvt.len_cvt);
630 }
LoadAdlibSoundData(const CWolfMap * map,const int i)631 static Mix_Chunk *LoadAdlibSoundData(const CWolfMap *map, const int i)
632 {
633 	char *data;
634 	size_t len;
635 	const int err = CWAudioGetAdlibSound(&map->audio, i, &data, &len);
636 	if (err != 0)
637 	{
638 		LOG(LM_MAP, LL_ERROR, "Failed to load adlib wolf sound %d: %d\n", i,
639 			err);
640 		return NULL;
641 	}
642 	if (len == 0)
643 	{
644 		LOG(LM_MAP, LL_ERROR, "Wolf sound %d has 0 len\n", i);
645 		return NULL;
646 	}
647 	return Mix_QuickLoad_RAW((Uint8 *)data, (Uint32)len);
648 }
AddNormalSound(const SoundDevice * s,const char * name,Mix_Chunk * data)649 static void AddNormalSound(
650 	const SoundDevice *s, const char *name, Mix_Chunk *data)
651 {
652 	SoundData *sound;
653 	CMALLOC(sound, sizeof *sound);
654 	sound->Type = SOUND_NORMAL;
655 	sound->u.normal = data;
656 	SoundAdd(s->customSounds, name, sound);
657 }
AddRandomSound(const SoundDevice * s,const char * name,Mix_Chunk * data)658 static void AddRandomSound(
659 	const SoundDevice *s, const char *name, Mix_Chunk *data)
660 {
661 	// Strip trailing slash and find the sound
662 	SoundData *sound;
663 	char nameBuf[CDOGS_PATH_MAX];
664 	strcpy(nameBuf, name);
665 	nameBuf[strlen(nameBuf) - 1] = '\0';
666 	const int err = hashmap_get(s->customSounds, nameBuf, (any_t *)&sound);
667 	if (err == MAP_OK && sound->Type == SOUND_RANDOM)
668 	{
669 		CArrayPushBack(&sound->u.random.sounds, &data);
670 	}
671 	else
672 	{
673 		CCALLOC(sound, sizeof *sound);
674 		sound->Type = SOUND_RANDOM;
675 		CArrayInit(&sound->u.random.sounds, sizeof(Mix_Chunk *));
676 		CArrayPushBack(&sound->u.random.sounds, &data);
677 		SoundAdd(s->customSounds, nameBuf, sound);
678 	}
679 }
680 
681 static void LoadTile(
682 	MissionStatic *m, const uint16_t ch, const CWolfMap *map,
683 	const struct vec2i v, const int missionIndex);
684 static void TryLoadWallObject(
685 	MissionStatic *m, const uint16_t ch, const CWolfMap *map,
686 	const struct vec2i v, const int missionIndex);
687 static void LoadEntity(
688 	Mission *m, const uint16_t ch, const CWolfMap *map, const struct vec2i v,
689 	const int missionIndex, int *bossObjIdx, int *spearObjIdx);
690 
691 typedef struct
692 {
693 	const CWolfMap *Map;
694 	int MissionIndex;
695 } MissionSongData;
GetMissionSong(void * data)696 static Mix_Chunk *GetMissionSong(void *data)
697 {
698 	MissionSongData *msd = data;
699 	return LoadMusic(
700 		msd->Map, CWAudioGetLevelMusic(msd->Map->type, msd->MissionIndex));
701 }
LoadMission(CampaignSetting * c,const map_t tileClasses,const CWolfMap * map,const int missionIndex)702 static void LoadMission(
703 	CampaignSetting *c, const map_t tileClasses, const CWolfMap *map,
704 	const int missionIndex)
705 {
706 	const CWLevel *level = &map->levels[missionIndex];
707 	Mission m;
708 	MissionInit(&m);
709 	CSTRDUP(m.Title, level->header.name);
710 	m.Size = svec2i(level->header.width, level->header.height);
711 	m.Type = MAPTYPE_STATIC;
712 	strcpy(m.ExitStyle, "plate");
713 	strcpy(m.KeyStyle, "plain2");
714 
715 	// TODO: objectives for treasure, kills (multiple items per obj)
716 	int bossObjIdx = -1;
717 	int spearObjIdx = -1;
718 
719 	const WeaponClass *wc = StrWeaponClass("Pistol");
720 	CArrayPushBack(&m.Weapons, &wc);
721 	wc = StrWeaponClass("Knife");
722 	CArrayPushBack(&m.Weapons, &wc);
723 	// Reset weapons at start of episodes
724 	if (map->type != CWMAPTYPE_SOD)
725 	{
726 		m.WeaponPersist = (missionIndex % 10) != 0;
727 	}
728 	else
729 	{
730 		m.WeaponPersist = true;
731 	}
732 
733 	m.Music.Type = MUSIC_SRC_CHUNK;
734 	MissionSongData *msd;
735 	CMALLOC(msd, sizeof *msd);
736 	msd->Map = map;
737 	msd->MissionIndex = missionIndex;
738 	m.Music.Data.Chunk.Data = msd;
739 	m.Music.Data.Chunk.GetData = GetMissionSong;
740 	m.Music.Data.Chunk.Chunk = NULL;
741 
742 	MissionStaticInit(&m.u.Static);
743 
744 	m.u.Static.TileClasses = hashmap_copy(tileClasses, TileClassCopyHashMap);
745 
746 	RECT_FOREACH(Rect2iNew(svec2i_zero(), m.Size))
747 	const uint16_t ch = CWLevelGetCh(level, 0, _v.x, _v.y);
748 	LoadTile(&m.u.Static, ch, map, _v, missionIndex);
749 	RECT_FOREACH_END()
750 	// Load objects after all tiles are loaded
751 	RECT_FOREACH(Rect2iNew(svec2i_zero(), m.Size))
752 	const uint16_t ch = CWLevelGetCh(level, 0, _v.x, _v.y);
753 	TryLoadWallObject(&m.u.Static, ch, map, _v, missionIndex);
754 	const uint16_t ech = CWLevelGetCh(level, 1, _v.x, _v.y);
755 	LoadEntity(&m, ech, map, _v, missionIndex, &bossObjIdx, &spearObjIdx);
756 	RECT_FOREACH_END()
757 
758 	if (m.u.Static.Exits.size == 0)
759 	{
760 		// This is a boss level where killing the boss ends the level
761 		// Make sure to skip over the secret level
762 		Exit e;
763 		e.Hidden = true;
764 		if (map->type == CWMAPTYPE_SOD)
765 		{
766 			if (missionIndex == 17)
767 			{
768 				// Skip over the two secret levels
769 				e.Mission = missionIndex + 3;
770 			}
771 			else
772 			{
773 				e.Mission = missionIndex + 1;
774 			}
775 		}
776 		else
777 		{
778 			// Skip over the secret level to the next episode
779 			e.Mission = missionIndex + 2;
780 		}
781 		e.R.Pos = svec2i_zero();
782 		e.R.Size = m.Size;
783 		CArrayPushBack(&m.u.Static.Exits, &e);
784 	}
785 	if (map->type == CWMAPTYPE_SOD && missionIndex == 17)
786 	{
787 		// Skip debrief and cut directly to angel boss level
788 		m.SkipDebrief = true;
789 	}
790 
791 	m.u.Static.AltFloorsEnabled = false;
792 
793 	CArrayPushBack(&c->Missions, &m);
794 }
795 
796 static int LoadWall(const uint16_t ch);
797 
LoadTile(MissionStatic * m,const uint16_t ch,const CWolfMap * map,const struct vec2i v,const int missionIndex)798 static void LoadTile(
799 	MissionStatic *m, const uint16_t ch, const CWolfMap *map,
800 	const struct vec2i v, const int missionIndex)
801 {
802 	UNUSED(missionIndex);
803 	const CWTile tile = CWChToTile(ch);
804 	int staticTile = 0;
805 	uint16_t staticAccess = 0;
806 	switch (tile)
807 	{
808 	case CWTILE_WALL:
809 		staticTile = LoadWall(ch);
810 		break;
811 	case CWTILE_DOOR_H:
812 	case CWTILE_DOOR_V:
813 		staticTile = 1;
814 		break;
815 	case CWTILE_DOOR_GOLD_H:
816 	case CWTILE_DOOR_GOLD_V:
817 		staticTile = 1;
818 		staticAccess = MAP_ACCESS_YELLOW;
819 		break;
820 	case CWTILE_DOOR_SILVER_H:
821 	case CWTILE_DOOR_SILVER_V:
822 		staticTile = 1;
823 		staticAccess = MAP_ACCESS_BLUE;
824 		break;
825 	case CWTILE_ELEVATOR_H:
826 	case CWTILE_ELEVATOR_V:
827 		staticTile = 2;
828 		break;
829 	case CWTILE_AREA:
830 		break;
831 	case CWTILE_SECRET_EXIT: {
832 		Exit e;
833 		e.Hidden = true;
834 		if (map->type == CWMAPTYPE_SOD)
835 		{
836 			// For SOD, missions 19/20 are always the secret ones
837 			if (missionIndex == 3)
838 			{
839 				e.Mission = 18;
840 			}
841 			else
842 			{
843 				e.Mission = 19;
844 			}
845 		}
846 		else
847 		{
848 			// Last map of the episode
849 			e.Mission = 9;
850 			while (e.Mission < missionIndex)
851 			{
852 				e.Mission += 10;
853 			}
854 		}
855 		e.R.Pos = v;
856 		e.R.Size = svec2i_zero();
857 		MissionStaticTryAddExit(m, &e);
858 	}
859 	break;
860 	default:
861 		CASSERT(false, "unknown tile");
862 		break;
863 	}
864 	CArrayPushBack(&m->Tiles, &staticTile);
865 	CArrayPushBack(&m->Access, &staticAccess);
866 }
867 
LoadWall(const uint16_t ch)868 static int LoadWall(const uint16_t ch)
869 {
870 	const CWWall wall = CWChToWall(ch);
871 	return (int)wall + 3;
872 }
873 
TryLoadWallObject(MissionStatic * m,const uint16_t ch,const CWolfMap * map,const struct vec2i v,const int missionIndex)874 static void TryLoadWallObject(
875 	MissionStatic *m, const uint16_t ch, const CWolfMap *map,
876 	const struct vec2i v, const int missionIndex)
877 {
878 	const CWLevel *level = &map->levels[missionIndex];
879 	const struct vec2i levelSize =
880 		svec2i(level->header.width, level->header.height);
881 	const struct vec2i vBelow = svec2i_add(v, svec2i(0, 1));
882 	const CWWall wall = CWChToWall(ch);
883 	const char *moName = NULL;
884 	switch (wall)
885 	{
886 	case CWWALL_GREY_BRICK_FLAG:
887 		moName = "heer_flag";
888 		break;
889 	case CWWALL_GREY_BRICK_HITLER:
890 		moName = "hitler_portrait";
891 		break;
892 	case CWWALL_CELL:
893 		moName = "jail_cell";
894 		break;
895 	case CWWALL_GREY_BRICK_EAGLE:
896 		moName = "brick_eagle";
897 		break;
898 	case CWWALL_CELL_SKELETON:
899 		moName = "jail_cell_skeleton";
900 		break;
901 	case CWWALL_WOOD_EAGLE:
902 		moName = "eagle_portrait";
903 		break;
904 	case CWWALL_WOOD_HITLER:
905 		moName = "hitler_portrait";
906 		break;
907 	case CWWALL_ENTRANCE:
908 		moName = "elevator_entrance";
909 		break;
910 	case CWWALL_STEEL_SIGN:
911 		moName = "no_sign";
912 		break;
913 	case CWWALL_RED_BRICK_SWASTIKA:
914 		moName = "swastika_wreath";
915 		break;
916 	case CWWALL_RED_BRICK_FLAG:
917 		moName = "coat_of_arms_flag";
918 		break;
919 	case CWWALL_ELEVATOR:
920 		if (MissionStaticGetTileClass(m, levelSize, vBelow)->Type ==
921 			TILE_CLASS_FLOOR)
922 		{
923 			moName = "elevator_interior";
924 		}
925 		// Elevators only occur on east/west tiles
926 		for (int dx = -1; dx <= 1; dx += 2)
927 		{
928 			const struct vec2i exitV = svec2i(v.x + dx, v.y);
929 			const TileClass *tc =
930 				MissionStaticGetTileClass(m, levelSize, exitV);
931 			if (tc != NULL && tc->Type == TILE_CLASS_FLOOR)
932 			{
933 				Exit e;
934 				e.Hidden = true;
935 				e.Mission = missionIndex + 1;
936 				// Check if coming back from secret level
937 				if (map->type == CWMAPTYPE_SOD)
938 				{
939 					if (missionIndex == 19)
940 					{
941 						e.Mission = 4;
942 					}
943 					else if (missionIndex == 20)
944 					{
945 						e.Mission = 12;
946 					}
947 				}
948 				else
949 				{
950 					switch (missionIndex)
951 					{
952 					case 9:
953 						e.Mission = 1;
954 						break;
955 					case 19:
956 						e.Mission = 11;
957 						break;
958 					case 29:
959 						e.Mission = 27;
960 						break;
961 					case 39:
962 						e.Mission = 33;
963 						break;
964 					case 49:
965 						e.Mission = 45;
966 						break;
967 					case 59:
968 						e.Mission = 53;
969 						break;
970 					default:
971 						break;
972 					}
973 				}
974 				e.R.Pos = exitV;
975 				e.R.Size = svec2i_zero();
976 				MissionStaticTryAddExit(m, &e);
977 			}
978 		}
979 		break;
980 	case CWWALL_DEAD_ELEVATOR:
981 		if (MissionStaticGetTileClass(
982 				m, svec2i(level->header.width, level->header.height), vBelow)
983 				->Type == TILE_CLASS_FLOOR)
984 		{
985 			moName = "elevator_interior";
986 		}
987 		break;
988 	case CWWALL_WOOD_IRON_CROSS:
989 		moName = "iron_cross";
990 		break;
991 	case CWWALL_DIRTY_BRICK_1:
992 	case CWWALL_DIRTY_BRICK_2: // fallthrough
993 		moName = "cobble_moss";
994 		break;
995 	case CWWALL_PURPLE_BLOOD:
996 		moName = "bloodstain";
997 		break;
998 	case CWWALL_GREY_BRICK_SIGN:
999 		moName = "no_sign";
1000 		break;
1001 	case CWWALL_BROWN_WEAVE_BLOOD_2:
1002 		moName = "bloodstain";
1003 		break;
1004 	case CWWALL_BROWN_WEAVE_BLOOD_3:
1005 		moName = "bloodstain1";
1006 		break;
1007 	case CWWALL_BROWN_WEAVE_BLOOD_1:
1008 		moName = "bloodstain2";
1009 		break;
1010 	case CWWALL_STAINED_GLASS:
1011 		moName = "hitler_glass";
1012 		break;
1013 	case CWWALL_BLUE_WALL_SKULL:
1014 		moName = "skull_blue";
1015 		break;
1016 	case CWWALL_BLUE_WALL_SWASTIKA:
1017 		moName = "swastika_blue";
1018 		break;
1019 	case CWWALL_GREY_WALL_VENT:
1020 		moName = "wall_vent";
1021 		break;
1022 	case CWWALL_MULTICOLOR_BRICK:
1023 		moName = "brick_color";
1024 		break;
1025 	case CWWALL_BLUE_BRICK_SIGN:
1026 		moName = "no_sign";
1027 		break;
1028 	case CWWALL_GREY_WALL_MAP:
1029 		moName = "map";
1030 		break;
1031 	case CWWALL_BROWN_MARBLE_FLAG:
1032 		moName = "heer_flag";
1033 		break;
1034 	case CWWALL_WOOD_PANEL:
1035 		moName = "panel";
1036 		break;
1037 	case CWWALL_GREY_WALL_HITLER:
1038 		moName = "hitler_poster";
1039 		break;
1040 	case CWWALL_STONE_WALL_1:
1041 	case CWWALL_STONE_WALL_2:	 // fallthrough
1042 	case CWWALL_RAMPART_STONE_1: // fallthrough
1043 	case CWWALL_RAMPART_STONE_2: // fallthrough
1044 		moName = "stone_color";
1045 		break;
1046 	case CWWALL_STONE_WALL_FLAG:
1047 		moName = "heer_flag";
1048 		break;
1049 	case CWWALL_STONE_WALL_WREATH:
1050 		moName = "swastika_wreath";
1051 		break;
1052 	case CWWALL_ELEVATOR_WALL:
1053 		moName = "elevator_interior";
1054 		break;
1055 	default:
1056 		break;
1057 	}
1058 	if (moName != NULL)
1059 	{
1060 		MissionStaticTryAddItem(m, StrMapObject(moName), vBelow);
1061 	}
1062 }
1063 
1064 typedef enum
1065 {
1066 	CHAR_GUARD = 1,
1067 	CHAR_DOG,
1068 	CHAR_SS,
1069 	CHAR_MUTANT,
1070 	CHAR_OFFICER,
1071 	CHAR_PACMAN_GHOST_RED,
1072 	CHAR_PACMAN_GHOST_YELLOW,
1073 	CHAR_PACMAN_GHOST_ROSE,
1074 	CHAR_PACMAN_GHOST_BLUE,
1075 	CHAR_HANS,
1076 	CHAR_SCHABBS,
1077 	CHAR_FAKE_HITLER,
1078 	CHAR_MECHA_HITLER,
1079 	CHAR_HITLER,
1080 	CHAR_OTTO,
1081 	CHAR_GRETEL,
1082 	CHAR_FETTGESICHT,
1083 	CHAR_TRANS,
1084 	CHAR_WILHELM,
1085 	CHAR_UBERMUTANT,
1086 	CHAR_DEATH_KNIGHT,
1087 	CHAR_GHOST,
1088 	CHAR_ANGEL
1089 } WolfChar;
1090 
1091 static void LoadChar(
1092 	Mission *m, const struct vec2i v, const direction_e d, const int charId,
1093 	int *bossObjIdx);
LoadEntity(Mission * m,const uint16_t ch,const CWolfMap * map,const struct vec2i v,const int missionIndex,int * bossObjIdx,int * spearObjIdx)1094 static void LoadEntity(
1095 	Mission *m, const uint16_t ch, const CWolfMap *map, const struct vec2i v,
1096 	const int missionIndex, int *bossObjIdx, int *spearObjIdx)
1097 {
1098 	const CWEntity entity = CWChToEntity(ch);
1099 	switch (entity)
1100 	{
1101 	case CWENT_NONE:
1102 		break;
1103 	case CWENT_PLAYER_SPAWN_N:
1104 	case CWENT_PLAYER_SPAWN_E:
1105 	case CWENT_PLAYER_SPAWN_S:
1106 	case CWENT_PLAYER_SPAWN_W:
1107 		m->u.Static.Start = v;
1108 		// Remove any exits that overlap with start
1109 		// SOD starts the player in elevators
1110 		CA_FOREACH(const Exit, e, m->u.Static.Exits)
1111 		const Rect2i er =
1112 			Rect2iNew(e->R.Pos, svec2i_add(e->R.Size, svec2i_one()));
1113 		if (Rect2iIsInside(er, v))
1114 		{
1115 			CArrayDelete(&m->u.Static.Exits, _ca_index);
1116 			_ca_index--;
1117 		}
1118 		CA_FOREACH_END()
1119 		if (map->type == CWMAPTYPE_SOD && missionIndex == 20)
1120 		{
1121 			// Start the mission with a yellow key
1122 			MissionStaticAddKey(&m->u.Static, 0, v);
1123 		}
1124 		break;
1125 	case CWENT_WATER:
1126 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("pool_water"), v);
1127 		break;
1128 	case CWENT_OIL_DRUM:
1129 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("barrel_green"), v);
1130 		break;
1131 	case CWENT_TABLE_WITH_CHAIRS:
1132 		MissionStaticTryAddItem(
1133 			&m->u.Static, StrMapObject("table_and_chairs"), v);
1134 		break;
1135 	case CWENT_FLOOR_LAMP:
1136 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("rod_light"), v);
1137 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("spotlight"), v);
1138 		break;
1139 	case CWENT_CHANDELIER:
1140 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("chandelier"), v);
1141 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("spotlight"), v);
1142 		break;
1143 	case CWENT_HANGING_SKELETON:
1144 		MissionStaticTryAddItem(
1145 			&m->u.Static, StrMapObject("hanging_skeleton"), v);
1146 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("shadow"), v);
1147 		break;
1148 	case CWENT_DOG_FOOD:
1149 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("dogfood"), v);
1150 		break;
1151 	case CWENT_WHITE_COLUMN:
1152 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("pillar"), v);
1153 		break;
1154 	case CWENT_GREEN_PLANT:
1155 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("plant"), v);
1156 		break;
1157 	case CWENT_SKELETON:
1158 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("bone_blood"), v);
1159 		break;
1160 	case CWENT_SINK_SKULLS_ON_STICK:
1161 		if (map->type == CWMAPTYPE_SOD)
1162 		{
1163 			MissionStaticTryAddItem(
1164 				&m->u.Static, StrMapObject("skull_pillar"), v);
1165 		}
1166 		else
1167 		{
1168 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("sink"), v);
1169 		}
1170 		break;
1171 	case CWENT_BROWN_PLANT:
1172 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("plant_brown"), v);
1173 		break;
1174 	case CWENT_VASE:
1175 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("urn"), v);
1176 		break;
1177 	case CWENT_TABLE:
1178 		MissionStaticTryAddItem(
1179 			&m->u.Static, StrMapObject("table_wood_round"), v);
1180 		break;
1181 	case CWENT_CEILING_LIGHT_GREEN:
1182 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("spotlight"), v);
1183 		break;
1184 	case CWENT_UTENSILS_BROWN_CAGE_BLOODY_BONES:
1185 		if (map->type == CWMAPTYPE_SOD)
1186 		{
1187 			MissionStaticTryAddItem(
1188 				&m->u.Static, StrMapObject("gibbet_bloody"), v);
1189 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("shadow"), v);
1190 		}
1191 		else
1192 		{
1193 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("knives"), v);
1194 		}
1195 		break;
1196 	case CWENT_ARMOR:
1197 		MissionStaticTryAddItem(
1198 			&m->u.Static, StrMapObject("suit_of_armor"), v);
1199 		break;
1200 	case CWENT_CAGE:
1201 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("gibbet"), v);
1202 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("shadow"), v);
1203 		break;
1204 	case CWENT_CAGE_SKELETON:
1205 		MissionStaticTryAddItem(
1206 			&m->u.Static, StrMapObject("gibbet_skeleton"), v);
1207 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("shadow"), v);
1208 		break;
1209 	case CWENT_BONES1:
1210 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("skull"), v);
1211 		break;
1212 	case CWENT_KEY_GOLD:
1213 		MissionStaticAddKey(&m->u.Static, 0, v);
1214 		break;
1215 	case CWENT_KEY_SILVER:
1216 		MissionStaticAddKey(&m->u.Static, 2, v);
1217 		break;
1218 	case CWENT_BED_CAGE_SKULLS:
1219 		if (map->type == CWMAPTYPE_SOD)
1220 		{
1221 			MissionStaticTryAddItem(
1222 				&m->u.Static, StrMapObject("gibbet_skulls"), v);
1223 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("shadow"), v);
1224 		}
1225 		else
1226 		{
1227 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("bed"), v);
1228 		}
1229 		break;
1230 	case CWENT_BASKET:
1231 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("basket"), v);
1232 		break;
1233 	case CWENT_FOOD:
1234 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("meal"), v);
1235 		break;
1236 	case CWENT_MEDKIT:
1237 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("medkit"), v);
1238 		break;
1239 	case CWENT_AMMO:
1240 		MissionStaticTryAddPickup(
1241 			&m->u.Static, StrPickupClass("ammo_clip"), v);
1242 		break;
1243 	case CWENT_MACHINE_GUN:
1244 		MissionStaticTryAddPickup(
1245 			&m->u.Static, StrPickupClass("gun_Machine Gun"), v);
1246 		break;
1247 	case CWENT_CHAIN_GUN:
1248 		MissionStaticTryAddPickup(
1249 			&m->u.Static, StrPickupClass("gun_Chain Gun"), v);
1250 		break;
1251 	case CWENT_CROSS:
1252 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("cross"), v);
1253 		break;
1254 	case CWENT_CHALICE:
1255 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("chalice"), v);
1256 		break;
1257 	case CWENT_CHEST:
1258 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("chest"), v);
1259 		break;
1260 	case CWENT_CROWN:
1261 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("crown"), v);
1262 		break;
1263 	case CWENT_LIFE:
1264 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("heart"), v);
1265 		break;
1266 	case CWENT_BONES_BLOOD:
1267 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("gibs"), v);
1268 		break;
1269 	case CWENT_BARREL:
1270 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("barrel_wood2"), v);
1271 		break;
1272 	case CWENT_WELL_WATER:
1273 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("well_water"), v);
1274 		break;
1275 	case CWENT_WELL:
1276 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("well"), v);
1277 		break;
1278 	case CWENT_POOL_OF_BLOOD:
1279 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("pool_blood"), v);
1280 		break;
1281 	case CWENT_FLAG:
1282 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("flag"), v);
1283 		break;
1284 	case CWENT_CEILING_LIGHT_RED_AARDWOLF:
1285 		if (map->type == CWMAPTYPE_WL6)
1286 		{
1287 			MissionStaticTryAddItem(
1288 				&m->u.Static, StrMapObject("spotlight"), v);
1289 		}
1290 		else
1291 		{
1292 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("skull2"), v);
1293 			break;
1294 		}
1295 		break;
1296 	case CWENT_BONES2:
1297 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("bones"), v);
1298 		break;
1299 	case CWENT_BONES3:
1300 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("bones2"), v);
1301 		break;
1302 	case CWENT_BONES4:
1303 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("bones3"), v);
1304 		break;
1305 	case CWENT_UTENSILS_BLUE_COW_SKULL:
1306 		if (map->type == CWMAPTYPE_SOD)
1307 		{
1308 			MissionStaticTryAddItem(
1309 				&m->u.Static, StrMapObject("cowskull_pillar"), v);
1310 		}
1311 		else
1312 		{
1313 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("pots"), v);
1314 		}
1315 		break;
1316 	case CWENT_STOVE_WELL_BLOOD:
1317 		if (map->type == CWMAPTYPE_SOD)
1318 		{
1319 			MissionStaticTryAddItem(
1320 				&m->u.Static, StrMapObject("well_blood"), v);
1321 		}
1322 		else
1323 		{
1324 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("stove"), v);
1325 		}
1326 		break;
1327 	case CWENT_RACK_ANGEL_STATUE:
1328 		if (map->type == CWMAPTYPE_SOD)
1329 		{
1330 			MissionStaticTryAddItem(
1331 				&m->u.Static, StrMapObject("statue_behemoth"), v);
1332 		}
1333 		else
1334 		{
1335 			MissionStaticTryAddItem(&m->u.Static, StrMapObject("spears"), v);
1336 		}
1337 		break;
1338 	case CWENT_VINES:
1339 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("grass"), v);
1340 		break;
1341 	case CWENT_BROWN_COLUMN:
1342 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("pillar_brown"), v);
1343 		break;
1344 	case CWENT_AMMO_BOX:
1345 		MissionStaticTryAddPickup(&m->u.Static, StrPickupClass("ammo_box"), v);
1346 		break;
1347 	case CWENT_TRUCK_REAR:
1348 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("truck"), v);
1349 		break;
1350 	case CWENT_SPEAR:
1351 		if (*spearObjIdx < 0)
1352 		{
1353 			Objective o;
1354 			memset(&o, 0, sizeof o);
1355 			o.Type = OBJECTIVE_COLLECT;
1356 			o.u.Pickup = StrPickupClass("spear");
1357 			CSTRDUP(o.Description, "Collect spear");
1358 			CArrayPushBack(&m->Objectives, &o);
1359 			*spearObjIdx = (int)m->Objectives.size - 1;
1360 		}
1361 		Objective *spearObj = CArrayGet(&m->Objectives, *spearObjIdx);
1362 		spearObj->Required++;
1363 		MissionStaticAddObjective(m, &m->u.Static, *spearObjIdx, 0, v, false);
1364 		break;
1365 	case CWENT_PUSHWALL: {
1366 		const CWLevel *level = &map->levels[missionIndex];
1367 		int *tile =
1368 			CArrayGet(&m->u.Static.Tiles, v.x + v.y * level->header.width);
1369 		*tile += TILE_CLASS_WALL_OFFSET;
1370 	}
1371 	break;
1372 	case CWENT_ENDGAME: {
1373 		Exit e;
1374 		e.Hidden = true;
1375 		// Skip over the secret level to the next episode
1376 		e.Mission = missionIndex + 2;
1377 		e.R.Pos = v;
1378 		e.R.Size = svec2i_zero();
1379 		CArrayPushBack(&m->u.Static.Exits, &e);
1380 	}
1381 	break;
1382 	case CWENT_GHOST:
1383 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_GHOST, bossObjIdx);
1384 		break;
1385 	case CWENT_ANGEL:
1386 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_ANGEL, bossObjIdx);
1387 		break;
1388 	case CWENT_DEAD_GUARD:
1389 		MissionStaticTryAddItem(&m->u.Static, StrMapObject("dead_guard"), v);
1390 		break;
1391 	case CWENT_DOG_E:
1392 		LoadChar(m, v, DIRECTION_RIGHT, (int)CHAR_DOG, bossObjIdx);
1393 		break;
1394 	case CWENT_DOG_N:
1395 		LoadChar(m, v, DIRECTION_UP, (int)CHAR_DOG, bossObjIdx);
1396 		break;
1397 	case CWENT_DOG_W:
1398 		LoadChar(m, v, DIRECTION_LEFT, (int)CHAR_DOG, bossObjIdx);
1399 		break;
1400 	case CWENT_DOG_S:
1401 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_DOG, bossObjIdx);
1402 		break;
1403 	case CWENT_GUARD_E:
1404 		LoadChar(m, v, DIRECTION_RIGHT, (int)CHAR_GUARD, bossObjIdx);
1405 		break;
1406 	case CWENT_GUARD_N:
1407 		LoadChar(m, v, DIRECTION_UP, (int)CHAR_GUARD, bossObjIdx);
1408 		break;
1409 	case CWENT_GUARD_W:
1410 		LoadChar(m, v, DIRECTION_LEFT, (int)CHAR_GUARD, bossObjIdx);
1411 		break;
1412 	case CWENT_GUARD_S:
1413 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_GUARD, bossObjIdx);
1414 		break;
1415 	case CWENT_SS_E:
1416 		LoadChar(m, v, DIRECTION_RIGHT, (int)CHAR_SS, bossObjIdx);
1417 		break;
1418 	case CWENT_SS_N:
1419 		LoadChar(m, v, DIRECTION_UP, (int)CHAR_SS, bossObjIdx);
1420 		break;
1421 	case CWENT_SS_W:
1422 		LoadChar(m, v, DIRECTION_LEFT, (int)CHAR_SS, bossObjIdx);
1423 		break;
1424 	case CWENT_SS_S:
1425 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_SS, bossObjIdx);
1426 		break;
1427 	case CWENT_MUTANT_E:
1428 		LoadChar(m, v, DIRECTION_RIGHT, (int)CHAR_MUTANT, bossObjIdx);
1429 		break;
1430 	case CWENT_MUTANT_N:
1431 		LoadChar(m, v, DIRECTION_UP, (int)CHAR_MUTANT, bossObjIdx);
1432 		break;
1433 	case CWENT_MUTANT_W:
1434 		LoadChar(m, v, DIRECTION_LEFT, (int)CHAR_MUTANT, bossObjIdx);
1435 		break;
1436 	case CWENT_MUTANT_S:
1437 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_MUTANT, bossObjIdx);
1438 		break;
1439 	case CWENT_OFFICER_E:
1440 		LoadChar(m, v, DIRECTION_RIGHT, (int)CHAR_OFFICER, bossObjIdx);
1441 		break;
1442 	case CWENT_OFFICER_N:
1443 		LoadChar(m, v, DIRECTION_UP, (int)CHAR_OFFICER, bossObjIdx);
1444 		break;
1445 	case CWENT_OFFICER_W:
1446 		LoadChar(m, v, DIRECTION_LEFT, (int)CHAR_OFFICER, bossObjIdx);
1447 		break;
1448 	case CWENT_OFFICER_S:
1449 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_OFFICER, bossObjIdx);
1450 		break;
1451 	case CWENT_TURN_E:
1452 	case CWENT_TURN_NE:
1453 	case CWENT_TURN_N:
1454 	case CWENT_TURN_NW:
1455 	case CWENT_TURN_W:
1456 	case CWENT_TURN_SW:
1457 	case CWENT_TURN_S:
1458 	case CWENT_TURN_SE:
1459 		break;
1460 	case CWENT_TRANS:
1461 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_TRANS, bossObjIdx);
1462 		break;
1463 	case CWENT_UBER_MUTANT:
1464 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_UBERMUTANT, bossObjIdx);
1465 		break;
1466 	case CWENT_BARNACLE_WILHELM:
1467 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_WILHELM, bossObjIdx);
1468 		break;
1469 	case CWENT_ROBED_HITLER:
1470 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_FAKE_HITLER, bossObjIdx);
1471 		break;
1472 	case CWENT_DEATH_KNIGHT:
1473 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_DEATH_KNIGHT, bossObjIdx);
1474 		break;
1475 	case CWENT_HITLER:
1476 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_MECHA_HITLER, bossObjIdx);
1477 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_HITLER, bossObjIdx);
1478 		break;
1479 	case CWENT_FETTGESICHT:
1480 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_FETTGESICHT, bossObjIdx);
1481 		break;
1482 	case CWENT_SCHABBS:
1483 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_SCHABBS, bossObjIdx);
1484 		break;
1485 	case CWENT_GRETEL:
1486 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_GRETEL, bossObjIdx);
1487 		break;
1488 	case CWENT_HANS:
1489 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_HANS, bossObjIdx);
1490 		break;
1491 	case CWENT_OTTO:
1492 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_OTTO, bossObjIdx);
1493 		break;
1494 	case CWENT_PACMAN_GHOST_RED:
1495 		LoadChar(m, v, DIRECTION_DOWN, (int)CHAR_PACMAN_GHOST_RED, bossObjIdx);
1496 		break;
1497 	case CWENT_PACMAN_GHOST_YELLOW:
1498 		LoadChar(
1499 			m, v, DIRECTION_DOWN, (int)CHAR_PACMAN_GHOST_YELLOW, bossObjIdx);
1500 		break;
1501 	case CWENT_PACMAN_GHOST_ROSE:
1502 		LoadChar(
1503 			m, v, DIRECTION_DOWN, (int)CHAR_PACMAN_GHOST_ROSE, bossObjIdx);
1504 		break;
1505 	case CWENT_PACMAN_GHOST_BLUE:
1506 		LoadChar(
1507 			m, v, DIRECTION_DOWN, (int)CHAR_PACMAN_GHOST_BLUE, bossObjIdx);
1508 		break;
1509 	default:
1510 		CASSERT(false, "unknown entity");
1511 		break;
1512 	}
1513 }
LoadChar(Mission * m,const struct vec2i v,const direction_e d,const int charId,int * bossObjIdx)1514 static void LoadChar(
1515 	Mission *m, const struct vec2i v, const direction_e d, const int charId,
1516 	int *bossObjIdx)
1517 {
1518 	CharacterPlace cp = {v, d};
1519 	switch (charId)
1520 	{
1521 	case CHAR_SCHABBS:
1522 	case CHAR_MECHA_HITLER:
1523 	case CHAR_HITLER:
1524 	case CHAR_OTTO:
1525 	case CHAR_FETTGESICHT:
1526 	case CHAR_ANGEL: {
1527 		CArrayPushBack(&m->SpecialChars, &charId);
1528 		if (*bossObjIdx < 0)
1529 		{
1530 			Objective o;
1531 			memset(&o, 0, sizeof o);
1532 			o.Type = OBJECTIVE_KILL;
1533 			CSTRDUP(o.Description, "Kill boss");
1534 			CArrayPushBack(&m->Objectives, &o);
1535 			*bossObjIdx = (int)m->Objectives.size - 1;
1536 		}
1537 		Objective *bossObj = CArrayGet(&m->Objectives, *bossObjIdx);
1538 		bossObj->Required++;
1539 		MissionStaticAddObjective(
1540 			m, &m->u.Static, *bossObjIdx, (int)m->SpecialChars.size - 1, v,
1541 			true);
1542 	}
1543 	break;
1544 	default:
1545 		MissionStaticAddCharacter(&m->u.Static, charId, cp);
1546 		break;
1547 	}
1548 }
1549 
1550 static bool TryLoadCampaign(CampaignList *list, const char *path);
MapWolfLoadCampaignsFromSystem(CampaignList * list)1551 void MapWolfLoadCampaignsFromSystem(CampaignList *list)
1552 {
1553 	char buf[CDOGS_PATH_MAX];
1554 
1555 	// Wolf
1556 	fsg_get_steam_game_path(buf, WOLF_STEAM_NAME);
1557 	if (strlen(buf) > 0)
1558 	{
1559 		// Steam installs to /base
1560 		strcat(buf, "/base");
1561 	}
1562 	if (!TryLoadCampaign(list, buf))
1563 	{
1564 		fsg_get_gog_game_path(buf, WOLF_GOG_ID);
1565 		TryLoadCampaign(list, buf);
1566 	}
1567 
1568 	// Spear
1569 	fsg_get_steam_game_path(buf, SPEAR_STEAM_NAME);
1570 	if (strlen(buf) > 0)
1571 	{
1572 		// Steam installs to /base
1573 		strcat(buf, "/base");
1574 	}
1575 	if (!TryLoadCampaign(list, buf))
1576 	{
1577 		fsg_get_gog_game_path(buf, SPEAR_GOG_ID);
1578 		if (strlen(buf) > 0)
1579 		{
1580 			strcat(buf, "/M1"); // Only support original spear
1581 		}
1582 		TryLoadCampaign(list, buf);
1583 	}
1584 }
TryLoadCampaign(CampaignList * list,const char * path)1585 static bool TryLoadCampaign(CampaignList *list, const char *path)
1586 {
1587 	if (strlen(path) > 0)
1588 	{
1589 		CampaignEntry entry;
1590 		if (CampaignEntryTryLoad(&entry, path, GAME_MODE_NORMAL))
1591 		{
1592 			CArrayPushBack(&list->list, &entry);
1593 			return true;
1594 		}
1595 	}
1596 	return false;
1597 }
1598