diff --git a/source/core/cheats.cpp b/source/core/cheats.cpp index 13a913fdd..b1db30714 100644 --- a/source/core/cheats.cpp +++ b/source/core/cheats.cpp @@ -414,6 +414,28 @@ CCMD(changemap) // //--------------------------------------------------------------------------- +void DoStartMap(FString mapname) +{ + FString mapfilename = mapname; + DefaultExtension(mapfilename, ".map"); + + // Check if the map is already defined. + auto map = FindMapByName(mapname); + if (map == nullptr) + { + map = SetupUserMap(mapfilename, g_gameType & GAMEFLAG_DUKE ? "dethtoll.mid" : nullptr); + } + if (map) + { + if (fileSystem.FindFile(map->fileName) < 0) + { + Printf(PRINT_BOLD, "%s: map file not found\n", map->fileName.GetChars()); + } + + DeferredStartGame(map, g_nextskill); + } +} + CCMD(map) { if (argv.argc() < 2) @@ -429,24 +451,6 @@ CCMD(map) } FString mapname = argv[1]; - FString mapfilename = mapname; - DefaultExtension(mapfilename, ".map"); - - // Check if the map is already defined. - auto map = FindMapByName(mapname); - if (map == nullptr) - { - map = SetupUserMap(mapfilename, g_gameType & GAMEFLAG_DUKE? "dethtoll.mid" : nullptr); - } - if (map) - { - if (fileSystem.FindFile(map->fileName) < 0) - { - Printf(PRINT_BOLD, "%s: map file not found\n", map->fileName.GetChars()); - } - - DeferredStartGame(map, g_nextskill); - } } //--------------------------------------------------------------------------- diff --git a/source/core/menu/usermap.cpp b/source/core/menu/usermap.cpp index 3c5847ced..c64998b86 100644 --- a/source/core/menu/usermap.cpp +++ b/source/core/menu/usermap.cpp @@ -46,8 +46,9 @@ #include "v_draw.h" #include "usermap.h" #include "gamecontrol.h" +#include "mapinfo.h" -static UsermapDirectory root; +static FUsermapDirectory root; void InsertMap(int lumpnum) { @@ -57,11 +58,11 @@ void InsertMap(int lumpnum) auto current = &root; for (unsigned i = 0; i < path.Size() - 1; i++) { - unsigned place = current->subdirectories.FindEx([=](const UsermapDirectory& entry) { return entry.name.CompareNoCase(path[i]) == 0; }); + unsigned place = current->subdirectories.FindEx([=](const FUsermapDirectory& entry) { return entry.dirname.CompareNoCase(path[i]) == 0; }); if (place == current->subdirectories.Size()) { place = current->subdirectories.Reserve(1); - current->subdirectories.Last().name = path[i]; + current->subdirectories.Last().dirname = path[i]; } current = ¤t->subdirectories[place]; } @@ -70,6 +71,8 @@ void InsertMap(int lumpnum) current->entries.Last().filename = fileSystem.GetFileFullName(lumpnum); current->entries.Last().container = fileSystem.GetResourceFileName(fileSystem.GetFileContainer(lumpnum)); current->entries.Last().size = fileSystem.FileLength(lumpnum); + auto mapinfo = FindMapByName(StripExtension(path.Last())); + if (mapinfo) current->entries.Last().info = mapinfo->name; } bool ValidateMap(int lumpnum) @@ -91,20 +94,20 @@ bool ValidateMap(int lumpnum) return true; } -void SortEntries(UsermapDirectory& dir) +void SortEntries(FUsermapDirectory& dir) { qsort(dir.subdirectories.Data(), dir.subdirectories.Size(), sizeof(dir.subdirectories[0]), [](const void* a, const void* b) -> int { - auto A = (UsermapDirectory*)a; - auto B = (UsermapDirectory*)b; + auto A = (FUsermapDirectory*)a; + auto B = (FUsermapDirectory*)b; - return A->name.CompareNoCase(B->name); + return A->dirname.CompareNoCase(B->dirname); }); qsort(dir.entries.Data(), dir.entries.Size(), sizeof(dir.entries[0]), [](const void* a, const void* b) -> int { - auto A = (UsermapEntry*)a; - auto B = (UsermapEntry*)b; + auto A = (FUsermapEntry*)a; + auto B = (FUsermapEntry*)b; return A->displayname.CompareNoCase(B->displayname); }); @@ -118,6 +121,10 @@ void SortEntries(UsermapDirectory& dir) void ReadUserMaps() { + static bool didit = false; + if (didit) return; + didit = true; + int numfiles = fileSystem.GetNumEntries(); for (int i = 0; i < numfiles; i++) @@ -132,7 +139,79 @@ void ReadUserMaps() SortEntries(root); } -CCMD(readusermaps) +DEFINE_FIELD(FUsermapEntry, filename); +DEFINE_FIELD(FUsermapEntry, container); +DEFINE_FIELD(FUsermapEntry, displayname); +DEFINE_FIELD(FUsermapEntry, info); +DEFINE_FIELD(FUsermapEntry, size); + +DEFINE_FIELD(FUsermapDirectory, dirname); +DEFINE_FIELD(FUsermapDirectory, parent); + + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, ReadData) { ReadUserMaps(); -} \ No newline at end of file + ACTION_RETURN_POINTER(&root); +} + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, GetNumEntries) +{ + PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); + ACTION_RETURN_INT(self->entries.Size()); +} + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, GetNumDirectories) +{ + PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); + ACTION_RETURN_INT(self->subdirectories.Size()); +} + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, GetEntry) +{ + PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); + + PARAM_UINT(num); + if (num >= self->entries.Size()) ACTION_RETURN_POINTER(nullptr); + ACTION_RETURN_POINTER(&self->entries[num]); +} + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, GetDirectory) +{ + PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); + + PARAM_UINT(num); + if (num >= self->subdirectories.Size()) ACTION_RETURN_POINTER(nullptr); + ACTION_RETURN_POINTER(&self->subdirectories[num]); +} + +DEFINE_ACTION_FUNCTION(FUsermapDirectory, DrawPreview) +{ + PARAM_SELF_STRUCT_PROLOGUE(FUsermapDirectory); + + PARAM_UINT(num); + PARAM_INT(left); + PARAM_INT(top); + PARAM_INT(width); + PARAM_INT(height); + if (num >= self->entries.Size()) return 0; + // todo + return 0; +} + +void DoStartMap(FString mapname); + +DEFINE_ACTION_FUNCTION(_UsermapMenu, StartMap) +{ + PARAM_PROLOGUE; + PARAM_POINTER(entry, FUsermapEntry); + + if (DMenu::InMenu == 0) + { + ThrowAbortException(X_OTHER, "Attempt to start user map outside of menu code"); + } + + DoStartMap(entry->filename); + M_ClearMenus(); + return 0; +} diff --git a/source/core/menu/usermap.h b/source/core/menu/usermap.h index 350666469..2adcd6058 100644 --- a/source/core/menu/usermap.h +++ b/source/core/menu/usermap.h @@ -1,19 +1,20 @@ #pragma once -struct UsermapEntry +struct FUsermapEntry { FString displayname; - const char* filename; - const char* container; + FString container; + FString filename; + FString info; int size; }; -struct UsermapDirectory +struct FUsermapDirectory { - FString name; - UsermapDirectory* parent = nullptr; - TArray subdirectories; - TArray entries; + FString dirname; + FUsermapDirectory* parent = nullptr; + TArray subdirectories; + TArray entries; }; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 54cdb5677..a82407fa6 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -406,6 +406,19 @@ ListMenu "SaveGameMenu" CaptionItem "$MNU_SAVEGAME" } +//------------------------------------------------------------------------------------------- +// +// Base definition for user map menu. Only the configurable part is done here +// +//------------------------------------------------------------------------------------------- + +ListMenu "UsermapMenu" +{ + Position 0, 40 + Class "UsermapMenu" // uses its own implementation + CaptionItem "$MNU_USERMAP" +} + //------------------------------------------------------------------------------------------- // // The generic options menus start here diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index f207778ed..5baf89663 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -31,6 +31,7 @@ version "4.3" #include "zscript/razebase.zs" #include "zscript/summary.zs" #include "zscript/statusbar.zs" +#include "zscript/usermapmenu.zs" #include "zscript/games/duke/dukegame.zs" #include "zscript/games/duke/ui/sbar.zs" #include "zscript/games/duke/ui/sbar_d.zs" diff --git a/wadsrc/static/zscript/usermapmenu.zs b/wadsrc/static/zscript/usermapmenu.zs new file mode 100644 index 000000000..92ec194d6 --- /dev/null +++ b/wadsrc/static/zscript/usermapmenu.zs @@ -0,0 +1,464 @@ +/* +** usermap browser +** Adapted from the loadsavemenu code. +** +**--------------------------------------------------------------------------- +** Copyright 2001-2010 Randy Heit +** Copyright 2010-2021 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + + +struct UsermapEntry native +{ + native readonly String displayname; + native readonly String container; + native readonly String filename; + native readonly String info; + native readonly int size; +} + +struct UsermapDirectory native +{ + native readonly String dirname; + native readonly UsermapDirectory parent; + + native static UsermapDirectory ReadData(); + native int GetNumEntries(); + native int GetNumDirectories(); + native UsermapEntry GetEntry(int num); + native UsermapDirectory GetDirectory(int num); + native void DrawPreview(int num, int left, int top, int width, int height); + String GetInfo(int Selected) + { + if (parent) Selected--; + Selected -= GetNumDirectories(); + let entry = GetEntry(Selected); + if (!entry) return ""; + if (entry.info.Length() > 0) return String.Format("Map %s: %s\n%s", entry.filename, entry.info, entry.container); + return String.Format("Map %s\n%s", entry.filename, entry.container); + } + void Select(int Selected) + { + } +} + + +class UsermapMenu : ListMenu +{ + UsermapDirectory currentDir; + int TopItem; + int Selected; + int NumTotalEntries; + + int previewLeft; + int previewTop; + int previewWidth; + int previewHeight; + int rowHeight; + int listboxLeft; + int listboxTop; + int listboxWidth; + + int listboxRows; + int listboxHeight; + int listboxRight; + + int commentLeft; + int commentTop; + int commentWidth; + int commentHeight; + int commentRows; + + bool mEntering; + double FontScale; + + int numparent, numdirs, numentries; + + BrokenLines BrokenComment; + Array selects; + + // private to this menu to prevent exploits. + private native static void StartMap(UsermapEntry entry); + + + //============================================================================= + // + // + // + //============================================================================= + + override void Init(Menu parent, ListMenuDescriptor desc) + { + Super.Init(parent, desc); + currentDir = UsermapDirectory.ReadData(); + SetSize(); + SetWindows(); + } + + private void SetWindows() + { + bool aspect43 = true; + int Width43 = screen.GetHeight() * 4 / 3; + int Left43 = (screen.GetWidth() - Width43) / 2; + + double wScale = Width43 / 640.; + + previewLeft = Left43 + int(20 * wScale); + previewTop = int(mDesc.mYpos * screen.GetHeight() / 200); + previewWidth = int(260 * wScale); + previewHeight = int(260 * wScale); + + FontScale = max(screen.GetHeight() / 480, 1); + rowHeight = int(max((NewConsoleFont.GetHeight() + 1) * FontScale, 1)); + + listboxLeft = previewLeft + previewWidth + int(20*wScale); + listboxTop = previewTop; + listboxWidth = Width43 + Left43 - listboxLeft - int(30 * wScale); + int listboxHeight1 = screen.GetHeight() - listboxTop - int(20*wScale); + listboxRows = (listboxHeight1 - 1) / rowHeight; + listboxHeight = listboxRows * rowHeight + 1; + listboxRight = listboxLeft + listboxWidth; + + commentLeft = previewLeft; + commentTop = previewTop + previewHeight + int(16 * wScale); + commentWidth = previewWidth; + commentHeight = listboxHeight - previewHeight - int(16 * wScale); + commentRows = commentHeight / rowHeight; + } + + + //============================================================================= + // + // + // + //============================================================================= + + virtual void DrawFrame(int left, int top, int width, int height) + { + let framecolor = Color(255, 80, 80, 80); + Screen.DrawLineFrame(framecolor, left, top, width, height, screen.GetHeight() / 240); + } + + void SetSize() + { + numparent = (currentDir.parent != null); + numdirs = currentDir.GetNumDirectories(); + numentries = currentDir.GetNumEntries(); + NumTotalEntries = numparent + numdirs + numentries; + Console.Printf("Selects = %d", selects.Size()); + } + + override void Drawer () + { + Super.Drawer(); + + int i; + int j; + + SetWindows(); + DrawFrame(previewLeft, previewTop, previewWidth, previewHeight); + screen.Dim(0, 0.6, previewLeft, previewTop, previewWidth, previewHeight); + currentDir.DrawPreview(Selected, previewLeft, previewTop, previewWidth, previewHeight); + + // Draw comment area + DrawFrame (commentLeft, commentTop, commentWidth, commentHeight); + screen.Dim(0, 0.6, commentLeft, commentTop, commentWidth, commentHeight); + + int numlinestoprint = min(commentRows, BrokenComment? BrokenComment.Count() : 0); + for(int i = 0; i < numlinestoprint; i++) + { + screen.DrawText(NewConsoleFont, Font.CR_ORANGE, commentLeft / FontScale, (commentTop + rowHeight * i) / FontScale, BrokenComment.StringAt(i), + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + + + // Draw file area + DrawFrame (listboxLeft, listboxTop, listboxWidth, listboxHeight); + screen.Dim(0, 0.6, listboxLeft, listboxTop, listboxWidth, listboxHeight); + + /* + if (NumTotalEntries == 0) + { + String text = Stringtable.Localize("$MNU_NOFILES"); + int textlen = int(NewConsoleFont.StringWidth(text) * FontScale); + + screen.DrawText (NewConsoleFont, Font.CR_GOLD, (listboxLeft+(listboxWidth-textlen)/2) / FontScale, (listboxTop+(listboxHeight-rowHeight)/2) / FontScale, text, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + return; + } + */ + + j = TopItem; + for (i = 0; i < listboxRows && j < NumTotalEntries; i++) + { + int colr; + String texttoprint; + + if (j < numparent) + { + colr = Font.CR_YELLOW; + texttoprint = "[..]"; + } + else if (j < numparent + numdirs) + { + colr = Font.CR_YELLOW; + let dir = currentDir.GetDirectory(j - numparent); + if (!dir) texttoprint = String.Format("Error reading directory %d", j - numparent); + else texttoprint = String.Format("[%s]", dir.dirName); + } + else + { + if (j == Selected) + { + colr = Font.CR_WHITE; + } + else + { + colr = Font.CR_TAN; + } + let dir = currentDir.GetEntry(j - numparent - numdirs); + if (!dir) texttoprint = String.Format("Error reading entry %d", j - numparent - numdirs); + else texttoprint = String.Format("%s", dir.displayName); + } + + screen.SetClipRect(listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1)); + + if (j == Selected) + { + screen.Clear (listboxLeft, listboxTop+rowHeight*i, listboxRight, listboxTop+rowHeight*(i+1), Color(255,0,0,255)); + screen.DrawText (NewConsoleFont, colr, (listboxLeft+1) / FontScale, (listboxTop+rowHeight*i + FontScale) / FontScale, texttoprint, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + else + { + screen.DrawText (NewConsoleFont, colr, (listboxLeft+1) / FontScale, (listboxTop+rowHeight*i + FontScale) / FontScale, texttoprint, + DTA_VirtualWidthF, screen.GetWidth() / FontScale, DTA_VirtualHeightF, screen.GetHeight() / FontScale, DTA_KeepRatio, true); + } + screen.ClearClipRect(); + j++; + } + } + + void UpdateComment() + { + BrokenComment = NewConsoleFont.BreakLines(currentDir.GetInfo(Selected), int(commentWidth / FontScale)); + } + + void GoToParent() + { + if (selects.Size() > 0) + { + Selected = selects[selects.Size()-1]; + selects.Pop(); + TopItem = selects[selects.Size()-1]; + selects.Pop(); + currentDir = currentDir.parent; + currentDir.Select(Selected); + SetSize(); + UpdateComment(); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent (int mkey, bool fromcontroller) + { + switch (mkey) + { + case MKEY_Up: + if (NumTotalEntries > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (--Selected < 0) Selected = NumTotalEntries-1; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = MAX(0, Selected - listboxRows + 1); + } + currentDir.Select(Selected); + UpdateComment(); + } + return true; + + case MKEY_Down: + if (NumTotalEntries > 1) + { + if (Selected == -1) Selected = TopItem; + else + { + if (++Selected >= NumTotalEntries) Selected = 0; + if (Selected < TopItem) TopItem = Selected; + else if (Selected >= TopItem + listboxRows) TopItem = MAX(0, Selected - listboxRows + 1); + } + currentDir.Select(Selected); + UpdateComment(); + } + return true; + + case MKEY_PageDown: + if (NumTotalEntries > 1) + { + if (TopItem >= NumTotalEntries - listboxRows) + { + TopItem = 0; + if (Selected != -1) Selected = 0; + } + else + { + TopItem = MIN(TopItem + listboxRows, NumTotalEntries - listboxRows); + if (TopItem > Selected && Selected != -1) Selected = TopItem; + } + currentDir.Select(Selected); + UpdateComment(); + } + return true; + + case MKEY_PageUp: + if (NumTotalEntries > 1) + { + if (TopItem == 0) + { + TopItem = MAX(0, NumTotalEntries - listboxRows); + if (Selected != -1) Selected = TopItem; + } + else + { + TopItem = MAX(TopItem - listboxRows, 0); + if (Selected >= TopItem + listboxRows) Selected = TopItem; + } + currentDir.Select(Selected); + UpdateComment(); + } + return true; + + case MKEY_Enter: + if (Selected < numparent) + { + GoToParent(); + } + else if (Selected < numparent + numdirs) + { + currentDir = currentDir.GetDirectory(Selected - numparent); + selects.Push(TopItem); + selects.Push(Selected); + Selected = 1; + currentDir.Select(Selected); + SetSize(); + UpdateComment(); + } + else + { + let entry = currentDir.GetEntry(Selected - numparent - numdirs); + StartMap(entry); + } + return true; + + case MKEY_Back: + if (selects.Size() > 0) + { + GoToParent(); + return true; + } + + default: + return Super.MenuEvent(mkey, fromcontroller); + } + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + if (x >= listboxLeft && x < listboxLeft + listboxWidth && + y >= listboxTop && y < listboxTop + listboxHeight) + { + int lineno = (y - listboxTop) / rowHeight; + + if (TopItem + lineno < NumTotalEntries) + { + Selected = TopItem + lineno; + currentDir.Select(Selected); + UpdateComment(); + if (type == MOUSE_Release) + { + if (MenuEvent(MKEY_Enter, true)) + { + return true; + } + } + } + else Selected = -1; + } + else Selected = -1; + + return Super.MouseEvent(type, x, y); + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool OnUIEvent(UIEvent ev) + { + if (ev.Type == UIEvent.Type_KeyDown) + { + switch (ev.KeyChar) + { + case UIEvent.Key_Backspace: + GoToParent(); + return true; + } + } + else if (ev.Type == UIEvent.Type_WheelUp) + { + if (TopItem > 0) TopItem--; + return true; + } + else if (ev.Type == UIEvent.Type_WheelDown) + { + if (TopItem < NumTotalEntries - listboxRows) TopItem++; + return true; + } + return Super.OnUIEvent(ev); + } + + +} +