mirror of
https://github.com/fabiangreffrath/woof.git
synced 2025-09-24 04:29:34 -04:00
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
This commit is contained in:
parent
dfc450d844
commit
6663fba661
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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"))
|
||||
|
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
|
11
src/g_game.c
11
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
|
||||
|
@ -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 <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
217
src/wi_interlvl.c
Normal file
217
src/wi_interlvl.c
Normal file
@ -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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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;
|
||||
}
|
90
src/wi_interlvl.h
Normal file
90
src/wi_interlvl.h
Normal file
@ -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
|
352
src/wi_stuff.c
352
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();
|
||||
|
||||
|
@ -41,6 +41,7 @@
|
||||
{
|
||||
"name": "libxmp",
|
||||
"default-features": false
|
||||
}
|
||||
},
|
||||
"cjson"
|
||||
]
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user