]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/commitdiff
Merge branch 'master' into Mario/q3compat_sanity
authorMario <mario.mario@y7mail.com>
Wed, 5 Aug 2020 01:54:05 +0000 (11:54 +1000)
committerMario <mario.mario@y7mail.com>
Wed, 5 Aug 2020 01:54:05 +0000 (11:54 +1000)
qcsrc/common/mapinfo.qc
qcsrc/common/mapinfo.qh
qcsrc/dpdefs/upstream/menudefs.qc
qcsrc/menu/draw.qc
qcsrc/menu/draw.qh
qcsrc/menu/xonotic/dialog_multiplayer_create_mapinfo.qc
qcsrc/menu/xonotic/maplist.qc
qcsrc/server/world.qc
qcsrc/server/world.qh
xonotic-common.cfg

index b8071fab64b23e7469b47f8fa71b233643ead94e..9e0648db4393580a8e79a8dd6af2460f9d653a1f 100644 (file)
@@ -8,6 +8,9 @@
     #include <common/monsters/_mod.qh>
 #endif
 
+bool autocvar_g_mapinfo_arena_compat = true;
+bool autocvar_g_mapinfo_arena_generate = false;
+
 #ifdef MENUQC
 #define WARN_COND false
 #else
@@ -298,6 +301,25 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp
        mapMins = '0 0 0';
        mapMaxs = '0 0 0';
 
