Skip to content

Commit 2c45be0

Browse files
committed
Merge remote-tracking branch 'Paril/sin_support' into sin_support_ericw
2 parents f298bd8 + c3ef9b5 commit 2c45be0

13 files changed

Lines changed: 201 additions & 102 deletions

File tree

bsputil/bsputil.cc

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ bsputil_settings::bsputil_settings()
7474
[&](const std::string &name, parser_base_t &parser, settings::source src) {
7575
return this->load_setting<settings::setting_bool>(name, parser, src, "");
7676
},
77-
nullptr, "Extract BSP texutres to the given wad file"},
77+
nullptr, "Extract BSP textures to the given wad file"},
7878
replace_textures{this, "replace-textures",
7979
[&](const std::string &name, parser_base_t &parser, settings::source src) {
8080
return this->load_setting<settings::setting_string>(name, parser, src, "");
@@ -131,11 +131,7 @@ bsputil_settings::bsputil_settings()
131131
return this->load_setting(name, src);
132132
},
133133
nullptr, "Decompile to the given .map file"},
134-
sin_anims{this, "sin_anims",
135-
[&](const std::string &name, parser_base_t &parser, settings::source src) {
136-
return this->load_setting(name, src);
137-
},
138-
nullptr, "Use for decompiling; creates a .srf file for a decompiled map for animations"},
134+
decompile_suffix{this, "decompile-suffix", ".decompile", "\"str\"", nullptr, "Suffix to add to decompiled map name"},
139135
decompile_geomonly{this, "decompile-geomonly",
140136
[&](const std::string &name, parser_base_t &parser, settings::source src) {
141137
return this->load_setting(name, src);
@@ -1539,59 +1535,75 @@ int bsputil_main(int _argc, const char **_argv)
15391535

15401536
// generate output filename
15411537
if (hull) {
1542-
source.replace_extension(fmt::format(".decompile.hull{}.map", hullnum));
1538+
source.replace_extension(fmt::format("{}.hull{}.map", bsputil_options.decompile_suffix.value(), hullnum));
15431539
} else {
1544-
source.replace_extension(".decompile.map");
1540+
source.replace_extension(fmt::format("{}.map", bsputil_options.decompile_suffix.value()));
15451541
}
15461542

1547-
logging::print("-> writing {}...\n", source);
1548-
1549-
std::ofstream f(source);
1550-
1551-
if (!f)
1552-
Error("couldn't open {} for writing\n", source);
1553-
1554-
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
1555-
15561543
decomp_options options;
15571544
options.geometryOnly = geomOnly;
15581545
options.ignoreBrushes = ignoreBrushes;
15591546
options.hullnum = hullnum;
15601547

1561-
DecompileBSP(&bsp, options, f);
1548+
if (bspdata.loadversion->game->id == GAME_SIN) {
1549+
source.replace_extension(".srf");
15621550

1563-
f.close();
1551+
logging::print("-> writing surface file {}...\n", source);
15641552

1565-
if (!f)
1566-
Error("{}", strerror(errno));
1567-
}else if (operation->primary_name() == "sin_anims") {
1568-
source.replace_extension(".srf");
1553+
std::ofstream f;
15691554

1570-
logging::print("-> writing {}...\n", source);
1555+
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
15711556

1572-
std::ofstream f(source);
1557+
std::map<std::string_view, std::string_view, natural_case_insensitive_less> written;
15731558

1574-
if (!f)
1575-
Error("couldn't open {} for writing\n", source);
1559+
for (auto &tex : bsp.texinfo) {
1560+
if (tex.nexttexinfo != -1) {
1561+
auto &next = bsp.texinfo[tex.nexttexinfo];
15761562

1577-
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
1563+
if (auto exists = written.find(tex.texturename); exists != written.end()) {
1564+
if (!string_iequals(next.texturename, exists->second)) {
1565+
logging::print("WARNING: animation for {} is specified twice but doesn't lead to {}, leads to {} instead\n", tex.texturename, exists->second, next.texturename);
1566+
continue;
1567+
} else {
1568+
// already written
1569+
continue;
1570+
}
1571+
}
15781572

1579-
std::set<std::string_view, natural_less> written;
1573+
if (options.sin_srfName.empty()) {
1574+
options.sin_srfName = fs::path(source.filename()).generic_string();
1575+
f.open(source);
15801576

1581-
for (auto &tex : bsp.texinfo) {
1582-
if (written.find(tex.texturename) != written.end()) {
1583-
continue;
1577+
if (!f)
1578+
Error("couldn't open {} for writing\n", source);
1579+
}
1580+
1581+
f << tex.texturename << " date 0 anim " << next.texturename << std::endl;
1582+
written.insert(std::make_pair(tex.texturename, next.texturename));
1583+
}
15841584
}
15851585

1586-
if (tex.nexttexinfo != -1) {
1587-
auto &next = bsp.texinfo[tex.nexttexinfo];
1586+
if (!options.sin_srfName.empty()) {
1587+
f.close();
15881588

1589-
f << tex.texturename << " date 0 anim " << next.texturename << std::endl;
1589+
if (!f)
1590+
Error("{}", strerror(errno));
15901591
}
15911592

1592-
written.insert(tex.texturename);
1593+
source.replace_extension(".map");
15931594
}
15941595

1596+
logging::print("-> writing {}...\n", source);
1597+
1598+
std::ofstream f(source);
1599+
1600+
if (!f)
1601+
Error("couldn't open {} for writing\n", source);
1602+
1603+
mbsp_t &bsp = std::get<mbsp_t>(bspdata.bsp);
1604+
1605+
DecompileBSP(&bsp, options, f);
1606+
15951607
f.close();
15961608

15971609
if (!f)

common/bspfile.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1397,10 +1397,11 @@ struct gamedef_sin_t : public gamedef_t
13971397
basedir = gamedir.parent_path() / default_base_dir;
13981398
}
13991399
}
1400-
1400+
1401+
std::error_code ec;
14011402
if (!exists(basedir)) {
14021403
logging::print("WARNING: failed to find basedir '{}'\n", basedir);
1403-
} else if (!exists(gamedir) || !equivalent(gamedir, basedir)) {
1404+
} else if (!exists(gamedir) || !equivalent(gamedir, basedir, ec)) {
14041405
addArchive(basedir);
14051406
logging::print("using basedir: '{}'\n", basedir);
14061407
}

common/decompile.cc

Lines changed: 112 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,6 @@ struct compiled_brush_t
243243
if (side.lightinfo.directangle) {
244244
ewt::print(stream, " directangle {}", side.lightinfo.directangle);
245245
}
246-
// nb: directstyle is unused
247246
if (side.translucence) {
248247
ewt::print(stream, " translucence {}", side.translucence);
249248
}
@@ -263,7 +262,10 @@ struct compiled_brush_t
263262
ewt::print(stream, " nonlitvalue {}", side.nonlit);
264263
}
265264
// nb: groupname is copied to directstyle by qbsp3, they should be identical
266-
// but they might not be in the case of consoles
265+
// but they might not be in the case of consoles.
266+
// the original lightinfo directstyle is only really used for
267+
// the controllable lights *or* setting a lightstyle on a surface as an integer
268+
// but the latter should just work:tm:
267269
if (side.lightinfo.directstylename[0]) {
268270
ewt::print(stream, " directstyle \"{}\"", side.lightinfo.directstylename.data());
269271
} else if (side.groupname[0]) {
@@ -943,7 +945,19 @@ static std::vector<compiled_brush_t> DecompileLeafTaskGeometryOnly(
943945
compiled_brush_t brush;
944946
brush.source = task.brush;
945947
brush.brush_offset = brush_offset;
946-
brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? task.brush->contents
948+
949+
int native_contents = task.brush->contents;
950+
951+
if (bsp->loadversion->game->id == GAME_SIN) {
952+
// SiN cleanup
953+
if (native_contents & SIN_CONTENTS_DUMMYFENCE) {
954+
native_contents &= ~SIN_CONTENTS_DUMMYFENCE;
955+
native_contents &= ~SIN_CONTENTS_WINDOW;
956+
native_contents |= SIN_CONTENTS_FENCE;
957+
}
958+
}
959+
960+
brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? native_contents
947961
: task.leaf ? task.leaf->contents
948962
: task.contents.value());
949963

@@ -1005,7 +1019,19 @@ static std::vector<compiled_brush_t> DecompileLeafTask(const mbsp_t *bsp, const
10051019
compiled_brush_t brush;
10061020
brush.source = task.brush;
10071021
brush.brush_offset = brush_offset;
1008-
brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? task.brush->contents
1022+
1023+
int native_contents = task.brush->contents;
1024+
1025+
if (bsp->loadversion->game->id == GAME_SIN) {
1026+
// SiN cleanup
1027+
if (native_contents & SIN_CONTENTS_DUMMYFENCE) {
1028+
native_contents &= ~SIN_CONTENTS_DUMMYFENCE;
1029+
native_contents &= ~SIN_CONTENTS_WINDOW;
1030+
native_contents |= SIN_CONTENTS_FENCE;
1031+
}
1032+
}
1033+
1034+
brush.contents = bsp->loadversion->game->create_contents_from_native(task.brush ? native_contents
10091035
: task.leaf ? task.leaf->contents
10101036
: task.contents.value());
10111037

@@ -1264,12 +1290,20 @@ static std::vector<compiled_brush_t> DecompileBrushTask(const mbsp_t *bsp, const
12641290
#include "common/parser.hh"
12651291

12661292
static void DecompileEntity(
1267-
const mbsp_t *bsp, const decomp_options &options, std::ofstream &file, entdict_t &dict, bool isWorld)
1293+
const mbsp_t *bsp, const decomp_options &options, std::ofstream &file, std::vector<entdict_t> &dicts, int entityNum)
12681294
{
1295+
auto &dict = dicts[entityNum];
1296+
12691297
// we use -1 to indicate it's not a brush model
12701298
int modelNum = -1;
1271-
if (isWorld) {
1299+
if (entityNum == 0) {
12721300
modelNum = 0;
1301+
1302+
dict.remove(std::string_view{"surfacefile"});
1303+
1304+
if (!options.sin_srfName.empty()) {
1305+
dict.set(std::string_view{"surfacefile"}, options.sin_srfName);
1306+
}
12731307
}
12741308

12751309
const dbrush_t *areaportal_brush = nullptr;
@@ -1304,7 +1338,76 @@ static void DecompileEntity(
13041338
return;
13051339
}
13061340

1341+
// in SiN, custom light styles can be set on certain entities
1342+
// to be controlled by scripts (basically just `trigger_SetLightStyle`). These are set on the entity as a string "style",
1343+
// which is converted to a style ID on compilation and then the directstyle lightinfo
1344+
// value is set with the equivalent value.
1345+
// To reverse this, find entities with the integral style key, then scan all of
1346+
// the lightinfo in the map to find the matching directstyle ID and write its
1347+
// name here instead.
1348+
if (bsp->loadversion->game->id == GAME_SIN && dict.has("style")) {
1349+
if (dict.find("classname")->second != "func_areaportal" &&
1350+
!dict.find("classname")->second.starts_with("light")) {
1351+
int styleId = dict.get_int("style");
1352+
1353+
if (styleId >= 32) {
1354+
dict.remove("style");
1355+
1356+
std::string face_style;
1357+
std::string targetname_style;
1358+
1359+
// find matching face style
1360+
for (auto &lightinfo : bsp->dlightinfo) {
1361+
if (lightinfo.directstyle == styleId) {
1362+
// found it; set the style ID.
1363+
// we don't have to unset the `directstyle` since we don't
1364+
// bother writing it.
1365+
face_style = lightinfo.directstylename.data();
1366+
break;
1367+
}
1368+
}
1369+
1370+
// find matching light style
1371+
for (auto &ent : dicts) {
1372+
if (!ent.has("classname") || !ent.get("classname").starts_with("light"))
1373+
continue;
1374+
1375+
if (ent.has("style") && ent.get_int("style") == styleId) {
1376+
std::string targetname = ent.get("targetname");
1377+
1378+
if (!targetname_style.empty() && targetname_style != targetname) {
1379+
logging::print("WARNING: lightstyle @ {} has conflicting light targetname/style\n{} resolved to both {} and {}\n", dict.get("origin"), styleId, targetname_style, targetname);
1380+
} else {
1381+
targetname_style = targetname;
1382+
}
1383+
}
1384+
}
1385+
1386+
if (face_style.empty() && targetname_style.empty()) {
1387+
logging::print("WARNING: light style {} @ {} exists on an entity but no matching directstyle lightinfo or light targetname was found\n", styleId, dict.get("origin"));
1388+
} else if (!face_style.empty() && !targetname_style.empty() &&
1389+
face_style != targetname_style) {
1390+
logging::print("WARNING: light style {} @ {} exists on an entity, and both a matching lightinfo ({}) and light targetname ({}) was found. We can't figure out which one was intended.\n", styleId, dict.get("origin"), face_style, targetname_style);
1391+
} else if (!face_style.empty()) {
1392+
logging::print("Remapped light style {} @ {} to {}\n", styleId, dict.get("origin"), face_style);
1393+
dict.set("style", face_style);
1394+
} else {
1395+
logging::print("Remapped light style {} @ {} to {}\n", styleId, dict.get("origin"), targetname_style);
1396+
dict.set("style", targetname_style);
1397+
}
1398+
}
1399+
} else if (dict.find("classname")->second.starts_with("light")) {
1400+
// remove controllable style IDs from lights just in case
1401+
int styleId = dict.get_int("style");
1402+
1403+
if (styleId >= 32) {
1404+
dict.remove("style");
1405+
}
1406+
}
1407+
}
1408+
13071409
// First, print the key/values for this entity
1410+
ewt::print(file, "// entity {}\n", entityNum);
13081411
ewt::print(file, "{{\n");
13091412
for (const auto &keyValue : dict) {
13101413
if (keyValue.first == "model" && !keyValue.second.empty() && keyValue.second[0] == '*') {
@@ -1549,15 +1652,16 @@ static void DecompileEntity(
15491652
}
15501653

15511654
ewt::print(file, "}}\n");
1655+
if (modelNum != -1)
1656+
ewt::print(file, "// was brush model {}\n", modelNum);
15521657
}
15531658

15541659
void DecompileBSP(const mbsp_t *bsp, const decomp_options &options, std::ofstream &file)
15551660
{
15561661
auto entdicts = EntData_Parse(*bsp);
15571662

15581663
for (size_t i = 0; i < entdicts.size(); ++i) {
1559-
// entity 0 is implicitly worldspawn (model 0)
1560-
DecompileEntity(bsp, options, file, entdicts[i], i == 0);
1664+
DecompileEntity(bsp, options, file, entdicts, i);
15611665
}
15621666
}
15631667

common/entdata.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ void entdict_t::parse(parser_base_t &parser)
161161

162162
std::string keystr = parser.token;
163163

164+
if (keystr == "surfacefile" ||
165+
keystr == "menufile") {
166+
parser.ignore_escapes = true;
167+
}
168+
164169
/* parse value */
165170
if (!parser.parse_token())
166171
FError("EOF without closing brace");
@@ -177,6 +182,7 @@ void entdict_t::parse(parser_base_t &parser)
177182
}
178183

179184
set(keystr, parser.token);
185+
parser.ignore_escapes = false;
180186
}
181187
}
182188
void EntData_ParseInto(parser_t &parser, std::vector<entdict_t> &vector)

common/parser.cc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ bool parser_t::parse_token(parseflags flags)
184184
}
185185
break;
186186
default:
187-
logging::print("WARNING: {}: Unrecognised string escape - \\{}\n", location, pos[1]);
187+
if (!ignore_escapes) {
188+
logging::print("WARNING: {}: Unrecognised string escape - \\{}\n", location, pos[1]);
189+
}
188190
break;
189191
}
190192
}

include/bsputil/bsputil.hh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public:
8282
settings::setting_func findleaf;
8383
settings::setting_func settexinfo;
8484
settings::setting_func decompile;
85-
settings::setting_func sin_anims;
85+
settings::setting_string decompile_suffix;
8686
settings::setting_func decompile_geomonly;
8787
settings::setting_func decompile_ignore_brushes;
8888
settings::setting_func decompile_hull;

include/common/decompile.hh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ struct decomp_options
2626
bool ignoreBrushes = false;
2727

2828
int hullnum = 0;
29+
30+
std::string sin_srfName = "";
2931
};
3032

3133
void DecompileBSP(const mbsp_t *bsp, const decomp_options &options, std::ofstream &file);

include/common/parser.hh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ struct parser_base_t
121121
std::string token; // the last token parsed by parse_token
122122
bool was_quoted = false; // whether the current token was from a quoted string or not
123123
parser_source_location location; // parse location, if any
124+
bool ignore_escapes = false; // ignore invalid escape sequences
124125

125126
inline parser_base_t(parser_source_location base_location)
126127
: location(base_location)

include/common/polylib.hh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,13 @@ public:
12101210
return result;
12111211
}
12121212

1213+
void translateInPlace(const vec3_type &offset)
1214+
{
1215+
for (auto it = begin(); it != end(); it++) {
1216+
*it += offset;
1217+
}
1218+
}
1219+
12131220
winding_base_t translate(const vec3_type &offset) const
12141221
{
12151222
winding_base_t result = this->clone();

0 commit comments

Comments
 (0)