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