diff --git a/doc/specs/udmf_srb2.txt b/doc/specs/udmf_srb2.txt index eaa3a8a97..76875a5c3 100644 --- a/doc/specs/udmf_srb2.txt +++ b/doc/specs/udmf_srb2.txt @@ -1,7 +1,7 @@ =============================================================================== -Universal Doom Map Format Sonic Robo Blast 2 extensions v1.0 19.06.2024 +Universal Doom Map Format Sonic Robo Blast 2 extensions v1.1 27.01.2025 - Copyright (c) 2024 Sonic Team Junior + Copyright (c) 2025 Sonic Team Junior uses Universal Doom Map Format Specification v1.1 as a template, original document Copyright (c) 2009 James Haley. Permission is granted to copy, distribute and/or modify this document @@ -80,6 +80,7 @@ Sonic Robo Blast 2 defines the following standardized fields: noskew = ; // Middle texture is not skewed. midpeg = ; // Middle texture is pegged. midsolid = ; // Middle texture is solid. + clipmidtex = ; // Line's mid textures are clipped to floor and ceiling. wrapmidtex = ; // Line's mid textures are wrapped. nonet = ; // Special only takes effect in singleplayer games. netonly = ; // Special only takes effect in multiplayer games. @@ -146,6 +147,8 @@ Sonic Robo Blast 2 defines the following standardized fields: light = ; // Light level, relative to 'sector' light level. Default = 0. lightabsolute = ; // true = 'light' is an absolute value, ignoring 'sector' light level. + clipmidtex = ; // Side's mid textures are clipped to floor and ceiling. + comment = ; // A comment. Implementors should attach no special // semantic meaning to this field. } @@ -308,6 +311,9 @@ Sonic Robo Blast 2 defines the following standardized fields: Changelog ======================================= +1.1: 27.01.2025 +Added clipmidtex property to lines and sides. + 1.0: 19.02.2024 Initial version. diff --git a/src/deh_tables.c b/src/deh_tables.c index a96adf8ae..d211e0825 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -4564,6 +4564,7 @@ const char *const ML_LIST[] = { "EFFECT6", "BOUNCY", "TFERLINE", + "CLIPMIDTEX", NULL }; diff --git a/src/doomdata.h b/src/doomdata.h index 276e03297..682d41a3d 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -104,13 +104,13 @@ typedef struct // // Solid, is an obstacle. -#define ML_IMPASSIBLE 1 +#define ML_IMPASSIBLE 1<<0 // Blocks monsters only. -#define ML_BLOCKMONSTERS 2 +#define ML_BLOCKMONSTERS 1<<1 // Backside will not be present at all if not two sided. -#define ML_TWOSIDED 4 +#define ML_TWOSIDED 1<<2 // If a texture is pegged, the texture will have // the end exposed to air held constant at the @@ -122,29 +122,31 @@ typedef struct // top and bottom textures (use next to windows). // upper texture unpegged -#define ML_DONTPEGTOP 8 +#define ML_DONTPEGTOP 1<<3 // lower texture unpegged -#define ML_DONTPEGBOTTOM 16 +#define ML_DONTPEGBOTTOM 1<<4 -#define ML_SKEWTD 32 +#define ML_SKEWTD 1<<5 // Don't let Knuckles climb on this line -#define ML_NOCLIMB 64 +#define ML_NOCLIMB 1<<6 -#define ML_NOSKEW 128 -#define ML_MIDPEG 256 -#define ML_MIDSOLID 512 -#define ML_WRAPMIDTEX 1024 +#define ML_NOSKEW 1<<7 +#define ML_MIDPEG 1<<8 +#define ML_MIDSOLID 1<<9 +#define ML_WRAPMIDTEX 1<<10 -#define ML_NETONLY 2048 // Apply effect only in netgames -#define ML_NONET 4096 // Apply effect only in single player games -#define ML_EFFECT6 8192 +#define ML_NETONLY 1<<11 // Apply effect only in netgames +#define ML_NONET 1<<12 // Apply effect only in single player games +#define ML_EFFECT6 1<<13 // Bounce off walls! -#define ML_BOUNCY 16384 +#define ML_BOUNCY 1<<14 -#define ML_TFERLINE 32768 +#define ML_TFERLINE 1<<15 + +#define ML_CLIPMIDTEX 1<<16 // Sector definition, from editing. typedef struct diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index d84322dfd..f1ec7345a 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1072,7 +1072,7 @@ static void HWR_RenderMidtexture(INT32 gl_midtexture, float cliplow, float cliph } // The cut-off values of a linedef can always be constant, since every line has an absoulute front and or back sector - if (gl_curline->polyseg) + if (gl_curline->polyseg && ((gl_linedef->flags & ML_CLIPMIDTEX) || (gl_sidedef->flags & SIDEFLAG_CLIP_MIDTEX)) == 0) { lowcut = polybottom; highcut = polytop; diff --git a/src/lua_maplib.c b/src/lua_maplib.c index c946b10ce..e3f8d65d9 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -213,6 +213,7 @@ enum side_e { side_sector, side_special, side_repeatcnt, + side_clipmidtex, side_light, side_light_top, side_light_mid, @@ -249,6 +250,7 @@ static const char *const side_opt[] = { "sector", "special", "repeatcnt", + "clipmidtex", "light", "light_top", "light_mid", @@ -1327,6 +1329,9 @@ static int side_get(lua_State *L) case side_repeatcnt: lua_pushinteger(L, side->repeatcnt); return 1; + case side_clipmidtex: + lua_pushinteger(L, side->flags & SIDEFLAG_CLIP_MIDTEX); + return 1; case side_light: lua_pushinteger(L, side->light); return 1; @@ -1453,6 +1458,12 @@ static int side_set(lua_State *L) case side_repeatcnt: side->repeatcnt = luaL_checkinteger(L, 3); break; + case side_clipmidtex: + if (luaL_checkboolean(L, 3)) + side->flags |= SIDEFLAG_CLIP_MIDTEX; + else + side->flags &= ~SIDEFLAG_CLIP_MIDTEX; + break; case side_light: side->light = luaL_checkinteger(L, 3); break; diff --git a/src/p_saveg.c b/src/p_saveg.c index 650622f59..a8553d086 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -1637,6 +1637,8 @@ static UINT32 GetSideDiff(const side_t *si, const side_t *spawnsi) diff |= LD_SDBOTSCALEY; if (si->repeatcnt != spawnsi->repeatcnt) diff |= LD_SDREPEATCNT; + if (si->flags != spawnsi->flags) + diff |= LD_SDFLAGS; if (si->light != spawnsi->light) diff |= LD_SDLIGHT; if (si->light_top != spawnsi->light_top) @@ -1696,6 +1698,8 @@ static void ArchiveSide(save_t *save_p, const side_t *si, UINT32 diff) P_WriteFixed(save_p, si->scaley_bottom); if (diff & LD_SDREPEATCNT) P_WriteINT16(save_p, si->repeatcnt); + if (diff & LD_SDFLAGS) + P_WriteUINT16(save_p, si->flags); if (diff & LD_SDLIGHT) P_WriteINT16(save_p, si->light); if (diff & LD_SDTOPLIGHT) @@ -1769,7 +1773,7 @@ static void ArchiveLines(save_t *save_p) if (diff & LD_DIFF2) P_WriteUINT8(save_p, diff2); if (diff & LD_FLAG) - P_WriteINT16(save_p, li->flags); + P_WriteUINT32(save_p, li->flags); if (diff & LD_SPECIAL) P_WriteINT16(save_p, li->special); if (diff & LD_CLLCOUNT) @@ -1852,6 +1856,8 @@ static void UnArchiveSide(save_t *save_p, side_t *si) si->scaley_bottom = P_ReadFixed(save_p); if (diff & LD_SDREPEATCNT) si->repeatcnt = P_ReadINT16(save_p); + if (diff & LD_SDFLAGS) + si->flags = P_ReadUINT16(save_p); if (diff & LD_SDLIGHT) si->light = P_ReadINT16(save_p); if (diff & LD_SDTOPLIGHT) @@ -1893,7 +1899,7 @@ static void UnArchiveLines(save_t *save_p) li = &lines[i]; if (diff & LD_FLAG) - li->flags = P_ReadINT16(save_p); + li->flags = P_ReadUINT32(save_p); if (diff & LD_SPECIAL) li->special = P_ReadINT16(save_p); if (diff & LD_CLLCOUNT) diff --git a/src/p_setup.c b/src/p_setup.c index 247587e11..802f34788 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -1367,6 +1367,7 @@ static void P_LoadSidedefs(UINT8 *data) sd->scalex_top = sd->scalex_mid = sd->scalex_bottom = FRACUNIT; sd->scaley_top = sd->scaley_mid = sd->scaley_bottom = FRACUNIT; + sd->flags = 0; sd->light = sd->light_top = sd->light_mid = sd->light_bottom = 0; sd->lightabsolute = sd->lightabsolute_top = sd->lightabsolute_mid = sd->lightabsolute_bottom = false; @@ -1980,6 +1981,8 @@ static void ParseTextmapSidedefParameter(UINT32 i, const char *param, const char P_SetSidedefSector(i, atol(val)); else if (fastcmp(param, "repeatcnt")) sides[i].repeatcnt = atol(val); + else if (fastcmp(param, "clipmidtex") && fastcmp("true", val)) + sides[i].flags |= SIDEFLAG_CLIP_MIDTEX; else if (fastcmp(param, "light")) sides[i].light = atol(val); else if (fastcmp(param, "light_top")) @@ -2078,10 +2081,10 @@ static void ParseTextmapLinedefParameter(UINT32 i, const char *param, const char lines[i].flags |= ML_MIDPEG; else if (fastcmp(param, "midsolid") && fastcmp("true", val)) lines[i].flags |= ML_MIDSOLID; + else if (fastcmp(param, "clipmidtex") && fastcmp("true", val)) + lines[i].flags |= ML_CLIPMIDTEX; else if (fastcmp(param, "wrapmidtex") && fastcmp("true", val)) lines[i].flags |= ML_WRAPMIDTEX; - /*else if (fastcmp(param, "effect6") && fastcmp("true", val)) - lines[i].flags |= ML_EFFECT6;*/ else if (fastcmp(param, "nonet") && fastcmp("true", val)) lines[i].flags |= ML_NONET; else if (fastcmp(param, "netonly") && fastcmp("true", val)) @@ -2668,6 +2671,8 @@ static void P_WriteTextmap(void) fprintf(f, "midpeg = true;\n"); if (wlines[i].flags & ML_MIDSOLID) fprintf(f, "midsolid = true;\n"); + if (wlines[i].flags & ML_CLIPMIDTEX) + fprintf(f, "clipmidtex = true;\n"); if (wlines[i].flags & ML_WRAPMIDTEX) fprintf(f, "wrapmidtex = true;\n"); if (wlines[i].flags & ML_NONET) @@ -2723,6 +2728,8 @@ static void P_WriteTextmap(void) fprintf(f, "texturemiddle = \"%.*s\";\n", 8, textures[wsides[i].midtexture]->name); if (wsides[i].repeatcnt != 0) fprintf(f, "repeatcnt = %d;\n", wsides[i].repeatcnt); + if (wsides[i].flags & SIDEFLAG_CLIP_MIDTEX) + fprintf(f, "clipmidtex = true;\n"); if (wsides[i].light != 0) fprintf(f, "light = %d;\n", wsides[i].light); if (wsides[i].light_top != 0) @@ -3141,6 +3148,7 @@ static void P_LoadTextmap(void) sd->bottomtexture = R_TextureNumForName("-"); sd->sector = NULL; sd->repeatcnt = 0; + sd->flags = 0; sd->light = sd->light_top = sd->light_mid = sd->light_bottom = 0; sd->lightabsolute = sd->lightabsolute_top = sd->lightabsolute_mid = sd->lightabsolute_bottom = false; diff --git a/src/r_defs.h b/src/r_defs.h index eac3e2d1f..a78072a24 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -588,7 +588,7 @@ typedef struct line_s angle_t angle; // Precalculated angle between dx and dy // Animation related. - INT16 flags; + UINT32 flags; INT16 special; taglist_t tags; INT32 args[NUMLINEARGS]; @@ -620,6 +620,12 @@ typedef struct line_s struct pslope_s *midtexslope; } line_t; +// Don't make available to Lua or I will find where you live +typedef enum +{ + SIDEFLAG_CLIP_MIDTEX = 1 << 0, // Like the line counterpart, but only for this side. +} sideflags_t; + typedef struct { // add this to the calculated texture column @@ -628,13 +634,16 @@ typedef struct // add this to the calculated texture top fixed_t rowoffset; - // per-texture offsets for UDMF + // per-texture offsets fixed_t offsetx_top, offsetx_mid, offsetx_bottom; fixed_t offsety_top, offsety_mid, offsety_bottom; fixed_t scalex_top, scalex_mid, scalex_bottom; fixed_t scaley_top, scaley_mid, scaley_bottom; + // Rendering-related flags + UINT16 flags; + // per-wall lighting for UDMF // TODO: implement per-texture lighting INT16 light, light_top, light_mid, light_bottom; diff --git a/src/r_segs.c b/src/r_segs.c index 4939d1fc8..3c896eba3 100644 --- a/src/r_segs.c +++ b/src/r_segs.c @@ -232,6 +232,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) fixed_t wall_scaley; fixed_t scalestep; fixed_t scale1; + fixed_t texture_top, texture_bottom, texture_height; + boolean clipmidtex; // Calculate light table. // Use different light tables @@ -403,12 +405,17 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) else back = backsector; + clipmidtex = (ldef->flags & ML_CLIPMIDTEX) || (sidedef->flags & SIDEFLAG_CLIP_MIDTEX); + texture_height = textureheight[texnum]; + if (sidedef->repeatcnt) repeats = 1 + sidedef->repeatcnt; else if (ldef->flags & ML_WRAPMIDTEX) { fixed_t high, low; + height = FixedDiv(texture_height, wall_scaley); + if (front->ceilingheight > back->ceilingheight) high = back->ceilingheight; else @@ -419,8 +426,8 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) else low = back->floorheight; - repeats = (high - low)/textureheight[texnum]; - if ((high-low)%textureheight[texnum]) + repeats = (high - low)/height; + if ((high-low)%height) repeats++; // tile an extra time to fill the gap -- Monster Iestyn } else @@ -428,6 +435,33 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) for (times = 0; times < repeats; times++) { + fixed_t left_top = 0, left_bottom = 0; + fixed_t right_top = 0, right_bottom = 0; + fixed_t top_step = 0, bottom_step = 0; + + // Get left and right ends of wall for clipping it + if (clipmidtex) + { + // For this to work correctly with polyobjects, it needs to use its own back sector, rather than the seg's front sector + sector_t *sec_front = curline->polyseg ? + curline->polyseg->lines[0]->backsector : + frontsector; + + // calculate both left ends + left_top = P_GetSectorCeilingZAt(sec_front, ds->leftpos.x, ds->leftpos.y) - viewz; + left_bottom = P_GetSectorFloorZAt(sec_front, ds->leftpos.x, ds->leftpos.y) - viewz; + + // calculate right ends now + right_top = P_GetSectorCeilingZAt(sec_front, ds->rightpos.x, ds->rightpos.y) - viewz; + right_bottom = P_GetSectorFloorZAt(sec_front, ds->rightpos.x, ds->rightpos.y) - viewz; + + top_step = (right_top - left_top) / range; + bottom_step = (right_bottom - left_bottom) / range; + + left_top += top_step * (x1 - ds->x1); + left_bottom += bottom_step * (x1 - ds->x1); + } + if (times > 0) { rw_scalestep = scalestep; @@ -441,17 +475,17 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) } } - dc_texheight = textureheight[texnum]>>FRACBITS; + dc_texheight = texture_height>>FRACBITS; // draw the columns for (dc_x = x1; dc_x <= x2; dc_x++) { dc_texturemid = ds->maskedtextureheight[dc_x]; - if (curline->linedef->flags & ML_MIDPEG) - dc_texturemid += (textureheight[texnum])*times + textureheight[texnum]; + if (ldef->flags & ML_MIDPEG) + dc_texturemid += texture_height*times + texture_height; else - dc_texturemid -= (textureheight[texnum])*times; + dc_texturemid -= texture_height*times; // Check for overflows first overflow_test = (INT64)centeryfrac - (((INT64)dc_texturemid*spryscale)>>FRACBITS); @@ -468,29 +502,53 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) } } spryscale += rw_scalestep; + if (clipmidtex) + { + left_top += top_step; + left_bottom += bottom_step; + } continue; } - // calculate lighting + texture_top = dc_texturemid; + texture_bottom = texture_top - texture_height; + + // If the texture is meant to be clipped + if (clipmidtex) + { + texture_top = min(FixedMul(left_top, wall_scaley), texture_top); + texture_bottom = max(FixedMul(left_bottom, wall_scaley), texture_bottom); + + left_top += top_step; + left_bottom += bottom_step; + } + + // NB: sprtopscreen needs to start where dc_texturemid does, so that R_DrawMaskedColumn works correctly. + // windowtop however is set so that the column gets clipped properly. + sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); + realbot = centeryfrac - FixedMul(texture_bottom, spryscale); + + // set wall bounds if necessary + if (dc_numlights || clipmidtex) + { + windowtop = centeryfrac - FixedMul(texture_top, spryscale); + windowbottom = realbot; + } + + dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley); + + col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS); + + // draw light list if there is one if (dc_numlights) { - lighttable_t **xwalllights; - - sprtopscreen = windowtop = (centeryfrac - FixedMul(dc_texturemid, spryscale)); - - realbot = FixedMul(textureheight[texnum], spryscale) + sprtopscreen; - dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley); - - windowbottom = realbot; - - // draw the texture - col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS); - for (i = 0; i < dc_numlights; i++) { + lighttable_t **xwalllights; + rlight = &dc_lightlist[i]; - if ((rlight->flags & FOF_NOSHADE)) + if (rlight->flags & FOF_NOSHADE) continue; if (rlight->lightnum < 0) @@ -500,7 +558,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) else xwalllights = scalelight[rlight->lightnum]; - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + pindex = FixedMul(FixedMul(spryscale, wall_scaley), LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; if (pindex >= MAXLIGHTSCALE) pindex = MAXLIGHTSCALE - 1; @@ -545,7 +603,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) } // calculate lighting - pindex = FixedMul(spryscale, LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; + pindex = FixedMul(FixedMul(spryscale, wall_scaley), LIGHTRESOLUTIONFIX)>>LIGHTSCALESHIFT; if (pindex >= MAXLIGHTSCALE) pindex = MAXLIGHTSCALE - 1; @@ -555,11 +613,7 @@ void R_RenderMaskedSegRange(drawseg_t *ds, INT32 x1, INT32 x2) if (frontsector->extra_colormap) dc_colormap = frontsector->extra_colormap->colormap + (dc_colormap - colormaps); - sprtopscreen = centeryfrac - FixedMul(dc_texturemid, spryscale); - dc_iscale = FixedMul(ds->invscale[dc_x], wall_scaley); - // draw the texture - col = R_GetColumn(texnum, maskedtexturecol[dc_x] >> FRACBITS); colfunc_2s(col, lengthcol); spryscale += rw_scalestep;