@@ -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
12661292static 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
15541659void 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
0 commit comments