+       if(autocvar_g_mapinfo_arena_generate)
+       {
+               // try for .arena or .defi files, as they may have more accurate information
+               bool isdefi = false;
+               string arena_fn = _MapInfo_FindArenaFile(pFilename, ".arena");
+               int arena_fh = fopen(arena_fn, FILE_READ);
+               if(arena_fh < 0)
+               {
+                       isdefi = true;
+                       arena_fn = _MapInfo_FindArenaFile(pFilename, ".defi");
+                       arena_fh = fopen(arena_fn, FILE_READ);
+               }
+               if(arena_fh >= 0)
+               {
+                       _MapInfo_ParseArena(arena_fn, arena_fh, pFilename, NULL, isdefi, true);
+                       fclose(arena_fh);
+               }
+       }
+
        for (;;)
        {
                if (!((s = fgets(fh))))
@@ -591,6 +613,7 @@ void _MapInfo_Map_ApplyGametypeEx(string s, Gametype pWantedType, Gametype pThis
 Gametype MapInfo_Type_FromString(string gtype, bool dowarn)
 {
        string replacement = "";
+       bool do_warn = true;
        switch (gtype)
        {
                case "nexball":   replacement = "nb"; break;
@@ -599,6 +622,12 @@ Gametype MapInfo_Type_FromString(string gtype, bool dowarn)
                case "invasion":  replacement = "inv"; break;
                case "assault":   replacement = "as"; break;
                case "race":      replacement = "rc"; break;
+               // quake 3 compat
+               case "ffa":       replacement = "dm"; do_warn = false; break;
+               case "cctf":
+               case "oneflag":   replacement = "ctf"; do_warn = false; break;
+               case "team":      replacement = "tdm"; do_warn = false; break;
+               case "tourney":   replacement = "duel"; do_warn = false; break;
        }
        if (replacement != "")
        {
@@ -753,6 +782,192 @@ float MapInfo_isRedundant(string fn, string t)
        return false;
 }
 
+bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator)
+{
+       // NOTE: .arena files can hold more than 1 map's information!
+       // to handle this, we're going to store gathered information in local variables and save it if we encounter the correct map name
+       bool in_brackets = false; // testing a potential mapinfo section (within brackets)
+       bool dosave = (arena_filename == strcat("scripts/", pFilename, ((isdefi) ? ".defi" : ".arena"))); // if the map is using the fallback, just accept the first found mapinfo (it's probably correct!)
+       string stored_Map_description = "";
+       string stored_Map_title = "";
+       string stored_Map_author = "";
+       int stored_supportedGametypes = 0;
+       int stored_supportedFeatures = 0;
+       int stored_flags = 0;
+       string t, s;
+       for (;;)
+       {
+               if (!((s = fgets(fh))))
+                       break;
+
+               // catch different sorts of comments
+               if(s == "")                    // empty lines
+                       continue;
+               if(substring(s, 0, 1) == "#")  // UNIX style
+                       continue;
+               if(substring(s, 0, 2) == "//") // C++ style
+                       continue;
+               if(substring(s, 0, 1) == "_")  // q3map style
+                       continue;
+               if(strstrofs(s, "{", 0) >= 0)
+               {
+                       if(in_brackets)
+                               return false; // edge case? already in a bracketed section!
+                       in_brackets = true;
+                       continue;
+               }
+               else if(!in_brackets)
+               {
+                       // if we're not inside a bracket, don't process map info
+                       continue;
+               }
+               if(strstrofs(s, "}", 0) >= 0)
+               {
+                       if(!in_brackets)
+                               return false; // no starting bracket! let the mapinfo generation system handle it
+                       in_brackets = false;
+                       if(dosave)
+                       {
+                               MapInfo_Map_description = stored_Map_description;
+                               if(stored_Map_title != "")
+                                       MapInfo_Map_title = stored_Map_title;
+                               MapInfo_Map_author = stored_Map_author;
+                               if(isgenerator)
+                                       MapInfo_Map_supportedGametypes = stored_supportedGametypes;
+                               else
+                               {
+                                       FOREACH(Gametypes, it.m_flags & stored_supportedGametypes,
+                                       {
+                                               _MapInfo_Map_ApplyGametype ("", pGametypeToSet, it, true);
+                                       });
+                               }
+                               MapInfo_Map_supportedFeatures = stored_supportedFeatures;
+                               MapInfo_Map_flags = stored_flags;
+                               return true; // no need to continue through the file, we have our map!
+                       }
+                       else
+                       {
+                               // discard any gathered locals, we're not using the correct map!
+                               stored_Map_description = "";
+                               stored_Map_title = "";
+                               stored_Map_author = "";
+                               stored_supportedGametypes = 0;
+                               stored_supportedFeatures = 0;
+                               stored_flags = 0;
+                               continue;
+                       }
+               }
+
+               s = strreplace("\t", " ", s);
+
+               float p = strstrofs(s, "//", 0);
+               if(p >= 0)
+                       s = substring(s, 0, p);
+
+               // perform an initial trim to ensure the first argument is properly obtained
+               //   remove leading spaces
+               while(substring(s, 0, 1) == " ")
+                       s = substring(s, 1, -1);
+
+               t = car(s); s = cdr(s);
+               t = strtolower(t); // apparently some q3 maps use capitalized parameters
+
+               //   remove trailing spaces
+               while(substring(t, -1, 1) == " ")
+                       t = substring(t, 0, -2);
+
+               //   remove trailing spaces
+               while(substring(s, -1, 1) == " ")
+                       s = substring(s, 0, -2);
+               //   remove leading spaces
+               while(substring(s, 0, 1) == " ")
+                       s = substring(s, 1, -1);
+               // limited support of ""
+               //   remove trailing and leading " of s
+               if(substring(s, 0, 1) == "\"")
+               {
+                       if(substring(s, -1, 1) == "\"")
+                               s = substring(s, 1, -2);
+               }
+               if(t == "longname")
+                       stored_Map_title = s;
+               else if(t == "author")
+                       stored_Map_author = s;
+               else if(t == "type")
+               {
+                       // if there is a valid gametype in this .arena file, include it in the menu
+                       stored_supportedFeatures |= MAPINFO_FEATURE_WEAPONS;
+                       // type in quake 3 holds all the supported gametypes, so we must loop through all of them
+                       FOREACH_WORD(s, true,
+                       {
+                               Gametype f = MapInfo_Type_FromString(it, false);
+                               if(f)
+                                       stored_supportedGametypes |= f.m_flags;
+                       });
+               }
+               else if(t == "style" && isdefi)
+               {
+                       // we have a defrag map on our hands, add CTS!
+                       // TODO: styles
+                       stored_supportedGametypes |= MAPINFO_TYPE_CTS.m_flags;
+               }
+               else if(t == "map")
+               {
+                       if(strtolower(s) == strtolower(pFilename))
+                               dosave = true; // yay, found our map!
+               }
+               else if(t == "quote")
+                       stored_Map_description = s;
+               // TODO: fraglimit
+       }
+
+       // if the map wasn't found in the .arena, fall back to generated .mapinfo
+       return false;
+}
+
+#if defined(CSQC) || defined(MENUQC)
+string(string filename) whichpack = #503;
+#endif
+string _MapInfo_FindArenaFile(string pFilename, string extension)
+{
+       string fallback = strcat("scripts/", pFilename, extension);
+       if(!checkextension("DP_QC_FS_SEARCH_PACKFILE"))
+               return fallback;
+       string base_pack = whichpack(strcat("maps/", pFilename, ".bsp"));
+       if(base_pack == "") // this map isn't packaged!
+               return fallback;
+
+       int glob = search_packfile_begin(strcat("scripts/*", extension), true, true, base_pack);
+       if(glob < 0)
+               return fallback;
+       int n = search_getsize(glob);
+       for(int j = 0; j < n; ++j)
+       {
+               string file = search_getfilename(glob, j);
+
+               int fh = fopen(file, FILE_READ);
+               if(fh < 0)
+                       continue; // how?
+               for(string s; (s = fgets(fh)); )
+               {
+                       int offset = strstrofs(s, "map", 0);
+                       if(offset >= 0)
+                       {
+                               if(strstrofs(strtolower(s), strcat("\"", strtolower(pFilename), "\""), offset) >= 0) // quake 3 is case insensitive
+                               {
+                                       fclose(fh);
+                                       search_end(glob);
+                                       return file; // FOUND IT!
+                               }
+                       }
+               }
+               fclose(fh);
+       }
+
+       search_end(glob);
+       return fallback; // if we get here, a valid .arena file could not be found
+}
+
 // load info about a map by name into the MapInfo_Map_* globals
 float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gametype pGametypeToSet)
 {
@@ -760,7 +975,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
        string s, t;
        float fh;
        int f, i;
-       float r, n, p;
+       float r, n;
        string acl;
 
        acl = MAPINFO_SETTEMP_ACL_USER;
@@ -784,6 +999,26 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
        fh = fopen(fn, FILE_READ);
        if(fh < 0)
        {
+               if(autocvar_g_mapinfo_arena_compat)
+               {
+                       // try for .arena or .defi files if no .mapinfo exists
+                       bool isdefi = false;
+                       fn = _MapInfo_FindArenaFile(pFilename, ".arena");
+                       fh = fopen(fn, FILE_READ);
+                       if(fh < 0)
+                       {
+                               isdefi = true;
+                               fn = _MapInfo_FindArenaFile(pFilename, ".defi");
+                               fh = fopen(fn, FILE_READ);
+                       }
+                       if(fh >= 0)
+                       {
+                               _MapInfo_Map_Reset();
+                               if(_MapInfo_ParseArena(fn, fh, pFilename, pGametypeToSet, isdefi, false))
+                                       goto mapinfo_handled; // skip generation
+                       }
+               }
+
                fn = strcat("maps/autogenerated/", pFilename, ".mapinfo");
                fh = fopen(fn, FILE_READ);
                if(fh < 0)
@@ -874,7 +1109,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
                if(substring(s, 0, 1) == "_")  // q3map style
                        continue;
 
-               p = strstrofs(s, "//", 0);
+               float p = strstrofs(s, "//", 0);
                if(p >= 0)
                        s = substring(s, 0, p);
 
@@ -1046,6 +1281,7 @@ float MapInfo_Get_ByName_NoFallbacks(string pFilename, int pAllowGenerate, Gamet
                else if(WARN_COND)
                        LOG_WARN("Map ", pFilename, " provides unknown info item ", t, ", ignored");
        }
+       LABEL(mapinfo_handled)
        fclose(fh);
 
        if(MapInfo_Map_title == "<TITLE>")
index c9d6c5e722449773a850188a4f02d9d4e7276d07..18044b1b45e5e45a69a13b9ea797e63062f687f8 100644 (file)
@@ -189,6 +189,10 @@ void MapInfo_Cache_Destroy(); // disable caching
 void MapInfo_Cache_Create(); // enable caching
 void MapInfo_Cache_Invalidate(); // delete cache if any, but keep enabled
 
+bool _MapInfo_ParseArena(string arena_filename, int fh, string pFilename, Gametype pGametypeToSet, bool isdefi, bool isgenerator);
+
+string _MapInfo_FindArenaFile(string pFilename, string extension);
+
 void _MapInfo_Parse_Settemp(string pFilename, string acl, float type, string s, float recurse);
 
 void MapInfo_ClearTemps(); // call this when done with mapinfo for this frame
index 36cedec4f2fdfd1b96e935f1f5f91b84b230fd53..63d2c6388e8447e90e46445abbe65506e93e074b 100644 (file)
@@ -159,7 +159,7 @@ float OS_MAC                = 2;
 //////////////////////////////////////////////////
 // AK FIXME: Create perhaps a special builtin file for the common cmds
 
-void   checkextension(string ext)      = #1;
+float  checkextension(string ext)      = #1;
 
 // error cmds
 void   error(string err,...)           = #2;
@@ -542,7 +542,7 @@ void coverage() = #642;  // Reports a coverage event. The engine counts for each
 //idea: Mario
 //darkplaces implementation: Mario
 //builtin definitions:
-float(string pattern, float caseinsensitive, float quiet, string packfile) search_packfile_begin = #444;
+float(string pattern, float caseinsensitive, float quiet, string packfile) search_packfile_begin = #74;
 //description:
 //extension to search_begin (DP_QC_FS_SEARCH), performs a filename search with the specified pattern (for example "maps/*.bsp") and stores the results in a search slot (minimum of 128 supported by any engine with this extension), the other functions take this returned search slot number, be sure to search_free when done (they are also freed on progs reload).
 //only searches for files within the specified packfile, which is expected to match the results of whichpack().
index ae6967e367565fc02c38f08f57c77ac0ccca706d..0bd4e29fe02c435de2d0c429bf9d82db0f877ae0 100644 (file)
@@ -83,6 +83,17 @@ vector draw_PictureSize(string pic)
        return drawgetimagesize(pic);
 }
 
+bool draw_PictureExists(string pic)
+{
+       pic = draw_UseSkinFor(pic);
+       if (fexists(strcat(pic, ".tga"))) return true;
+       if (fexists(strcat(pic, ".png"))) return true;
+       if (fexists(strcat(pic, ".jpg"))) return true;
+       if (fexists(strcat(pic, ".pcx"))) return true;
+
+       return false;
+}
+
 void draw_Fill(vector theOrigin, vector theSize, vector theColor, float theAlpha)
 {
        drawfill(boxToGlobal(theOrigin, draw_shift, draw_scale), boxToGlobalSize(theSize, draw_scale), theColor, theAlpha * draw_alpha, 0);
index 69178cb03eedb522c29a0597b22c9f09303133cb..611281913d8d8ed209123a8ae4ec4813f8d5cf81 100644 (file)
@@ -21,6 +21,7 @@ void draw_VertButtonPicture(vector theOrigin, string pic, vector theSize, vector
 void draw_BorderPicture(vector theOrigin, string pic, vector theSize, vector theColor, float theAlpha, vector theBorderSize);
 void draw_Picture(vector origin, string pic, vector size, vector color, float alpha);
 vector draw_PictureSize(string pic);
+bool draw_PictureExists(string pic);
 void draw_Fill(vector theOrigin, vector theSize, vector theColor, float theAlpha);
 void draw_Text(vector origin, string text, vector size, vector color, float alpha, float allowColorCodes);
 void draw_CenterText(vector origin, string text, vector size, vector color, float alpha, float allowColorCodes);
@@ -40,3 +41,4 @@ vector globalToBoxSize(vector v, vector scale);
 
 float draw_TextWidth_WithColors(string s, vector size);
 float draw_TextWidth_WithoutColors(string s, vector size);
+
index 87ffadf3831d8015641eb7b3b9e50e7d8e95ecc7..57c2c61e6908ed5a8d4175cf23634bc459eee629 100644 (file)
@@ -17,12 +17,14 @@ void XonoticMapInfoDialog_loadMapInfo(entity me, int i, entity mlb)
        strcpy(me.currentMapAuthor, strdecolorize(MapInfo_Map_author));
        strcpy(me.currentMapDescription, MapInfo_Map_description);
        strcpy(me.currentMapPreviewImage, strcat("/maps/", MapInfo_Map_bspname));
+       if(!draw_PictureExists(me.currentMapPreviewImage)) // Quake 3 compatibility
+               strcpy(me.currentMapPreviewImage, strcat("/levelshots/", MapInfo_Map_bspname));
 
        me.frame.setText(me.frame, me.currentMapBSPName);
        me.titleLabel.setText(me.titleLabel, me.currentMapTitle);
        me.authorLabel.setText(me.authorLabel, me.currentMapAuthor);
        me.descriptionLabel.setText(me.descriptionLabel, me.currentMapDescription);
-       if(draw_PictureSize(me.currentMapPreviewImage) == '0 0 0')
+       if(!draw_PictureExists(me.currentMapPreviewImage))
                me.previewImage.src = "nopreview_map";
        else
                me.previewImage.src = me.currentMapPreviewImage;
index a18037db63178830a7e85d675a66564cf01a7a47..924924f01dcc72ccccfda2ca3bf2192f88d05018 100644 (file)
@@ -156,7 +156,12 @@ void XonoticMapList_drawListBoxItem(entity me, int i, vector absSize, bool isSel
        }
 
        if(draw_PictureSize(strcat("/maps/", MapInfo_Map_bspname)) == '0 0 0')
-               draw_Picture(me.columnPreviewOrigin * eX, "nopreview_map", me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+       {
+               if(!draw_PictureExists(strcat("/levelshots/", MapInfo_Map_bspname)))
+                       draw_Picture(me.columnPreviewOrigin * eX, "nopreview_map", me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+               else
+                       draw_Picture(me.columnPreviewOrigin * eX, strcat("/levelshots/", MapInfo_Map_bspname), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
+       }
        else
                draw_Picture(me.columnPreviewOrigin * eX, strcat("/maps/", MapInfo_Map_bspname), me.columnPreviewSize * eX + eY, '1 1 1', theAlpha);
 
index c37998545ff8c7dabd7d87152d06dbd2ef313195..b54dde7a8cd817df355df79460d52631fe3063d9 100644 (file)
@@ -887,12 +887,34 @@ spawnfunc(worldspawn)
        MapInfo_Enumerate();
        MapInfo_FilterGametype(MapInfo_CurrentGametype(), MapInfo_CurrentFeatures(), MapInfo_RequiredFlags(), MapInfo_ForbiddenFlags(), 1);
 
-       if(fexists(strcat("scripts/", mapname, ".arena")))
+       if(fexists(_MapInfo_FindArenaFile(mapname, ".arena")))
                cvar_settemp("sv_q3acompat_machineshotgunswap", "1");
 
-       if(fexists(strcat("scripts/", mapname, ".defi")))
+       if(fexists(_MapInfo_FindArenaFile(mapname, ".defi")))
                cvar_settemp("sv_q3defragcompat", "1");
 
+       // quake 3 music support
+       if(world.music || world.noise)
+       {
+               // prefer .music over .noise
+               string chosen_music;
+               string oldstuff;
+               if(world.music)
+                       chosen_music = world.music;
+               else
+                       chosen_music = world.noise;
+               if(
+                       substring(chosen_music, strlen(chosen_music) - 4, 4) == ".wav"
+                       ||
+                       substring(chosen_music, strlen(chosen_music) - 4, 4) == ".ogg"
+               )
+                       oldstuff = strcat(clientstuff, "cd loop \"", chosen_music, "\"\n");
+               else
+                       oldstuff = strcat(clientstuff, "cd loop \"", chosen_music, "\"\n");
+
+               strcpy(clientstuff, oldstuff);
+       }
+
        if(whichpack(strcat("maps/", mapname, ".cfg")) != "")
        {
                int fd = fopen(strcat("maps/", mapname, ".cfg"), FILE_READ);
index 3bbaad68246e942d4288f379d326686a6cb970ae..2894299d619beee64fd0dbfa9b9510c4dacea443 100644 (file)
@@ -61,5 +61,9 @@ float DoNextMapOverride(float reinit);
 void CheckRules_World();
 float RedirectionThink();
 
+// quake 3 music compatibility
+.string music;
+.string noise;
+
 IntrusiveList g_moveables;
 STATIC_INIT(g_moveables) { g_moveables = IL_NEW(); }
index 88958889af0a9b52f1624b59eb14359db12f69f7..fbdf838ee76f2b0910c963b88fea561bccdd077e 100644 (file)
@@ -150,6 +150,9 @@ set debug_deglobalization_clear 0 "make the new wrappers set globals to NaN afte
 // disabling until it's complete
 set prvm_garbagecollection_enable 0
 
+set g_mapinfo_arena_compat 1 "allow mapinfo data to be pulled directly from .arena and .defi files if they exist, rather than generating .mapinfo files for them"
+set g_mapinfo_arena_generate 0 "allow mapinfo data to be pulled from .arena and .defi files during generation"
+
 // load console command aliases and settings
 exec commands.cfg