From 6663fba661413fc6a3200680da4ad0599e0a9263 Mon Sep 17 00:00:00 2001 From: Roman Fomin Date: Tue, 3 Sep 2024 14:44:46 +0700 Subject: [PATCH] id24: intermissions (#1833) * use cJSON library * implement array_foreach, custom memory allocator for m_array * use Doom 1 duration of the "entering" screen for id24 animation * add player->visitedlevels array * add U_BuildEpisodes function, try to emulate behaviour of KEX port and R&R --- .github/workflows/main.yml | 10 +- CMakeLists.txt | 1 + src/CMakeLists.txt | 4 +- src/d_main.c | 1 + src/d_player.h | 16 +- src/g_game.c | 11 +- src/m_array.h | 41 +++-- src/p_saveg.c | 33 ++++ src/p_saveg.h | 3 +- src/u_mapinfo.c | 70 +++++++- src/u_mapinfo.h | 17 ++ src/wi_interlvl.c | 217 +++++++++++++++++++++++ src/wi_interlvl.h | 90 ++++++++++ src/wi_stuff.c | 352 ++++++++++++++++++++++++++++++++++++- vcpkg.json | 3 +- 15 files changed, 845 insertions(+), 24 deletions(-) create mode 100644 src/wi_interlvl.c create mode 100644 src/wi_interlvl.h diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f2e050ba..f5a26b76 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -51,7 +51,8 @@ jobs: libopenal-dev \ libsndfile1-dev \ libfluidsynth-dev \ - libxmp-dev + libxmp-dev \ + libcjson-dev - name: Install dependencies (macOS) if: runner.os == 'macOS' @@ -63,7 +64,8 @@ jobs: openal-soft \ libsndfile \ fluid-synth \ - libxmp + libxmp \ + cjson - name: Install dependencies (MSYS2) if: matrix.config.shell == 'msys2 {0}' @@ -83,6 +85,7 @@ jobs: ${{ matrix.config.msys-env }}-libsndfile ${{ matrix.config.msys-env }}-fluidsynth ${{ matrix.config.msys-env }}-libxmp + ${{ matrix.config.msys-env }}-cjson - uses: actions/checkout@v4 @@ -140,7 +143,8 @@ jobs: libopenal-dev \ libsndfile1-dev \ libfluidsynth-dev \ - libxmp-dev + libxmp-dev \ + libcjson-dev - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index b10ba459..fc6126e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,7 @@ find_package(SndFile 1.0.29 REQUIRED) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(ALSA REQUIRED) endif() +find_package(cJSON REQUIRED) if(ALSA_FOUND) set(HAVE_ALSA TRUE) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6282fe35..0f9a2de6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -142,6 +142,7 @@ set(WOOF_SOURCES w_file.c w_zip.c wi_stuff.c wi_stuff.h + wi_interlvl.c wi_interlvl.h z_zone.c z_zone.h) # Standard target definition @@ -187,7 +188,8 @@ target_link_libraries(woof PRIVATE opl textscreen miniz - spng) + spng + cjson) # Some platforms require standard libraries to be linked against. if(HAVE_LIBM) diff --git a/src/d_main.c b/src/d_main.c index 38002a82..484b2758 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -2345,6 +2345,7 @@ void D_DoomMain(void) { W_ProcessInWads("UMAPINFO", U_ParseMapInfo, true); W_ProcessInWads("UMAPINFO", U_ParseMapInfo, false); + U_BuildEpisodes(); } if (!M_CheckParm("-save")) diff --git a/src/d_player.h b/src/d_player.h index af268cf8..f23e2933 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -24,6 +24,7 @@ // of other structs: items (internal inventory), // animation states (closely tied to the sprites // used to represent them, unfortunately). +#include "doomtype.h" #include "p_pspr.h" #include "tables.h" @@ -52,6 +53,11 @@ typedef enum } playerstate_t; +typedef struct +{ + int episode; + int map; +} level_t; // // Player internal flags, for cheats and debug. @@ -214,6 +220,9 @@ typedef struct player_s // Local angle for blending per-frame and per-tic turning. angle_t ticangle, oldticangle; + int num_visitedlevels; + level_t *visitedlevels; + } player_t; @@ -232,7 +241,7 @@ typedef struct int stime; int frags[4]; int score; // current score on entry, modified on return - + } wbplayerstruct_t; typedef struct wbstartstruct_s @@ -245,7 +254,8 @@ typedef struct wbstartstruct_s // previous and next levels, origin 0 int last; int next; - int nextep; // for when MAPINFO progression crosses into another episode. + // for when MAPINFO progression crosses into another episode. + int nextep; struct mapentry_s *lastmapinfo; struct mapentry_s *nextmapinfo; @@ -265,6 +275,8 @@ typedef struct wbstartstruct_s // [FG] total time for all completed levels int totaltimes; + level_t *visitedlevels; + } wbstartstruct_t; diff --git a/src/g_game.c b/src/g_game.c index bd7245dd..0bbe1e9a 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1822,6 +1822,14 @@ frommapinfo: StatCopy(&wminfo); } + for (int i = 0; i < MAXPLAYERS; ++i) + { + level_t level = {gameepisode, gamemap}; + array_push(players[i].visitedlevels, level); + players[i].num_visitedlevels = array_size(players[i].visitedlevels); + } + wminfo.visitedlevels = players[consoleplayer].visitedlevels; + WI_Start (&wminfo); } @@ -2133,7 +2141,7 @@ static void G_DoPlayDemo(void) // killough 2/22/98: version id string format for savegames #define VERSIONID "MBF %d" -#define CURRENT_SAVE_VERSION "Woof 13.0.0" +#define CURRENT_SAVE_VERSION "Woof 15.0.0" static char *savename = NULL; @@ -2432,6 +2440,7 @@ static void G_DoLoadGame(void) CheckSaveVersion(vcheck, saveg_mbf); CheckSaveVersion("Woof 6.0.0", saveg_woof600); + CheckSaveVersion("Woof 13.0.0", saveg_woof1300); CheckSaveVersion(CURRENT_SAVE_VERSION, saveg_current); // killough 2/22/98: Friendly savegame version difference message diff --git a/src/m_array.h b/src/m_array.h index 6579ce2e..2955c663 100644 --- a/src/m_array.h +++ b/src/m_array.h @@ -18,6 +18,9 @@ // array_push(), array_grow() and array_free() may change the buffer pointer, // and any previously-taken pointers should be considered invalidated. +#ifndef M_ARRAY_H +#define M_ARRAY_H + #include #include @@ -27,6 +30,18 @@ # define M_ARRAY_INIT_CAPACITY 8 #endif +#ifndef M_ARRAY_MALLOC +# define M_ARRAY_MALLOC(size) malloc((size)) +#endif + +#ifndef M_ARRAY_REALLOC +# define M_ARRAY_REALLOC(ptr, size) I_Realloc((ptr), (size)) +#endif + +#ifndef M_ARRAY_FREE +# define M_ARRAY_FREE(ptr) free((ptr)) +#endif + typedef struct { int capacity; @@ -73,16 +88,19 @@ inline static void array_clear(const void *v) (v)[array_ptr((v))->size++] = (e); \ } while (0) -#define array_free(v) \ - do \ - { \ - if (v) \ - { \ - free(array_ptr((v))); \ - (v) = NULL; \ - } \ +#define array_free(v) \ + do \ + { \ + if (v) \ + { \ + M_ARRAY_FREE(array_ptr((v))); \ + (v) = NULL; \ + } \ } while (0) +#define array_foreach(ptr, v) \ + for (ptr = (v); ptr != &(v)[array_size((v))]; ++ptr) + inline static void *M_ArrayGrow(void *v, size_t esize, int n) { m_array_buffer_t *p; @@ -90,15 +108,18 @@ inline static void *M_ArrayGrow(void *v, size_t esize, int n) if (v) { p = array_ptr(v); - p = I_Realloc(p, sizeof(m_array_buffer_t) + (p->capacity + n) * esize); + p = M_ARRAY_REALLOC(p, sizeof(m_array_buffer_t) + + (p->capacity + n) * esize); p->capacity += n; } else { - p = malloc(sizeof(m_array_buffer_t) + n * esize); + p = M_ARRAY_MALLOC(sizeof(m_array_buffer_t) + n * esize); p->capacity = n; p->size = 0; } return p->buffer; } + +#endif // M_ARRAY_H diff --git a/src/p_saveg.c b/src/p_saveg.c index e1694d0d..04e37f02 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -28,6 +28,7 @@ #include "doomstat.h" #include "i_system.h" #include "info.h" +#include "m_array.h" #include "m_random.h" #include "p_enemy.h" #include "p_maputl.h" @@ -973,6 +974,27 @@ static void saveg_read_player_t(player_t *str) str->slope = 0; str->maxkilldiscount = 0; } + + if (saveg_compat > saveg_woof1300) + { + // [Woof!]: int num_visitedlevels; + str->num_visitedlevels = saveg_read32(); + + // [Woof!]: level_t *visitedlevels; + array_clear(str->visitedlevels); + for (int i = 0; i < str->num_visitedlevels; ++i) + { + level_t level = {0}; + level.episode = saveg_read32(); + level.map = saveg_read32(); + array_push(str->visitedlevels, level); + } + } + else + { + str->num_visitedlevels = 0; + array_clear(str->visitedlevels); + } } static void saveg_write_player_t(player_t *str) @@ -1125,6 +1147,17 @@ static void saveg_write_player_t(player_t *str) // [Woof!]: int maxkilldiscount; saveg_write32(str->maxkilldiscount); + + // [Woof!]: int num_visitedlevels; + saveg_write32(str->num_visitedlevels); + + // [Woof!]: level_t *visitedlevels; + level_t *level; + array_foreach(level, str->visitedlevels) + { + saveg_write32(level->episode); + saveg_write32(level->map); + } } diff --git a/src/p_saveg.h b/src/p_saveg.h index a2dca8d5..49649b25 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -56,7 +56,8 @@ typedef enum saveg_compat_e saveg_mbf, saveg_woof510, saveg_woof600, - saveg_current, // saveg_woof1300 + saveg_woof1300, + saveg_current, // saveg_woof1500 } saveg_compat_t; extern saveg_compat_t saveg_compat; diff --git a/src/u_mapinfo.c b/src/u_mapinfo.c index b86666b8..aaa9744e 100644 --- a/src/u_mapinfo.c +++ b/src/u_mapinfo.c @@ -23,7 +23,9 @@ #include "doomdef.h" #include "doomstat.h" +#include "doomtype.h" #include "i_system.h" +#include "m_array.h" #include "m_misc.h" #include "u_mapinfo.h" #include "u_scanner.h" @@ -40,6 +42,8 @@ umapinfo_t U_mapinfo; umapinfo_t default_mapinfo; +static level_t *secretlevels; + static const char *const ActorNames[] = { "DoomPlayer", "ZombieMan", "ShotgunGuy", "Archvile", "ArchvileFire", @@ -273,6 +277,14 @@ static void UpdateMapEntry(mapentry_t *mape, mapentry_t *newe) { strcpy(mape->enterpic, newe->enterpic); } + if (newe->exitanim[0]) + { + strcpy(mape->exitanim, newe->exitanim); + } + if (newe->enteranim[0]) + { + strcpy(mape->enteranim, newe->enteranim); + } if (newe->interbackdrop[0]) { strcpy(mape->interbackdrop, newe->interbackdrop); @@ -466,6 +478,7 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) } M_AddEpisode(mape->mapname, lumpname, alttext, key); + mape->flags |= MapInfo_Episode; if (alttext) { @@ -489,11 +502,13 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) else if (!strcasecmp(pname, "nextsecret")) { status = ParseLumpName(s, mape->nextsecret); - if (!G_ValidateMapName(mape->nextsecret, NULL, NULL)) + level_t level = {0}; + if (!G_ValidateMapName(mape->nextsecret, &level.episode, &level.map)) { U_Error(s, "Invalid map name %s", mape->nextsecret); status = 0; } + array_push(secretlevels, level); } else if (!strcasecmp(pname, "levelpic")) { @@ -517,6 +532,7 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) if (s->sc_boolean) { strcpy(mape->endpic, "$CAST"); + mape->flags |= MapInfo_Endgame; } else { @@ -529,6 +545,7 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) if (s->sc_boolean) { strcpy(mape->endpic, "$BUNNY"); + mape->flags |= MapInfo_Endgame; } else { @@ -541,6 +558,7 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) if (s->sc_boolean) { strcpy(mape->endpic, "!"); + mape->flags |= MapInfo_Endgame; } else { @@ -555,6 +573,14 @@ static int ParseStandardProperty(u_scanner_t *s, mapentry_t *mape) { status = ParseLumpName(s, mape->enterpic); } + else if (!strcasecmp(pname, "exitanim")) + { + status = ParseLumpName(s, mape->exitanim); + } + else if (!strcasecmp(pname, "enteranim")) + { + status = ParseLumpName(s, mape->enteranim); + } else if (!strcasecmp(pname, "nointermission")) { if (U_MustGetToken(s, TK_BoolConst)) @@ -847,3 +873,45 @@ boolean U_CheckField(char *str) { return str && str[0] && strcmp(str, "-"); } + +boolean U_IsSecretMap(int episode, int map) +{ + level_t *level; + array_foreach(level, secretlevels) + { + if (level->episode == episode && level->map == map) + { + return true; + } + } + return false; +} + +void U_BuildEpisodes(void) +{ + boolean episode_started; + int current_map_number = 1, all_number = 1; + + for (int i = 0; i < U_mapinfo.mapcount; ++i) + { + mapentry_t *mape = &U_mapinfo.maps[i]; + + if (mape->flags & MapInfo_Episode) + { + episode_started = true; + current_map_number = 1; + } + + if (episode_started) + { + mape->map_number = current_map_number++; + } + + if (mape->flags & MapInfo_Endgame) + { + episode_started = false; + } + + mape->all_number = all_number++; + } +} diff --git a/src/u_mapinfo.h b/src/u_mapinfo.h index 0128fc5c..644077ca 100644 --- a/src/u_mapinfo.h +++ b/src/u_mapinfo.h @@ -30,6 +30,13 @@ typedef struct int tag; } bossaction_t; +typedef enum +{ + MapInfo_None = 0x0001, + MapInfo_Episode = 0x0002, + MapInfo_Endgame = 0x0004, +} mapinfo_flags_t; + typedef struct mapentry_s { char *mapname; @@ -46,9 +53,14 @@ typedef struct mapentry_s char endpic[9]; char exitpic[9]; char enterpic[9]; + char exitanim[9]; + char enteranim[9]; char interbackdrop[9]; char intermusic[9]; int partime; + mapinfo_flags_t flags; + int map_number; + int all_number; boolean nointermission; int numbossactions; bossaction_t *bossactions; @@ -64,6 +76,7 @@ extern umapinfo_t U_mapinfo; extern umapinfo_t default_mapinfo; extern boolean EpiCustom; + mapentry_t *G_LookupMapinfo(int episode, int map); boolean U_CheckField(char *str); @@ -72,4 +85,8 @@ void U_ParseMapDefInfo(int lumpnum); void U_ParseMapInfo(int lumpnum); +void U_BuildEpisodes(void); + +boolean U_IsSecretMap(int episode, int map); + #endif diff --git a/src/wi_interlvl.c b/src/wi_interlvl.c new file mode 100644 index 00000000..e5f94fd9 --- /dev/null +++ b/src/wi_interlvl.c @@ -0,0 +1,217 @@ +// +// Copyright(C) 2024 Roman Fomin +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#include "wi_interlvl.h" + +#include +#include + +#include "doomtype.h" +#include "i_printf.h" +#include "w_wad.h" +#include "z_zone.h" + +#define M_ARRAY_MALLOC(size) Z_Malloc((size), PU_LEVEL, NULL) +#define M_ARRAY_REALLOC(ptr, size) Z_Realloc((ptr), (size), PU_LEVEL, NULL) +#define M_ARRAY_FREE(ptr) Z_Free((ptr)) +#include "m_array.h" + +#include "cjson/cJSON.h" + +static char *WI_StringDuplicate(const char *orig) +{ + return strcpy(Z_Malloc(strlen(orig) + 1, PU_LEVEL, NULL), orig); +} + +static boolean ParseCondition(cJSON *json, interlevelcond_t *out) +{ + cJSON *condition = cJSON_GetObjectItemCaseSensitive(json, "condition"); + if (!cJSON_IsNumber(condition)) + { + return false; + } + out->condition = condition->valueint; + + cJSON *param = cJSON_GetObjectItemCaseSensitive(json, "param"); + if (!cJSON_IsNumber(param)) + { + return false; + } + out->param = param->valueint; + + return true; +} + +static boolean ParseFrame(cJSON *json, interlevelframe_t *out) +{ + cJSON *image_lump = cJSON_GetObjectItemCaseSensitive(json, "image"); + if (!cJSON_IsString(image_lump)) + { + return false; + } + out->image_lump = WI_StringDuplicate(image_lump->valuestring); + + cJSON *type = cJSON_GetObjectItemCaseSensitive(json, "type"); + if (!cJSON_IsNumber(type)) + { + return false; + } + out->type = type->valueint; + + cJSON *duration = cJSON_GetObjectItemCaseSensitive(json, "duration"); + if (!cJSON_IsNumber(duration)) + { + return false; + } + out->duration = duration->valuedouble; + + cJSON *maxduration = cJSON_GetObjectItemCaseSensitive(json, "maxduration"); + if (!cJSON_IsNumber(maxduration)) + { + return false; + } + out->maxduration = maxduration->valuedouble; + + return true; +} + +static boolean ParseAnim(cJSON *json, interlevelanim_t *out) +{ + cJSON *js_frames = cJSON_GetObjectItemCaseSensitive(json, "frames"); + cJSON *js_frame = NULL; + interlevelframe_t *frames = NULL; + + cJSON_ArrayForEach(js_frame, js_frames) + { + interlevelframe_t frame = {0}; + if (ParseFrame(js_frame, &frame)) + { + array_push(frames, frame); + } + } + out->frames = frames; + + cJSON *x_pos = cJSON_GetObjectItemCaseSensitive(json, "x"); + cJSON *y_pos = cJSON_GetObjectItemCaseSensitive(json, "y"); + if (!cJSON_IsNumber(x_pos) || !cJSON_IsNumber(y_pos)) + { + return false; + } + out->x_pos = x_pos->valueint; + out->y_pos = y_pos->valueint; + + cJSON *js_conditions = cJSON_GetObjectItemCaseSensitive(json, "conditions"); + cJSON *js_condition = NULL; + interlevelcond_t *conditions = NULL; + + cJSON_ArrayForEach(js_condition, js_conditions) + { + interlevelcond_t condition = {0}; + if (ParseCondition(js_condition, &condition)) + { + array_push(conditions, condition); + } + } + out->conditions = conditions; + + return true; +} + +static void ParseLevelLayer(cJSON *json, interlevellayer_t *out) +{ + cJSON *js_anims = cJSON_GetObjectItemCaseSensitive(json, "anims"); + cJSON *js_anim = NULL; + interlevelanim_t *anims = NULL; + + cJSON_ArrayForEach(js_anim, js_anims) + { + interlevelanim_t anim = {0}; + if (ParseAnim(js_anim, &anim)) + { + array_push(anims, anim); + } + } + out->anims = anims; + + cJSON *js_conditions = cJSON_GetObjectItemCaseSensitive(json, "conditions"); + cJSON *js_condition = NULL; + interlevelcond_t *conditions = NULL; + + cJSON_ArrayForEach(js_condition, js_conditions) + { + interlevelcond_t condition = {0}; + if (ParseCondition(js_condition, &condition)) + { + array_push(conditions, condition); + } + } + out->conditions = conditions; +} + +interlevel_t *WI_ParseInterlevel(const char *lumpname) +{ + interlevel_t *out = Z_Calloc(1, sizeof(*out), PU_LEVEL, NULL); + + cJSON *json = cJSON_Parse(W_CacheLumpName(lumpname, PU_CACHE)); + if (json == NULL) + { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) + { + char error_buf[32] = {0}; + memcpy(error_buf, error_ptr, sizeof(error_buf) - 1); + I_Printf(VB_ERROR, "WI_ParseInterlevel: Error before: %s\n", + error_buf); + } + free(out); + cJSON_Delete(json); + return NULL; + } + + cJSON *data = cJSON_GetObjectItemCaseSensitive(json, "data"); + if (!cJSON_IsObject(data)) + { + free(out); + cJSON_Delete(json); + return NULL; + } + + cJSON *music = cJSON_GetObjectItemCaseSensitive(data, "music"); + cJSON *backgroundimage = + cJSON_GetObjectItemCaseSensitive(data, "backgroundimage"); + + if (!cJSON_IsString(music) || !cJSON_IsString(backgroundimage)) + { + free(out); + cJSON_Delete(json); + return NULL; + } + + out->music_lump = WI_StringDuplicate(music->valuestring); + out->background_lump = WI_StringDuplicate(backgroundimage->valuestring); + + cJSON *js_layers = cJSON_GetObjectItemCaseSensitive(data, "layers"); + cJSON *js_layer = NULL; + interlevellayer_t *layers = NULL; + + cJSON_ArrayForEach(js_layer, js_layers) + { + interlevellayer_t layer = {0}; + ParseLevelLayer(js_layer, &layer); + array_push(layers, layer); + } + out->layers = layers; + + cJSON_Delete(json); + return out; +} diff --git a/src/wi_interlvl.h b/src/wi_interlvl.h new file mode 100644 index 00000000..15dd849c --- /dev/null +++ b/src/wi_interlvl.h @@ -0,0 +1,90 @@ +// +// Copyright(C) 2024 Roman Fomin +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +#ifndef WI_INTERLVL +#define WI_INTERLVL + +typedef enum +{ + AnimCondition_None, + + AnimCondition_MapNumGreater, // Checks: Current/next map number. + // Parameter: map number + + AnimCondition_MapNumEqual, // Checks: Current/next map number. + // Parameter: map number + + AnimCondition_MapVisited, // Checks: Visited flag for map number. + // Parameter: map number + + AnimCondition_MapNotSecret, // Checks: Current/next map. + // Parameter: none + + AnimCondition_SecretVisited, // Checks: Any secret map visited. + // Parameter: none + + AnimCondition_Tally, // Checks: Victory screen type + // Parameter: none + + AnimCondition_IsEntering, // Checks: Victory screen type + // Parameter: none +} animcondition_t; + +typedef enum +{ + Frame_None = 0x0000, + Frame_Infinite = 0x0001, + Frame_FixedDuration = 0x0002, + Frame_RandomDuration = 0x0004, + Frame_RandomStart = 0x1000, +} frametype_t; + +typedef struct interlevelcond_s +{ + animcondition_t condition; + int param; +} interlevelcond_t; + +typedef struct interlevelframe_s +{ + char *image_lump; + int image_lumpnum; + frametype_t type; + double duration; + double maxduration; +} interlevelframe_t; + +typedef struct interlevelanim_s +{ + interlevelframe_t *frames; + interlevelcond_t *conditions; + int x_pos; + int y_pos; +} interlevelanim_t; + +typedef struct interlevellayer_s +{ + interlevelanim_t *anims; + interlevelcond_t *conditions; +} interlevellayer_t; + +typedef struct interlevel_s +{ + char *music_lump; + char *background_lump; + interlevellayer_t *layers; +} interlevel_t; + +interlevel_t *WI_ParseInterlevel(const char *lumpname); + +#endif diff --git a/src/wi_stuff.c b/src/wi_stuff.c index fa89a356..85ac96d8 100644 --- a/src/wi_stuff.c +++ b/src/wi_stuff.c @@ -42,6 +42,7 @@ #include "v_fmt.h" #include "v_video.h" #include "w_wad.h" +#include "wi_interlvl.h" #include "wi_stuff.h" #include "z_zone.h" @@ -385,10 +386,296 @@ static int num_lnames; static const char *exitpic, *enterpic; +#define M_ARRAY_MALLOC(size) Z_Malloc((size), PU_LEVEL, NULL) +#define M_ARRAY_REALLOC(ptr, size) Z_Realloc((ptr), (size), PU_LEVEL, NULL) +#define M_ARRAY_FREE(ptr) Z_Free((ptr)) +#include "m_array.h" + +typedef struct +{ + interlevelframe_t *frames; + int x_pos; + int y_pos; + int frame_index; + boolean frame_start; + int duration_left; +} wi_animationstate_t; + +typedef struct +{ + interlevel_t *interlevel_exiting; + interlevel_t *interlevel_entering; + + wi_animationstate_t *exiting_states; + wi_animationstate_t *entering_states; + + wi_animationstate_t *states; + int background_lumpnum; +} wi_animation_t; + +static wi_animation_t *animation; + // // CODE // +static boolean CheckConditions(interlevelcond_t *conditions, + boolean enteringcondition) +{ + boolean conditionsmet = true; + + int map_number, map, episode; + + { + mapentry_t *mape; + if (enteringcondition) + { + mape = wbs->nextmapinfo; + map = wbs->next + 1; + episode = wbs->nextep + 1; + } + else + { + mape = wbs->lastmapinfo; + map = wbs->last + 1; + episode = wbs->epsd + 1; + } + map_number = mape->map_number ? mape->map_number : mape->all_number; + } + + interlevelcond_t *cond; + array_foreach(cond, conditions) + { + switch (cond->condition) + { + case AnimCondition_MapNumGreater: + conditionsmet = (map_number > cond->param); + break; + + case AnimCondition_MapNumEqual: + conditionsmet = (map_number == cond->param); + break; + + case AnimCondition_MapVisited: + conditionsmet = false; + + level_t *level; + array_foreach(level, wbs->visitedlevels) + { + mapentry_t *mape = + G_LookupMapinfo(level->episode, level->map); + + if ((mape->map_number && mape->map_number == cond->param) + || mape->all_number == cond->param) + { + conditionsmet = true; + break; + } + } + break; + + case AnimCondition_MapNotSecret: + conditionsmet = !U_IsSecretMap(episode, map); + break; + + case AnimCondition_SecretVisited: + conditionsmet = wbs->didsecret; + break; + + case AnimCondition_Tally: + conditionsmet = !enteringcondition; + break; + + case AnimCondition_IsEntering: + conditionsmet = enteringcondition; + break; + + default: + break; + } + } + return conditionsmet; +} + +static void UpdateAnimationStates(wi_animationstate_t *states) +{ + wi_animationstate_t *state; + array_foreach(state, states) + { + interlevelframe_t *frame = &state->frames[state->frame_index]; + + int lumpnum = W_CheckNumForName(frame->image_lump); + if (lumpnum < 0) + { + lumpnum = (W_CheckNumForName)(frame->image_lump, ns_sprites); + } + frame->image_lumpnum = lumpnum; + + if (frame->type & Frame_Infinite) + { + continue; + } + + if (state->duration_left == 0) + { + int tics = 1; + switch (frame->type) + { + case Frame_RandomStart: + if (state->frame_start) + { + int maxtics = frame->duration * TICRATE; + tics = M_Random() % maxtics; + break; + } + // fall through + case Frame_FixedDuration: + tics = frame->duration * TICRATE; + break; + + case Frame_RandomDuration: + { + int maxtics = frame->maxduration * TICRATE; + int mintics = frame->duration * TICRATE; + tics = M_Random() % maxtics; + tics = BETWEEN(mintics, maxtics, tics); + } + break; + + default: + break; + } + + state->duration_left = MAX(tics, 1); + + if (!state->frame_start) + { + state->frame_index++; + if (state->frame_index == array_size(state->frames)) + { + state->frame_index = 0; + } + } + } + + state->duration_left--; + state->frame_start = false; + } +} + +static boolean UpdateAnimation(boolean enteringcondition) +{ + if (!animation) + { + return false; + } + + animation->states = NULL; + + if (!enteringcondition && animation->interlevel_exiting) + { + animation->states = animation->exiting_states; + animation->background_lumpnum = + W_CheckNumForName(animation->interlevel_exiting->background_lump); + } + else if (animation->interlevel_entering) + { + animation->states = animation->entering_states; + animation->background_lumpnum = + W_CheckNumForName(animation->interlevel_entering->background_lump); + } + + UpdateAnimationStates(animation->states); + + return true; +} + +static boolean DrawAnimation(void) +{ + if (!animation) + { + return false; + } + + V_DrawPatchFullScreen( + V_CachePatchNum(animation->background_lumpnum, PU_CACHE)); + + wi_animationstate_t *state; + array_foreach(state, animation->states) + { + interlevelframe_t *frame = &state->frames[state->frame_index]; + patch_t *patch = V_CachePatchNum(frame->image_lumpnum, PU_CACHE); + V_DrawPatch(state->x_pos, state->y_pos, patch); + } + + return true; +} + +static wi_animationstate_t *SetupAnimationStates(interlevellayer_t *layers, + boolean enteringcondition) +{ + wi_animationstate_t *states = NULL; + + interlevellayer_t *layer; + array_foreach(layer, layers) + { + if (!CheckConditions(layer->conditions, enteringcondition)) + { + continue; + } + + interlevelanim_t *anim; + array_foreach(anim, layer->anims) + { + if (!CheckConditions(anim->conditions, enteringcondition)) + { + continue; + } + + wi_animationstate_t state = {0}; + state.x_pos = anim->x_pos; + state.y_pos = anim->y_pos; + state.frames = anim->frames; + state.frame_start = true; + array_push(states, state); + } + } + + return states; +} + +static boolean SetupAnimation(void) +{ + if (!animation) + { + return false; + } + + if (animation->interlevel_exiting) + { + animation->exiting_states = + SetupAnimationStates(animation->interlevel_exiting->layers, false); + } + + if (animation->interlevel_entering) + { + animation->entering_states = + SetupAnimationStates(animation->interlevel_entering->layers, true); + } + + return true; +} + +static boolean NextLocAnimation(void) +{ + if (animation && animation->entering_states + && !(demorecording || demoplayback)) + { + return true; + } + + return false; +} // ==================================================================== // WI_slamBackground @@ -596,6 +883,11 @@ static void WI_initAnimatedBack(boolean firstcall) int i; anim_t* a; + if (SetupAnimation()) + { + return; + } + if (exitpic) return; if (enterpic && entering) @@ -616,7 +908,7 @@ static void WI_initAnimatedBack(boolean firstcall) // via WI_initShowNextLoc. Fixes notable blinking of Tower of Babel drawing // and the rest of animations from being restarted. if (firstcall) - a->ctr = -1; + a->ctr = -1; // specify the next time to draw it if (a->type == ANIM_ALWAYS) @@ -642,6 +934,11 @@ static void WI_updateAnimatedBack(void) int i; anim_t* a; + if (UpdateAnimation(state != StatCount)) + { + return; + } + if (exitpic) return; if (enterpic && state != StatCount) @@ -704,6 +1001,11 @@ static void WI_drawAnimatedBack(void) int i; anim_t* a; + if (DrawAnimation()) + { + return; + } + if (exitpic) return; if (enterpic && state != StatCount) @@ -1267,6 +1569,8 @@ static void WI_updateDeathmatchStats(void) { S_StartSound(0, sfx_slop); + if (NextLocAnimation()) + WI_initShowNextLoc(); if ( gamemode == commercial) WI_initNoState(); else @@ -1570,6 +1874,9 @@ static void WI_updateNetgameStats(void) if (acceleratestage) { S_StartSound(0, sfx_sgcock); + + if (NextLocAnimation()) + WI_initShowNextLoc(); if ( gamemode == commercial ) WI_initNoState(); else @@ -1788,7 +2095,9 @@ static void WI_updateStats(void) { S_StartSound(0, sfx_sgcock); - if (gamemode == commercial) + if (NextLocAnimation()) + WI_initShowNextLoc(); + else if (gamemode == commercial) WI_initNoState(); else WI_initShowNextLoc(); @@ -2218,8 +2527,43 @@ void WI_Start(wbstartstruct_t* wbstartstruct) { WI_initVariables(wbstartstruct); - exitpic = (wbs->lastmapinfo && wbs->lastmapinfo->exitpic[0]) ? wbs->lastmapinfo->exitpic : NULL; - enterpic = (wbs->nextmapinfo && wbs->nextmapinfo->enterpic[0]) ? wbs->nextmapinfo->enterpic : NULL; + exitpic = NULL; + enterpic = NULL; + animation = NULL; + + if (wbs->lastmapinfo) + { + if (wbs->lastmapinfo->exitpic[0]) + { + exitpic = wbs->lastmapinfo->exitpic; + } + if (wbs->lastmapinfo->exitanim[0]) + { + if (!animation) + { + animation = Z_Calloc(1, sizeof(*animation), PU_LEVEL, NULL); + } + animation->interlevel_exiting = + WI_ParseInterlevel(wbs->lastmapinfo->exitanim); + } + } + + if (wbs->nextmapinfo) + { + if (wbs->nextmapinfo->enterpic[0]) + { + enterpic = wbs->nextmapinfo->enterpic; + } + if (wbs->nextmapinfo->enteranim[0]) + { + if (!animation) + { + animation = Z_Calloc(1, sizeof(*animation), PU_LEVEL, NULL); + } + animation->interlevel_entering = + WI_ParseInterlevel(wbs->nextmapinfo->enteranim); + } + } WI_loadData(); diff --git a/vcpkg.json b/vcpkg.json index 13ef1397..65d23200 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -41,6 +41,7 @@ { "name": "libxmp", "default-features": false - } + }, + "cjson" ] }