diff --git a/common/bspfile.cc b/common/bspfile.cc index ca16aa90..cdf7f133 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -40,6 +40,38 @@ struct gamedef_generic_t : public gamedef_t { contentflags_t cluster_contents(const contentflags_t &, const contentflags_t &) const { throw std::bad_cast(); } + + int32_t get_content_type(const contentflags_t &) const { + throw std::bad_cast(); + } + + int32_t contents_priority(const contentflags_t &) const { + throw std::bad_cast(); + } + + contentflags_t create_empty_contents(const int32_t &) const { + throw std::bad_cast(); + } + + contentflags_t create_solid_contents(const int32_t &) const { + throw std::bad_cast(); + } + + contentflags_t create_sky_contents(const int32_t &) const { + throw std::bad_cast(); + } + + contentflags_t create_liquid_contents(const int32_t &, const int32_t &) const { + throw std::bad_cast(); + } + + bool contents_are_liquid(const contentflags_t &) const { + throw std::bad_cast(); + } + + bool contents_are_valid(const contentflags_t &, bool) const { + throw std::bad_cast(); + } }; template @@ -63,21 +95,78 @@ struct gamedef_q1_like_t : public gamedef_t { if (contents0 == contents1) return contents0; + const int32_t merged_flags = contents0.extended | contents1.extended; + /* * Clusters may be partially solid but still be seen into * ?? - Should we do something more explicit with mixed liquid contents? */ if (contents0.native == CONTENTS_EMPTY || contents1.native == CONTENTS_EMPTY) - return { CONTENTS_EMPTY, (uint16_t) (contents0.extended | contents1.extended) }; + return create_empty_contents(merged_flags); if (contents0.native >= CONTENTS_LAVA && contents0.native <= CONTENTS_WATER) - return { contents0.native, (uint16_t) (contents0.extended | contents1.extended) }; + return create_liquid_contents(contents0.native, merged_flags); if (contents1.native >= CONTENTS_LAVA && contents1.native <= CONTENTS_WATER) - return { contents1.native, (uint16_t) (contents0.extended | contents1.extended) }; + return create_liquid_contents(contents1.native, merged_flags); if (contents0.native == CONTENTS_SKY || contents1.native == CONTENTS_SKY) - return { CONTENTS_SKY, (uint16_t) (contents0.extended | contents1.extended) }; + return create_sky_contents(merged_flags); - return { CONTENTS_SOLID, (uint16_t) (contents0.extended | contents1.extended) }; + return create_solid_contents(merged_flags); + } + + int32_t get_content_type(const contentflags_t &contents) const { + return contents.native; + } + + int32_t contents_priority(const contentflags_t &contents) const { + if (contents.extended & CFLAGS_DETAIL) { + return 5; + } else if (contents.extended & CFLAGS_DETAIL_ILLUSIONARY) { + return 3; + } else if (contents.extended & CFLAGS_DETAIL_FENCE) { + return 4; + } else if (contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) { + return 2; + } else switch( contents.native ) { + case CONTENTS_SOLID: return 7; + + case CONTENTS_SKY: return 6; + + case CONTENTS_WATER: return 2; + case CONTENTS_SLIME: return 2; + case CONTENTS_LAVA: return 2; + + case CONTENTS_EMPTY: return 1; + case 0: return 0; + + default: + Error("Bad contents in face (%s)", __func__); + return 0; + } + } + + contentflags_t create_empty_contents(const int32_t &cflags) const { + return { CONTENTS_EMPTY, cflags }; + } + + contentflags_t create_solid_contents(const int32_t &cflags) const { + return { CONTENTS_SOLID, cflags }; + } + + contentflags_t create_sky_contents(const int32_t &cflags) const { + return { CONTENTS_SKY, cflags }; + } + + contentflags_t create_liquid_contents(const int32_t &liquid_type, const int32_t &cflags) const { + return { liquid_type, cflags }; + } + + bool contents_are_liquid(const contentflags_t &contents) const { + return contents.native <= CONTENTS_WATER && contents.native >= CONTENTS_LAVA; + } + + bool contents_are_valid(const contentflags_t &contents, bool strict) const { + return contents.native <= 0; } }; @@ -113,7 +202,7 @@ struct gamedef_q2_t : public gamedef_t { } contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const { - contentflags_t c = { contents0.native | contents1.native, (uint16_t) (contents0.extended | contents1.extended) }; + contentflags_t c = { contents0.native | contents1.native, contents0.extended | contents1.extended }; // a cluster may include some solid detail areas, but // still be seen into @@ -122,6 +211,71 @@ struct gamedef_q2_t : public gamedef_t { return c; } + + int32_t get_content_type(const contentflags_t &contents) const { + return (contents.native & ((Q2_LAST_VISIBLE_CONTENTS << 1) - 1)) | (Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP); + } + + int32_t contents_priority(const contentflags_t &contents) const { + if (contents.extended & CFLAGS_DETAIL) { + return 8; + } else if (contents.extended & CFLAGS_DETAIL_ILLUSIONARY) { + return 6; + } else if (contents.extended & CFLAGS_DETAIL_FENCE) { + return 7; + } else if (contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) { + return 2; + } else switch( contents.native & (Q2_LAST_VISIBLE_CONTENTS - 1) ) { + case Q2_CONTENTS_SOLID: return 10; + case Q2_CONTENTS_WINDOW: return 9; + case Q2_CONTENTS_AUX: return 5; + case Q2_CONTENTS_LAVA: return 4; + case Q2_CONTENTS_SLIME: return 3; + case Q2_CONTENTS_WATER: return 2; + case Q2_CONTENTS_MIST: return 1; + default: return 0; + } + } + + contentflags_t create_empty_contents(const int32_t &cflags) const { + return { 0, cflags }; + } + + contentflags_t create_solid_contents(const int32_t &cflags) const { + return { Q2_CONTENTS_SOLID, cflags }; + } + + contentflags_t create_sky_contents(const int32_t &cflags) const { + return create_solid_contents(cflags); + } + + contentflags_t create_liquid_contents(const int32_t &liquid_type, const int32_t &cflags) const { + switch (liquid_type) { + case CONTENTS_WATER: + return { Q2_CONTENTS_WATER, cflags }; + case CONTENTS_SLIME: + return { Q2_CONTENTS_SLIME, cflags }; + case CONTENTS_LAVA: + return { Q2_CONTENTS_LAVA, cflags }; + default: + Error("bad contents"); + } + } + + bool contents_are_sky(const contentflags_t &contents) const { + return false; + } + + bool contents_are_liquid(const contentflags_t &contents) const { + return contents.native & Q2_CONTENTS_LIQUID; + } + + bool contents_are_valid(const contentflags_t &contents, bool strict) const { + if (!strict) { + return true; + } + return contents.native & (Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW | Q2_CONTENTS_LIQUID | Q2_CONTENTS_MIST | Q2_CONTENTS_AUX); + } }; static const gamedef_generic_t gamedef_generic; @@ -141,6 +295,35 @@ static const gamedef_q2_t gamedef_q2; const bspversion_t bspver_q2 { Q2_BSPIDENT, Q2_BSPVERSION, "q2bsp", "Quake II BSP", &gamedef_q2 }; const bspversion_t bspver_qbism { Q2_QBISMIDENT, Q2_BSPVERSION, "qbism", "Quake II Qbism BSP", &gamedef_q2 }; +bool contentflags_t::types_equal(const contentflags_t &other, const gamedef_t *game) const { + return (extended & CFLAGS_DETAIL_MASK) == (other.extended & CFLAGS_DETAIL_MASK) && + game->get_content_type(*this) == game->get_content_type(other); +} + +int32_t contentflags_t::priority(const gamedef_t *game) const { + return game->contents_priority(*this); +} + +bool contentflags_t::is_empty(const gamedef_t *game) const { + return game->contents_are_empty(*this); +} + +bool contentflags_t::is_solid(const gamedef_t *game) const { + return game->contents_are_solid(*this); +} + +bool contentflags_t::is_sky(const gamedef_t *game) const { + return game->contents_are_sky(*this); +} + +bool contentflags_t::is_liquid(const gamedef_t *game) const { + return game->contents_are_liquid(*this); +} + +bool contentflags_t::is_valid(const gamedef_t *game, bool strict) const { + return game->contents_are_valid(*this, strict); +} + static const char * BSPVersionString(const bspversion_t *version) { diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 30967ba2..36d58f10 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -263,40 +263,87 @@ typedef struct { #define Q2_CONTENTS_LADDER 0x20000000 // Special contents flags for the compiler only -#define CFLAGS_STRUCTURAL_COVERED_BY_DETAIL (1U << 0) -#define CFLAGS_WAS_ILLUSIONARY (1U << 1) /* was illusionary, got changed to something else */ -#define CFLAGS_DETAIL_WALL (1U << 2) /* don't clip world for func_detail_wall entities */ -#define CFLAGS_BMODEL_MIRROR_INSIDE (1U << 3) /* set "_mirrorinside" "1" on a bmodel to mirror faces for when the player is inside. */ -#define CFLAGS_NO_CLIPPING_SAME_TYPE (1U << 4) /* Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY */ +#define CFLAGS_STRUCTURAL_COVERED_BY_DETAIL (1 << 0) +#define CFLAGS_WAS_ILLUSIONARY (1 << 1) /* was illusionary, got changed to something else */ +#define CFLAGS_DETAIL_WALL (1 << 2) /* don't clip world for func_detail_wall entities */ +#define CFLAGS_BMODEL_MIRROR_INSIDE (1 << 3) /* set "_mirrorinside" "1" on a bmodel to mirror faces for when the player is inside. */ +#define CFLAGS_NO_CLIPPING_SAME_TYPE (1 << 4) /* Don't clip the same content type. mostly intended for CONTENTS_DETAIL_ILLUSIONARY */ // only one of these flags below should ever be set. -#define CFLAGS_HINT (1U << 5) -#define CFLAGS_CLIP (1U << 6) -#define CFLAGS_ORIGIN (1U << 7) -#define CFLAGS_DETAIL (1U << 8) -#define CFLAGS_DETAIL_ILLUSIONARY (1U << 9) -#define CFLAGS_DETAIL_FENCE (1U << 10) -#define CFLAGS_ILLUSIONARY_VISBLOCKER (1U << 11) +#define CFLAGS_HINT (1 << 5) +#define CFLAGS_CLIP (1 << 6) +#define CFLAGS_ORIGIN (1 << 7) +#define CFLAGS_DETAIL (1 << 8) +#define CFLAGS_DETAIL_ILLUSIONARY (1 << 9) +#define CFLAGS_DETAIL_FENCE (1 << 10) +#define CFLAGS_ILLUSIONARY_VISBLOCKER (1 << 11) +// all of the detail values +#define CFLAGS_DETAIL_MASK (CFLAGS_DETAIL | CFLAGS_DETAIL_ILLUSIONARY | CFLAGS_DETAIL_FENCE) + +struct gamedef_t; struct contentflags_t { // native flags value; what's written to the BSP basically int32_t native; // extra flags, specific to BSP only - uint16_t extended; + int32_t extended; // merge these content flags with other, and use // their native contents. - contentflags_t merge(const contentflags_t &other) const { - return { other.native, (uint16_t) (extended | other.extended) }; + constexpr contentflags_t merge(const contentflags_t &other) const { + return { other.native, extended | other.extended }; } - bool operator==(const contentflags_t &other) const { + constexpr bool operator==(const contentflags_t &other) const { return native == other.native && extended == other.extended; } - bool operator!=(const contentflags_t &other) const { + constexpr bool operator!=(const contentflags_t &other) const { return !(*this == other); } + + // check if these contents are marked as any (or a specific kind of) detail brush. + constexpr bool is_detail(int32_t types = CFLAGS_DETAIL_MASK) const { + return (extended & CFLAGS_DETAIL_MASK) & types; + } + + bool is_empty(const gamedef_t *game) const; + bool is_solid(const gamedef_t *game) const; + bool is_sky(const gamedef_t *game) const; + bool is_liquid(const gamedef_t *game) const; + bool is_valid(const gamedef_t *game, bool strict = true) const; + + bool is_structural_solid(const gamedef_t *game) const { + return is_solid(game) && !is_detail(); + } + + bool is_structural_sky(const gamedef_t *game) const { + return is_sky(game) && !is_detail(); + } + + bool is_structural_sky_or_solid(const gamedef_t *game) const { + return (is_sky(game) || is_solid(game)) && !is_detail(); + } + + constexpr bool is_hint() const { + return extended & CFLAGS_HINT; + } + + constexpr bool clips_same_type() const { + return !(extended & CFLAGS_NO_CLIPPING_SAME_TYPE); + } + + constexpr bool is_fence() const { + return is_detail(CFLAGS_DETAIL_FENCE | CFLAGS_DETAIL_ILLUSIONARY); + } + + // check if this content's `type` - which is distinct from various + // flags that turn things on/off - match. Exactly what the native + // "type" is depends on the game, but any of the detail flags must + // also match. + bool types_equal(const contentflags_t &other, const gamedef_t *game) const; + + int32_t priority(const gamedef_t *game) const; }; struct bsp29_dnode_t { @@ -1042,6 +1089,23 @@ struct gamedef_t virtual bool surf_is_lightmapped(const surfflags_t &flags) const = 0; virtual bool surf_is_subdivided(const surfflags_t &flags) const = 0; virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0; + virtual int32_t get_content_type(const contentflags_t &contents) const = 0; + virtual int32_t contents_priority(const contentflags_t &contents) const = 0; + virtual contentflags_t create_empty_contents(const int32_t &cflags = 0) const = 0; + virtual contentflags_t create_solid_contents(const int32_t &cflags = 0) const = 0; + virtual contentflags_t create_sky_contents(const int32_t &cflags = 0) const = 0; + virtual contentflags_t create_liquid_contents(const int32_t &liquid_type, const int32_t &cflags = 0) const = 0; + virtual bool contents_are_empty(const contentflags_t &contents) const { + return contents.native == create_empty_contents().native; + } + virtual bool contents_are_solid(const contentflags_t &contents) const { + return contents.native == create_solid_contents().native; + } + virtual bool contents_are_sky(const contentflags_t &contents) const { + return contents.native == create_sky_contents().native; + } + virtual bool contents_are_liquid(const contentflags_t &contents) const = 0; + virtual bool contents_are_valid(const contentflags_t &contents, bool strict = true) const = 0; }; // BSP version struct & instances diff --git a/include/qbsp/qbsp.hh b/include/qbsp/qbsp.hh index dbfd200f..50df411b 100644 --- a/include/qbsp/qbsp.hh +++ b/include/qbsp/qbsp.hh @@ -220,10 +220,7 @@ typedef struct node_s { mapentity_t *occupant; // example occupant, for leak hunting bool detail_separator; // for vis portal generation. true if ALL faces on node, and on all descendant nodes/leafs, are detail. - bool opaque() const { - return contents.native == CONTENTS_SOLID - || contents.native == CONTENTS_SKY; - } + bool opaque() const; } node_t; #include diff --git a/include/qbsp/solidbsp.hh b/include/qbsp/solidbsp.hh index 37e1626d..c5d273e8 100644 --- a/include/qbsp/solidbsp.hh +++ b/include/qbsp/solidbsp.hh @@ -27,7 +27,6 @@ extern std::atomic splitnodes; void DetailToSolid(node_t *node); -int Contents_Priority(const contentflags_t &contents); const char *GetContentsName( const contentflags_t &Contents ); void DivideFacet(face_t *in, qbsp_plane_t *split, face_t **front, face_t **back); void CalcSurfaceInfo(surface_t *surf); diff --git a/qbsp/brush.cc b/qbsp/brush.cc index 9f473633..9564e796 100644 --- a/qbsp/brush.cc +++ b/qbsp/brush.cc @@ -356,6 +356,17 @@ FixRotateOrigin(mapentity_t *entity) SetKeyValue(entity, "origin", value); } +static bool +DiscardHintSkipFace_Q1(const int hullnum, const hullbrush_t *hullbrush, const mtexinfo_t &texinfo) { + const char *texname = map.miptexTextureName(texinfo.miptex).c_str(); + + return Q_strcasecmp(texname, "hint"); // anything texname other than "hint" in a hint brush is treated as "hintskip", and discarded +} + +static bool +DiscardHintSkipFace_Q2(const int hullnum, const hullbrush_t *hullbrush, const mtexinfo_t &texinfo) { + return texinfo.flags.native & Q2_SURF_SKIP; // skip brushes in a hint brush are treated as "hintskip", and discarded +} /* ================= @@ -383,15 +394,16 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, hullbrush->maxs[i] = -VECT_MAX; } + auto DiscardHintSkipFace = (options.target_version->game->id == GAME_QUAKE_II) ? DiscardHintSkipFace_Q2 : DiscardHintSkipFace_Q1; + mapface = hullbrush->faces; for (i = 0; i < hullbrush->numfaces; i++, mapface++) { - if (!hullnum && (hullbrush->contents.extended & CFLAGS_HINT)) { + if (!hullnum && hullbrush->contents.is_hint()) { /* Don't generate hintskip faces */ const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo); - const char *texname = map.miptexTextureName(texinfo.miptex).c_str(); - if (Q_strcasecmp(texname, "hint")) - continue; // anything texname other than "hint" in a hint brush is treated as "hintskip", and discarded + if (DiscardHintSkipFace(hullnum, hullbrush, texinfo)) + continue; } w = BaseWindingForPlane(&mapface->plane); @@ -832,11 +844,36 @@ Brush_IsDetail(const mapbrush_t *mapbrush) return false; } +// adjust the given content flags from the texture name input. +// this is where special names are transformed into game-specific +// contents. +static bool AdjustContentsFromName(const char *texname, contentflags_t &flags) { + if (!Q_strcasecmp(texname, "origin")) + flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_ORIGIN)); + else if (!Q_strcasecmp(texname, "hint")) + flags = flags.merge(options.target_version->game->create_empty_contents(CFLAGS_HINT)); + else if (!Q_strcasecmp(texname, "clip")) + flags = flags.merge(options.target_version->game->create_solid_contents(CFLAGS_CLIP)); + else if (texname[0] == '*') { + if (!Q_strncasecmp(texname + 1, "lava", 4)) + flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_LAVA)); + else if (!Q_strncasecmp(texname + 1, "slime", 5)) + flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_SLIME)); + flags = flags.merge(options.target_version->game->create_liquid_contents(CONTENTS_WATER)); + } + else if (!Q_strncasecmp(texname, "sky", 3)) + flags = flags.merge(options.target_version->game->create_sky_contents()); + else + return false; + + return true; +} static contentflags_t -Brush_GetContents(const mapbrush_t *mapbrush) +Brush_GetContents_Q1(const mapbrush_t *mapbrush, const contentflags_t &base_contents) { const char *texname; + contentflags_t contents = base_contents; //check for strong content indicators for (int i = 0; i < mapbrush->numfaces; i++) @@ -845,36 +882,65 @@ Brush_GetContents(const mapbrush_t *mapbrush) const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); texname = map.miptexTextureName(texinfo.miptex).c_str(); - // if we were already assigned contents from somewhere else - // (Quake II), use this. - if (options.target_version->game->id == GAME_QUAKE_II) { - if (mapface.contents) { - return { mapface.contents }; - } - - continue; + if (AdjustContentsFromName(texname, contents)) { + return contents; } - - if (!Q_strcasecmp(texname, "origin")) - return { CONTENTS_EMPTY, CFLAGS_ORIGIN }; - if (!Q_strcasecmp(texname, "hint")) - return { CONTENTS_EMPTY, CFLAGS_HINT }; - if (!Q_strcasecmp(texname, "clip")) - return { CONTENTS_SOLID, CFLAGS_CLIP }; - - if (texname[0] == '*') { - if (!Q_strncasecmp(texname + 1, "lava", 4)) - return { CONTENTS_LAVA }; - if (!Q_strncasecmp(texname + 1, "slime", 5)) - return { CONTENTS_SLIME }; - return { CONTENTS_WATER }; - } - - if (!Q_strncasecmp(texname, "sky", 3)) - return { CONTENTS_SKY }; } + //and anything else is assumed to be a regular solid. - return { options.target_version->game->id == GAME_QUAKE_II ? Q2_CONTENTS_SOLID : CONTENTS_SOLID }; + return options.target_version->game->create_solid_contents(); +} + +static contentflags_t +Brush_GetContents_Q2 (const mapbrush_t *mapbrush, const contentflags_t &base_contents) +{ + bool is_trans = false; + bool is_hint = false; + contentflags_t contents = base_contents.merge({ mapbrush->face(0).contents }); + + for (int i = 0; i < mapbrush->numfaces; i++) + { + const mapface_t &mapface = mapbrush->face(i); + const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo); + + if (!is_trans && (texinfo.flags.native & (Q2_SURF_TRANS33 | Q2_SURF_TRANS66))) { + is_trans = true; + } + + if (!is_hint && (texinfo.flags.native & Q2_SURF_HINT)) { + is_hint = true; + } + + if (mapface.contents != contents.native) { + logprint("mixed face contents\n"); // TODO: need entity # and brush # + break; + } + } + + // if any side is translucent, mark the contents + // and change solid to window + if (is_trans) { + contents.native = (contents.native & ~Q2_CONTENTS_SOLID) | (Q2_CONTENTS_TRANSLUCENT | Q2_CONTENTS_WINDOW); + } + + // add extended flags that we may need + if (contents.native & Q2_CONTENTS_DETAIL) { + contents.extended |= CFLAGS_DETAIL; + } + + if (contents.native & (Q2_CONTENTS_MONSTERCLIP | Q2_CONTENTS_PLAYERCLIP)) { + contents.extended |= CFLAGS_CLIP; + } + + if (contents.native & Q2_CONTENTS_ORIGIN) { + contents.extended |= CFLAGS_ORIGIN; + } + + if (is_hint) { + contents.extended |= CFLAGS_HINT; + } + + return contents; } @@ -1114,7 +1180,6 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) int i; int lmshift; bool all_detail, all_detail_fence, all_detail_illusionary; - contentflags_t contents { }; /* * The brush list needs to be ordered (lowest to highest priority): @@ -1132,9 +1197,21 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) rotation_t rottype = rotation_t::none; VectorCopy(vec3_origin, rotate_offset); + const bool func_illusionary_visblocker = + (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); + + contentflags_t base_contents = options.target_version->game->create_empty_contents(); + + if (func_illusionary_visblocker) { + base_contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; + } + + // TODO: move to game + auto Brush_GetContents = (options.target_version->game->id == GAME_QUAKE_II) ? Brush_GetContents_Q2 : Brush_GetContents_Q1; + for (int i = 0; i < src->nummapbrushes; i++) { const mapbrush_t *mapbrush = &src->mapbrush(i); - const contentflags_t contents = Brush_GetContents(mapbrush); + const contentflags_t contents = Brush_GetContents(mapbrush, base_contents); if (contents.extended & CFLAGS_ORIGIN) { if (dst == pWorldEnt()) { Message(msgWarning, warnOriginBrushInWorld); @@ -1175,7 +1252,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) } if (!Q_strcasecmp(classname, "func_detail_wall") && !options.fNodetail) { all_detail = true; - contents.extended |= CFLAGS_DETAIL_WALL; + base_contents.extended |= CFLAGS_DETAIL_WALL; } all_detail_fence = false; @@ -1200,20 +1277,17 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) /* _mirrorinside key (for func_water etc.) */ if (atoi(ValueForKey(src, "_mirrorinside"))) { - contents.extended |= CFLAGS_BMODEL_MIRROR_INSIDE; + base_contents.extended |= CFLAGS_BMODEL_MIRROR_INSIDE; } /* _noclipfaces */ if (atoi(ValueForKey(src, "_noclipfaces"))) { - contents.extended |= CFLAGS_NO_CLIPPING_SAME_TYPE; + base_contents.extended |= CFLAGS_NO_CLIPPING_SAME_TYPE; } - const bool func_illusionary_visblocker = - (0 == Q_strcasecmp(classname, "func_illusionary_visblocker")); - for (i = 0; i < src->nummapbrushes; i++, mapbrush++) { mapbrush = &src->mapbrush(i); - contents = contents.merge(Brush_GetContents(mapbrush)); + contentflags_t contents = Brush_GetContents(mapbrush, base_contents); // per-brush settings bool detail = Brush_IsDetail(mapbrush); @@ -1224,21 +1298,15 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) detail |= all_detail; detail_illusionary |= all_detail_illusionary; detail_fence |= all_detail_fence; - - /* FIXME: move into Brush_GetContents? */ - if (func_illusionary_visblocker) { - contents.native = CONTENTS_EMPTY; - contents.extended |= CFLAGS_ILLUSIONARY_VISBLOCKER; - } /* "origin" brushes always discarded */ if (contents.extended & CFLAGS_ORIGIN) continue; /* -omitdetail option omits all types of detail */ - if (options.fOmitDetail && detail && !(contents.extended & CFLAGS_DETAIL_WALL)) + if (options.fOmitDetail && detail && !contents.is_detail(CFLAGS_DETAIL_WALL)) continue; - if ((options.fOmitDetail || options.fOmitDetailWall) && detail && (contents.extended & CFLAGS_DETAIL_WALL)) + if ((options.fOmitDetail || options.fOmitDetailWall) && detail && contents.is_detail(CFLAGS_DETAIL_WALL)) continue; if ((options.fOmitDetail || options.fOmitDetailIllusionary) && detail_illusionary) continue; @@ -1246,15 +1314,13 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) continue; /* turn solid brushes into detail, if we're in hull0 */ - if (hullnum == 0 && contents.native == CONTENTS_SOLID) { + if (hullnum == 0 && contents.is_solid(options.target_version->game)) { if (detail) { contents.extended |= CFLAGS_DETAIL; } else if (detail_illusionary) { - contents.native = CONTENTS_EMPTY; - contents.extended |= CFLAGS_DETAIL_ILLUSIONARY; + contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_ILLUSIONARY)); } else if (detail_fence) { - contents.native = CONTENTS_EMPTY; // fences need to generate leaves - contents.extended |= CFLAGS_DETAIL_FENCE; + contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE)); // fences need to generate leaves } } @@ -1282,15 +1348,15 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) } /* "hint" brushes don't affect the collision hulls */ - if (contents.extended & CFLAGS_HINT) { + if (contents.is_hint()) { if (hullnum) continue; - contents.native = CONTENTS_EMPTY; + contents = contents.merge(options.target_version->game->create_empty_contents()); } /* entities never use water merging */ if (dst != pWorldEnt()) - contents.native = CONTENTS_SOLID; + contents = contents.merge(options.target_version->game->create_solid_contents()); /* Hack to turn bmodels with "_mirrorinside" into func_detail_fence in hull 0. this is to allow "_mirrorinside" to work on func_illusionary, func_wall, etc. @@ -1301,20 +1367,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) contents type. */ if (dst != pWorldEnt() && hullnum == 0 && (contents.extended & CFLAGS_BMODEL_MIRROR_INSIDE)) { - contents.native = CONTENTS_EMPTY; - contents.extended |= CFLAGS_DETAIL_FENCE; + contents = contents.merge(options.target_version->game->create_empty_contents(CFLAGS_DETAIL_FENCE)); } /* nonsolid brushes don't show up in clipping hulls */ // TODO: will this statement need to be modified since clip // detail etc aren't native types any more? - if (hullnum > 0 && contents.native != CONTENTS_SOLID && contents.native != CONTENTS_SKY) + if (hullnum > 0 && !contents.is_solid(options.target_version->game) && !contents.is_sky(options.target_version->game)) continue; /* sky brushes are solid in the collision hulls */ - if (hullnum > 0 && contents.native == CONTENTS_SKY) - contents.native = CONTENTS_SOLID; - + if (hullnum > 0 && contents.is_sky(options.target_version->game)) + contents = contents.merge(options.target_version->game->create_solid_contents()); + brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum); if (!brush) continue; @@ -1322,19 +1387,19 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum) dst->numbrushes++; brush->lmshift = lmshift; - if (brush->contents.extended & CFLAGS_DETAIL) { + if (brush->contents.is_detail(CFLAGS_DETAIL)) { brush->next = dst->detail; dst->detail = brush; - } else if (brush->contents.extended & CFLAGS_DETAIL_ILLUSIONARY) { + } else if (brush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY)) { brush->next = dst->detail_illusionary; dst->detail_illusionary = brush; - } else if (brush->contents.extended & CFLAGS_DETAIL_FENCE) { + } else if (brush->contents.is_detail(CFLAGS_DETAIL_FENCE)) { brush->next = dst->detail_fence; dst->detail_fence = brush; - } else if (brush->contents.native == CONTENTS_SOLID) { + } else if (brush->contents.is_solid(options.target_version->game)) { brush->next = dst->solid; dst->solid = brush; - } else if (brush->contents.native == CONTENTS_SKY) { + } else if (brush->contents.is_sky(options.target_version->game)) { brush->next = dst->sky; dst->sky = brush; } else { diff --git a/qbsp/csg4.cc b/qbsp/csg4.cc index 8c76a004..efd14c41 100644 --- a/qbsp/csg4.cc +++ b/qbsp/csg4.cc @@ -373,8 +373,8 @@ SaveFacesToPlaneList(face_t *facelist, bool mirror, std::map &pla // HACK: We only want this mirrored face for CONTENTS_DETAIL // to force the right content type for the leaf, but we don't actually // want the face. So just set the texinfo to "skip" so it gets deleted. - if ((face->contents[1].extended & (CFLAGS_DETAIL | CFLAGS_DETAIL_ILLUSIONARY | CFLAGS_DETAIL_FENCE | CFLAGS_WAS_ILLUSIONARY)) - || (options.fContentHack && face->contents[1].native == CONTENTS_SOLID)) { + if ((face->contents[1].is_detail() || (face->contents[1].extended & CFLAGS_WAS_ILLUSIONARY)) + || (options.fContentHack && face->contents[1].is_structural_solid(options.target_version->game))) { // if CFLAGS_BMODEL_MIRROR_INSIDE is set, never change to skip if (!(face->contents[1].extended & CFLAGS_BMODEL_MIRROR_INSIDE)) { @@ -423,7 +423,7 @@ contents override the face inside contents. static void SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) { - Q_assert(clipbrush->contents.native != CONTENTS_SOLID); + Q_assert(!clipbrush->contents.is_solid(options.target_version->game)); face_t *next; @@ -434,8 +434,8 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) next = face->next; face->contents[0] = clipbrush->contents; - if ((face->contents[1].native == CONTENTS_SOLID || face->contents[1].native == CONTENTS_SKY) - && (clipbrush->contents.extended & CFLAGS_DETAIL)) { + if ((face->contents[1].is_solid(options.target_version->game) || face->contents[1].is_sky(options.target_version->game)) + && clipbrush->contents.is_detail(CFLAGS_DETAIL)) { // This case is when a structural and detail brush are touching, // and we want to save the sturctural face that is // touching detail. @@ -450,26 +450,26 @@ SaveInsideFaces(face_t *face, const brush_t *clipbrush, face_t **savelist) // marked as empty here, and the detail faces have their "back" // marked as detail. - face->contents[0] = { CONTENTS_EMPTY, CFLAGS_STRUCTURAL_COVERED_BY_DETAIL }; + face->contents[0] = options.target_version->game->create_empty_contents(CFLAGS_STRUCTURAL_COVERED_BY_DETAIL); face->texinfo = MakeSkipTexinfo(); } // N.B.: We don't need a hack like above for when clipbrush->contents == CONTENTS_DETAIL_ILLUSIONARY. // These would create leaks - Q_assert(!((face->contents[1].native == CONTENTS_SKY || face->contents[1].native == CONTENTS_SOLID) && - (face->contents[0].extended & CFLAGS_DETAIL))); + Q_assert(!((face->contents[1].is_sky(options.target_version->game) || face->contents[1].is_solid(options.target_version->game)) && + face->contents[0].is_detail(CFLAGS_DETAIL))); /* * If the inside brush is empty space, inherit the outside contents. * The only brushes with empty contents currently are hint brushes. */ - if (face->contents[1].extended & CFLAGS_DETAIL_ILLUSIONARY) { - face->contents[1] = { clipbrush->contents.native, (uint16_t) ((face->contents[1].extended & ~CFLAGS_DETAIL_ILLUSIONARY) | CFLAGS_WAS_ILLUSIONARY) }; + if (face->contents[1].is_detail(CFLAGS_DETAIL_ILLUSIONARY)) { + face->contents[1] = { clipbrush->contents.native, (face->contents[1].extended & ~CFLAGS_DETAIL_ILLUSIONARY) | CFLAGS_WAS_ILLUSIONARY }; } - if (face->contents[1].native == CONTENTS_EMPTY) + if (face->contents[1].is_empty(options.target_version->game)) { face->contents[1] = clipbrush->contents; - + } face->next = *savelist; *savelist = face; @@ -533,7 +533,7 @@ CopyBrushFaces(const brush_t *brush) brushfaces++; newface = (face_t *)AllocMem(OTHER, sizeof(face_t), true); *newface = *face; - newface->contents[0] = { CONTENTS_EMPTY }; + newface->contents[0] = options.target_version->game->create_empty_contents(); newface->contents[1] = brush->contents; newface->lmshift[0] = brush->lmshift; newface->lmshift[1] = brush->lmshift; @@ -544,17 +544,6 @@ CopyBrushFaces(const brush_t *brush) return facelist; } -static bool IsLiquid(const contentflags_t &contents) -{ - return contents.native == CONTENTS_WATER - || contents.native == CONTENTS_LAVA - || contents.native == CONTENTS_SLIME; -} - -static bool IsFence(const contentflags_t &contents) { - return contents.extended & (CFLAGS_DETAIL_FENCE | CFLAGS_DETAIL_ILLUSIONARY); -} - /* ================== CSGFaces @@ -610,35 +599,35 @@ CSGFaces(const mapentity_t *entity) overwrite = true; continue; } - if (clipbrush->contents.native == CONTENTS_EMPTY) { + if (clipbrush->contents.is_hint()) { /* Ensure hint never clips anything */ continue; } - if ((clipbrush->contents.extended & CFLAGS_DETAIL_ILLUSIONARY) - && !(brush->contents.extended & CFLAGS_DETAIL_ILLUSIONARY)) { + if (clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY) + && !brush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY)) { /* CONTENTS_DETAIL_ILLUSIONARY never clips anything but itself */ continue; } - if ((clipbrush->contents.extended & CFLAGS_DETAIL) && (clipbrush->contents.extended & CFLAGS_DETAIL_WALL) - && !((brush->contents.extended & CFLAGS_DETAIL) && (brush->contents.extended & CFLAGS_DETAIL_WALL))) { + if (clipbrush->contents.is_detail(CFLAGS_DETAIL) && clipbrush->contents.is_detail(CFLAGS_DETAIL_WALL) + && !(brush->contents.is_detail(CFLAGS_DETAIL) && brush->contents.is_detail(CFLAGS_DETAIL_WALL))) { /* if clipbrush has CONTENTS_DETAIL and CFLAGS_DETAIL_WALL are set, only clip other brushes with both CONTENTS_DETAIL and CFLAGS_DETAIL_WALL. */ continue; } - if ((clipbrush->contents.extended & CFLAGS_DETAIL_FENCE) - && !(brush->contents.extended & CFLAGS_DETAIL_FENCE)) { + if (clipbrush->contents.is_detail(CFLAGS_DETAIL_FENCE) + && !brush->contents.is_detail(CFLAGS_DETAIL_FENCE)) { /* CONTENTS_DETAIL_FENCE never clips anything but itself */ continue; } // TODO: this might break because this == won't catch the extended types now. // might need a specific function for this one. - if (clipbrush->contents.native == brush->contents.native - && (clipbrush->contents.extended & CFLAGS_NO_CLIPPING_SAME_TYPE)) { + if (clipbrush->contents.types_equal(brush->contents, options.target_version->game) + && !clipbrush->contents.clips_same_type()) { /* _noclipfaces key */ continue; } @@ -679,14 +668,18 @@ CSGFaces(const mapentity_t *entity) * * FIXME: clean this up, the predicate seems to be "can you see 'brush' from inside 'clipbrush'" */ - if ((brush->contents.native == CONTENTS_SOLID && clipbrush->contents.native != CONTENTS_SOLID) - || (brush->contents.native == CONTENTS_SKY && (clipbrush->contents.native != CONTENTS_SOLID - && clipbrush->contents.native != CONTENTS_SKY)) - || ((brush->contents.extended & CFLAGS_DETAIL) && (clipbrush->contents.native != CONTENTS_SOLID - && clipbrush->contents.native != CONTENTS_SKY - && !(clipbrush->contents.extended & CFLAGS_DETAIL))) - || (IsLiquid(brush->contents) && (clipbrush->contents.extended & CFLAGS_DETAIL_ILLUSIONARY)) - || (IsFence(brush->contents) && (IsLiquid(clipbrush->contents) || IsFence(clipbrush->contents)))) + if ((brush->contents.is_structural_solid(options.target_version->game) && !clipbrush->contents.is_structural_solid(options.target_version->game)) + + || (brush->contents.is_structural_sky(options.target_version->game) && !clipbrush->contents.is_structural_sky_or_solid(options.target_version->game)) + + || ((brush->contents.is_solid(options.target_version->game) && brush->contents.is_detail(CFLAGS_DETAIL)) && + (!clipbrush->contents.is_solid(options.target_version->game) + && !clipbrush->contents.is_sky(options.target_version->game) + && !clipbrush->contents.is_detail(CFLAGS_DETAIL))) + + || (brush->contents.is_liquid(options.target_version->game) && clipbrush->contents.is_detail(CFLAGS_DETAIL_ILLUSIONARY)) + + || (brush->contents.is_fence() && (clipbrush->contents.is_liquid(options.target_version->game) || clipbrush->contents.is_fence()))) { SaveInsideFaces(inside, clipbrush, &outside); } else { @@ -708,7 +701,7 @@ CSGFaces(const mapentity_t *entity) * All of the faces left on the outside list are real surface faces * If the brush is non-solid, mirror faces for the inside view */ - const bool mirror = options.fContentHack ? true : (brush->contents.native != CONTENTS_SOLID); + const bool mirror = options.fContentHack ? true : !brush->contents.is_solid(options.target_version->game); SaveFacesToPlaneList(outside, mirror, planefaces); } surface_t *surfaces = BuildSurfaces(planefaces); diff --git a/qbsp/outside.cc b/qbsp/outside.cc index ff50eb5b..43e488b5 100644 --- a/qbsp/outside.cc +++ b/qbsp/outside.cc @@ -338,7 +338,7 @@ ClearOutFaces(node_t *node) } // visit the leaf - if (node->contents.native != CONTENTS_SOLID) { + if (!node->contents.is_solid(options.target_version->game)) { return; } @@ -365,8 +365,8 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count) return; // Don't fill sky, or count solids as outleafs - if (node->contents.native == CONTENTS_SKY - || node->contents.native == CONTENTS_SOLID) + if (node->contents.is_sky(options.target_version->game) + || node->contents.is_solid(options.target_version->game)) return; // Now check all faces touching the leaf. If any of them are partially going into the occupied part of the map, @@ -383,7 +383,7 @@ OutLeafsToSolid_r(node_t *node, int *outleafs_count) } // Finally, we can fill it in as void. - node->contents = { CONTENTS_SOLID }; + node->contents = options.target_version->game->create_solid_contents(); *outleafs_count += 1; } diff --git a/qbsp/portals.cc b/qbsp/portals.cc index 8a9295cd..eb4741d0 100644 --- a/qbsp/portals.cc +++ b/qbsp/portals.cc @@ -72,7 +72,7 @@ PortalThru(const portal_t *p) contentflags_t contents1 = ClusterContents(p->nodes[1]); /* Can't see through solids */ - if (contents0.native == CONTENTS_SOLID || contents1.native == CONTENTS_SOLID) + if (contents0.is_structural_solid(options.target_version->game) || contents1.is_structural_solid(options.target_version->game)) return false; /* Can't see through func_illusionary_visblocker */ @@ -85,19 +85,17 @@ PortalThru(const portal_t *p) /* If water is transparent, liquids are like empty space */ if (options.fTranswater) { - if (contents0.native >= CONTENTS_LAVA && contents0.native <= CONTENTS_WATER && - contents1.native == CONTENTS_EMPTY) + if (contents0.is_liquid(options.target_version->game) && contents1.is_empty(options.target_version->game)) return true; - if (contents1.native >= CONTENTS_LAVA && contents1.native <= CONTENTS_WATER && - contents0.native == CONTENTS_EMPTY) + if (contents1.is_liquid(options.target_version->game) && contents0.is_empty(options.target_version->game)) return true; } /* If sky is transparent, then sky is like empty space */ if (options.fTranssky) { - if (contents0.native == CONTENTS_SKY && contents1.native == CONTENTS_EMPTY) + if (contents0.is_sky(options.target_version->game) && contents1.is_empty(options.target_version->game)) return true; - if (contents0.native == CONTENTS_EMPTY && contents1.native == CONTENTS_SKY) + if (contents0.is_empty(options.target_version->game) && contents1.is_sky(options.target_version->game)) return true; } @@ -118,7 +116,7 @@ WritePortals_r(node_t *node, FILE *portalFile, bool clusters) WritePortals_r(node->children[1], portalFile, clusters); return; } - if (node->contents.native == CONTENTS_SOLID) + if (node->contents.is_solid(options.target_version->game)) return; for (p = node->portals; p; p = next) { @@ -163,7 +161,7 @@ WriteClusters_r(node_t *node, FILE *portalFile, int viscluster) viscluster = WriteClusters_r(node->children[1], portalFile, viscluster); return viscluster; } - if (node->contents.native == CONTENTS_SOLID) + if (node->contents.is_solid(options.target_version->game)) return viscluster; /* If we're in the next cluster, start a new line */ @@ -225,7 +223,7 @@ NumberLeafs_r(node_t *node, portal_state_t *state, int cluster) return; } - if (node->contents.native == CONTENTS_SOLID) { + if (node->contents.is_solid(options.target_version->game)) { /* solid block, viewpoint never inside */ node->visleafnum = -1; node->viscluster = -1; @@ -381,7 +379,7 @@ MakeHeadnodePortals(const mapentity_t *entity, node_t *node) bounds[1][i] = entity->maxs[i] + SIDESPACE; } - outside_node.contents = { CONTENTS_SOLID }; + outside_node.contents = options.target_version->game->create_solid_contents(); outside_node.portals = NULL; for (i = 0; i < 3; i++) diff --git a/qbsp/qbsp.cc b/qbsp/qbsp.cc index f62f625c..c737f6f0 100644 --- a/qbsp/qbsp.cc +++ b/qbsp/qbsp.cc @@ -34,6 +34,10 @@ static const char *IntroString = // command line flags options_t options; +bool node_t::opaque() const { + return contents.is_structural_sky_or_solid(options.target_version->game); +} + /* =============== ProcessEntity diff --git a/qbsp/solidbsp.cc b/qbsp/solidbsp.cc index 52a1e93b..8bc16701 100644 --- a/qbsp/solidbsp.cc +++ b/qbsp/solidbsp.cc @@ -74,10 +74,10 @@ DetailToSolid(node_t *node) // If both children are solid, we can merge the two leafs into one. // DarkPlaces has an assertion that fails if both children are // solid. - if (node->children[0]->contents.native == CONTENTS_SOLID - && node->children[1]->contents.native == CONTENTS_SOLID) { + if (node->children[0]->contents.is_structural_solid(options.target_version->game) + && node->children[1]->contents.is_structural_solid(options.target_version->game)) { // This discards any faces on-node. Should be safe (?) - ConvertNodeToLeaf(node, { CONTENTS_SOLID }); + ConvertNodeToLeaf(node, options.target_version->game->create_solid_contents()); } } } @@ -538,7 +538,7 @@ CalcSurfaceInfo(surface_t *surf) surf->has_struct = false; for (const face_t *f = surf->faces; f; f = f->next) { - if (f->contents[0].native >= 0 || f->contents[1].native >= 0) + if (!f->contents[0].is_valid(options.target_version->game, false) || !f->contents[1].is_valid(options.target_version->game, false)) Error("Bad contents in face (%s)", __func__); surf->lmshift = (f->lmshift[0]lmshift[1])?f->lmshift[0]:f->lmshift[1]; @@ -728,34 +728,6 @@ GetContentsName( const contentflags_t &Contents ) { } } -int Contents_Priority(const contentflags_t &contents) -{ - if (contents.extended & CFLAGS_DETAIL) { - return 5; - } else if (contents.extended & CFLAGS_DETAIL_ILLUSIONARY) { - return 3; - } else if (contents.extended & CFLAGS_DETAIL_FENCE) { - return 4; - } else if (contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) { - return 2; - } else switch( contents.native ) { - case CONTENTS_SOLID: return 7; - - case CONTENTS_SKY: return 6; - - case CONTENTS_WATER: return 2; - case CONTENTS_SLIME: return 2; - case CONTENTS_LAVA: return 2; - - case CONTENTS_EMPTY: return 1; - case 0: return 0; - - default: - Error("Bad contents in face (%s)", __func__); - return 0; - } -} - /* ================== LinkConvexFaces @@ -770,26 +742,29 @@ static void LinkConvexFaces(surface_t *planelist, node_t *leafnode) { leafnode->faces = NULL; - leafnode->contents = { 0, 0 }; leafnode->planenum = PLANENUM_LEAF; int count = 0; + std::optional contents; + for (surface_t *surf = planelist; surf; surf = surf->next) { for (face_t *f = surf->faces; f; f = f->next) { count++; - const int currentpri = Contents_Priority(leafnode->contents); - const int fpri = Contents_Priority(f->contents[0]); + const int currentpri = contents.has_value() ? contents->priority(options.target_version->game) : -1; + const int fpri = f->contents[0].priority(options.target_version->game); if (fpri > currentpri) { - leafnode->contents = f->contents[0]; + contents = f->contents[0]; } // HACK: Handle structural covered by detail. if (f->contents[0].extended & CFLAGS_STRUCTURAL_COVERED_BY_DETAIL) { - Q_assert(f->contents[0].native == CONTENTS_EMPTY); + Q_assert(f->contents[0].is_empty(options.target_version->game)); - if (Contents_Priority({ CONTENTS_SOLID, CFLAGS_DETAIL }) > currentpri) { - leafnode->contents = { CONTENTS_SOLID, CFLAGS_DETAIL }; + const contentflags_t solid_detail = options.target_version->game->create_solid_contents(CFLAGS_DETAIL); + + if (solid_detail.priority(options.target_version->game) > currentpri) { + contents = solid_detail; } } } @@ -798,9 +773,7 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode) // NOTE: This is crazy.. // Liquid leafs get assigned liquid content types because of the // "cosmetic" mirrored faces. - if (!leafnode->contents.native) { - leafnode->contents.native = CONTENTS_SOLID; // FIXME: Need to create CONTENTS_DETAIL sometimes? - } + leafnode->contents = contents.value_or(options.target_version->game->create_solid_contents()); // FIXME: Need to create CONTENTS_DETAIL sometimes? if (leafnode->contents.extended & CFLAGS_ILLUSIONARY_VISBLOCKER) { c_illusionary_visblocker++; @@ -810,20 +783,13 @@ LinkConvexFaces(surface_t *planelist, node_t *leafnode) c_detail_illusionary++; } else if (leafnode->contents.extended & CFLAGS_DETAIL) { c_detail++; - } else switch (leafnode->contents.native) { - case CONTENTS_EMPTY: + } else if (leafnode->contents.is_empty(options.target_version->game)) { c_empty++; - break; - case CONTENTS_SOLID: + } else if (leafnode->contents.is_solid(options.target_version->game)) { c_solid++; - break; - case CONTENTS_WATER: - case CONTENTS_SLIME: - case CONTENTS_LAVA: - case CONTENTS_SKY: + } else if (leafnode->contents.is_liquid(options.target_version->game) || leafnode->contents.is_sky(options.target_version->game)) { c_water++; - break; - default: + } else { Error("Bad contents in face (%s)", __func__); } @@ -983,11 +949,11 @@ SolidBSP(const mapentity_t *entity, surface_t *surfhead, bool midsplit) } headnode->children[0] = (node_t *)AllocMem(OTHER, sizeof(node_t), true); headnode->children[0]->planenum = PLANENUM_LEAF; - headnode->children[0]->contents = { CONTENTS_EMPTY }; + headnode->children[0]->contents = options.target_version->game->create_empty_contents(); headnode->children[0]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true); headnode->children[1] = (node_t *)AllocMem(OTHER, sizeof(node_t), true); headnode->children[1]->planenum = PLANENUM_LEAF; - headnode->children[1]->contents = { CONTENTS_EMPTY }; + headnode->children[1]->contents = options.target_version->game->create_empty_contents(); headnode->children[1]->markfaces = (face_t **)AllocMem(OTHER, sizeof(face_t *), true); return headnode; diff --git a/qbsp/surfaces.cc b/qbsp/surfaces.cc index 868085cf..5ccad464 100644 --- a/qbsp/surfaces.cc +++ b/qbsp/surfaces.cc @@ -292,8 +292,8 @@ GetEdge(mapentity_t *entity, const vec3_t p1, const vec3_t p2, int v1, v2; int i; - if (!face->contents[0].native) - Error("Face with 0 contents (%s)", __func__); + if (!face->contents[0].is_valid(options.target_version->game)) + Error("Face with invalid contents (%s)", __func__); v1 = GetVertex(entity, p1); v2 = GetVertex(entity, p2); diff --git a/qbsp/writebsp.cc b/qbsp/writebsp.cc index c157d854..ec10ee4b 100644 --- a/qbsp/writebsp.cc +++ b/qbsp/writebsp.cc @@ -48,8 +48,8 @@ AssertVanillaContentType(int content) } } -static int -RemapContentsForExport_(const contentflags_t &content) +static contentflags_t +RemapContentsForExport(const contentflags_t &content) { if (content.extended & CFLAGS_DETAIL_FENCE) { /* @@ -58,40 +58,10 @@ RemapContentsForExport_(const contentflags_t &content) * * Normally solid leafs are not written and just referenced as leaf 0. */ - return CONTENTS_SOLID; + return options.target_version->game->create_solid_contents(); } - return content.native; -} - -static int -RemapContentsForExport(const contentflags_t &content) -{ - int32_t contents = RemapContentsForExport_(content); - - // TODO - if (options.target_version->game->id == GAME_QUAKE_II) { - switch (contents) { - case CONTENTS_EMPTY: - return 0; - case CONTENTS_SOLID: - case CONTENTS_SKY: - if (content.extended & CFLAGS_CLIP) { - return Q2_CONTENTS_PLAYERCLIP | Q2_CONTENTS_MONSTERCLIP; - } - return Q2_CONTENTS_SOLID; - case CONTENTS_WATER: - return Q2_CONTENTS_WATER; - case CONTENTS_SLIME: - return Q2_CONTENTS_SLIME; - case CONTENTS_LAVA: - return Q2_CONTENTS_LAVA; - default: - Error("dunno what to do with contents %i\n", content); - } - } - - return contents; + return content; } /** @@ -225,7 +195,7 @@ ExportLeaf(mapentity_t *entity, node_t *node) map.exported_leafs.push_back({}); mleaf_t *dleaf = &map.exported_leafs.back(); - dleaf->contents = RemapContentsForExport(node->contents); + dleaf->contents = RemapContentsForExport(node->contents).native; AssertVanillaContentType(dleaf->contents); /* @@ -291,7 +261,7 @@ ExportDrawNodes(mapentity_t *entity, node_t *node) if (node->children[i]->planenum == PLANENUM_LEAF) { // In Q2, all leaves must have their own ID even if they share solidity. // (probably for collision purposes? makes sense given they store leafbrushes) - if (options.target_version->game->id != GAME_QUAKE_II && node->children[i]->contents.native == CONTENTS_SOLID) + if (options.target_version->game->id != GAME_QUAKE_II && node->children[i]->contents.is_solid(options.target_version->game)) dnode->children[i] = -1; else { int nextLeafIndex = static_cast(map.exported_leafs.size()); @@ -368,7 +338,7 @@ BeginBSPFile(void) // Leave room for leaf 0 (must be solid) map.exported_leafs.push_back({}); - map.exported_leafs.back().contents = RemapContentsForExport({ CONTENTS_SOLID }); + map.exported_leafs.back().contents = options.target_version->game->create_solid_contents().native; Q_assert(map.exported_leafs.size() == 1); }