diff --git a/specs/usdf_zdoom.txt b/specs/usdf_zdoom.txt index f800b5ae7..cd4393351 100644 --- a/specs/usdf_zdoom.txt +++ b/specs/usdf_zdoom.txt @@ -38,6 +38,7 @@ conversation page { drop = ; + userstring = ; New field which can be used to pass data to custom conversation menu classes. ifitem { item = ; @@ -63,10 +64,6 @@ either refuse loading dialogues with the 'ZDoom' namespace or if it does not outright abort on incompatible namespaces fail with a type mismatch error on one of the specified propeties. -In addition ZDoom defines one new field in the top level of a conversation block: - -id = ; Assigns a conversation ID for use in Thing_SetConversation or in UDMF's 'conversation' actor property. - ZDoom-format dialogues need to start with the line: namespace = "ZDoom"; @@ -86,6 +83,7 @@ conversation // Starts a dialog. // the standard conversation ID ('actor' property) is used instead // for this purpose but since 'ZDoom' namespace requires the actor // to be a class name it needs a separate field for this. + class = ; //Override the default conversation menu class for this conversation. page { diff --git a/src/g_level.cpp b/src/g_level.cpp index 333d133cf..3df7a78a8 100644 --- a/src/g_level.cpp +++ b/src/g_level.cpp @@ -1937,6 +1937,7 @@ DEFINE_FIELD_BIT(FLevelLocals, flags2, polygrind, LEVEL2_POLYGRIND) DEFINE_FIELD_BIT(FLevelLocals, flags2, nomonsters, LEVEL2_NOMONSTERS) DEFINE_FIELD_BIT(FLevelLocals, flags2, frozen, LEVEL2_FROZEN) DEFINE_FIELD_BIT(FLevelLocals, flags2, infinite_flight, LEVEL2_INFINITE_FLIGHT) +DEFINE_FIELD_BIT(FLevelLocals, flags2, no_dlg_freeze, LEVEL2_CONV_SINGLE_UNFREEZE) //========================================================================== // diff --git a/src/gi.cpp b/src/gi.cpp index 247036ea7..18fd39f69 100644 --- a/src/gi.cpp +++ b/src/gi.cpp @@ -52,6 +52,7 @@ DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, ArmorIcon2) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, gametype) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, norandomplayerclass) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, infoPages) +DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, mBackButton) const char *GameNames[17] = @@ -362,6 +363,7 @@ void FMapInfoParser::ParseGameInfo() GAMEINFOKEY_INT(TextScreenX, "textscreenx") GAMEINFOKEY_INT(TextScreenY, "textscreeny") GAMEINFOKEY_STRING(DefaultEndSequence, "defaultendsequence") + GAMEINFOKEY_STRING(DefaultConversationMenuClass, "defaultconversationmenuclass") GAMEINFOKEY_FONT(mStatscreenMapNameFont, "statscreen_mapnamefont") GAMEINFOKEY_FONT(mStatscreenFinishedFont, "statscreen_finishedfont") GAMEINFOKEY_FONT(mStatscreenEnteringFont, "statscreen_enteringfont") diff --git a/src/gi.h b/src/gi.h index 1bab4e9a7..24053b002 100644 --- a/src/gi.h +++ b/src/gi.h @@ -172,6 +172,7 @@ struct gameinfo_t double gibfactor; int TextScreenX; int TextScreenY; + FName DefaultConversationMenuClass; FName DefaultEndSequence; FString mMapArrow, mCheatMapArrow; FString mEasyKey, mCheatKey; diff --git a/src/menu/menu.cpp b/src/menu/menu.cpp index 6569c00c7..fe5af6268 100644 --- a/src/menu/menu.cpp +++ b/src/menu/menu.cpp @@ -160,56 +160,15 @@ DMenu::DMenu(DMenu *parent) mParentMenu = parent; mMouseCapture = false; mBackbuttonSelected = false; + DontDim = false; GC::WriteBarrier(this, parent); } -bool DMenu::Responder (event_t *ev) -{ - bool res = false; - if (ev->type == EV_GUI_Event) - { - if (ev->subtype == EV_GUI_LButtonDown) - { - res = MouseEventBack(MOUSE_Click, ev->data1, ev->data2); - // make the menu's mouse handler believe that the current coordinate is outside the valid range - if (res) ev->data2 = -1; - res |= CallMouseEvent(MOUSE_Click, ev->data1, ev->data2); - if (res) - { - SetCapture(); - } - - } - else if (ev->subtype == EV_GUI_MouseMove) - { - BackbuttonTime = BACKBUTTON_TIME; - if (mMouseCapture || m_use_mouse == 1) - { - res = MouseEventBack(MOUSE_Move, ev->data1, ev->data2); - if (res) ev->data2 = -1; - res |= CallMouseEvent(MOUSE_Move, ev->data1, ev->data2); - } - } - else if (ev->subtype == EV_GUI_LButtonUp) - { - if (mMouseCapture) - { - ReleaseCapture(); - res = MouseEventBack(MOUSE_Release, ev->data1, ev->data2); - if (res) ev->data2 = -1; - res |= CallMouseEvent(MOUSE_Release, ev->data1, ev->data2); - } - } - } - return false; -} - -DEFINE_ACTION_FUNCTION(DMenu, Responder) -{ - PARAM_SELF_PROLOGUE(DMenu); - PARAM_POINTER(ev, event_t); - ACTION_RETURN_BOOL(self->Responder(ev)); -} +//============================================================================= +// +// +// +//============================================================================= bool DMenu::CallResponder(event_t *ev) { @@ -221,7 +180,7 @@ bool DMenu::CallResponder(event_t *ev) GlobalVMStack.Call(func, params, 2, &ret, 1, nullptr); return !!retval; } - else return Responder(ev); + else return false; } //============================================================================= @@ -230,29 +189,6 @@ bool DMenu::CallResponder(event_t *ev) // //============================================================================= -bool DMenu::MenuEvent (int mkey, bool fromcontroller) -{ - switch (mkey) - { - case MKEY_Back: - { - Close(); - S_Sound (CHAN_VOICE | CHAN_UI, - CurrentMenu != nullptr? "menu/backup" : "menu/clear", snd_menuvolume, ATTN_NONE); - return true; - } - } - return false; -} - -DEFINE_ACTION_FUNCTION(DMenu, MenuEvent) -{ - PARAM_SELF_PROLOGUE(DMenu); - PARAM_INT(key); - PARAM_BOOL(fromcontroller); - ACTION_RETURN_BOOL(self->MenuEvent(key, fromcontroller)); -} - bool DMenu::CallMenuEvent(int mkey, bool fromcontroller) { IFVIRTUAL(DMenu, MenuEvent) @@ -263,7 +199,7 @@ bool DMenu::CallMenuEvent(int mkey, bool fromcontroller) GlobalVMStack.Call(func, params, 3, &ret, 1, nullptr); return !!retval; } - else return MenuEvent(mkey, fromcontroller); + else return false; } //============================================================================= // @@ -271,6 +207,15 @@ bool DMenu::CallMenuEvent(int mkey, bool fromcontroller) // //============================================================================= +DEFINE_ACTION_FUNCTION(DMenu, SetMouseCapture) +{ + PARAM_PROLOGUE; + PARAM_BOOL(on); + if (on) I_SetMouseCapture(); + else I_ReleaseMouseCapture(); + return 0; +} + void DMenu::Close () { if (CurrentMenu == nullptr) return; // double closing can happen in the save menu. @@ -287,108 +232,19 @@ void DMenu::Close () } } -//============================================================================= -// -// -// -//============================================================================= - -bool DMenu::MouseEvent(int type, int x, int y) -{ - return true; -} - -DEFINE_ACTION_FUNCTION(DMenu, MouseEvent) +DEFINE_ACTION_FUNCTION(DMenu, Close) { PARAM_SELF_PROLOGUE(DMenu); - PARAM_INT(type); - PARAM_INT(x); - PARAM_INT(y); - ACTION_RETURN_BOOL(self->MouseEvent(type, x, y)); -} - -bool DMenu::CallMouseEvent(int type, int x, int y) -{ - IFVIRTUAL(DMenu, MouseEvent) - { - VMValue params[] = { (DObject*)this, type, x, y }; - int retval; - VMReturn ret(&retval); - GlobalVMStack.Call(func, params, 4, &ret, 1, nullptr); - return !!retval; - } - else return MouseEvent (type, x, y); -} - -//============================================================================= -// -// -// -//============================================================================= - -bool DMenu::MouseEventBack(int type, int x, int y) -{ - if (m_show_backbutton >= 0) - { - FTexture *tex = TexMan(gameinfo.mBackButton); - if (tex != nullptr) - { - if (m_show_backbutton&1) x -= screen->GetWidth() - tex->GetScaledWidth() * CleanXfac; - if (m_show_backbutton&2) y -= screen->GetHeight() - tex->GetScaledHeight() * CleanYfac; - mBackbuttonSelected = ( x >= 0 && x < tex->GetScaledWidth() * CleanXfac && - y >= 0 && y < tex->GetScaledHeight() * CleanYfac); - if (mBackbuttonSelected && type == MOUSE_Release) - { - if (m_use_mouse == 2) mBackbuttonSelected = false; - CallMenuEvent(MKEY_Back, true); - } - return mBackbuttonSelected; - } - } - return false; -} - -//============================================================================= -// -// -// -//============================================================================= - -void DMenu::SetCapture() -{ - if (!mMouseCapture) - { - mMouseCapture = true; - I_SetMouseCapture(); - } -} - -void DMenu::ReleaseCapture() -{ - if (mMouseCapture) - { - mMouseCapture = false; - I_ReleaseMouseCapture(); - } -} - -//============================================================================= -// -// -// -//============================================================================= - -void DMenu::Ticker () -{ -} - -DEFINE_ACTION_FUNCTION(DMenu, Ticker) -{ - PARAM_SELF_PROLOGUE(DMenu); - self->Ticker(); + self->Close(); return 0; } +//============================================================================= +// +// +// +//============================================================================= + void DMenu::CallTicker() { IFVIRTUAL(DMenu, Ticker) @@ -396,38 +252,9 @@ void DMenu::CallTicker() VMValue params[] = { (DObject*)this }; GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); } - else Ticker(); } -void DMenu::Drawer () -{ - if (this == CurrentMenu && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) - { - FTexture *tex = TexMan(gameinfo.mBackButton); - int w = tex->GetScaledWidth() * CleanXfac; - int h = tex->GetScaledHeight() * CleanYfac; - int x = (!(m_show_backbutton&1))? 0:screen->GetWidth() - w; - int y = (!(m_show_backbutton&2))? 0:screen->GetHeight() - h; - if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) - { - screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, MAKEARGB(40, 255,255,255), TAG_DONE); - } - else - { - screen->DrawTexture(tex, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha, TAG_DONE); - } - } -} - - -DEFINE_ACTION_FUNCTION(DMenu, Drawer) -{ - PARAM_SELF_PROLOGUE(DMenu); - self->Drawer(); - return 0; -} - void DMenu::CallDrawer() { IFVIRTUAL(DMenu, Drawer) @@ -435,19 +262,6 @@ void DMenu::CallDrawer() VMValue params[] = { (DObject*)this }; GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); } - else Drawer(); -} - -DEFINE_ACTION_FUNCTION(DMenu, Close) -{ - PARAM_SELF_PROLOGUE(DMenu); - self->Close(); - return 0; -} - -bool DMenu::DimAllowed() -{ - return true; } bool DMenu::TranslateKeyboardEvents() @@ -504,7 +318,11 @@ void M_StartControlPanel (bool makeSound) void M_ActivateMenu(DMenu *menu) { if (menuactive == MENU_Off) menuactive = MENU_On; - if (CurrentMenu != nullptr) CurrentMenu->ReleaseCapture(); + if (CurrentMenu != nullptr && CurrentMenu->mMouseCapture) + { + CurrentMenu->mMouseCapture = false; + I_ReleaseMouseCapture(); + } CurrentMenu = menu; GC::WriteBarrier(CurrentMenu); } @@ -651,10 +469,15 @@ void M_SetMenu(FName menu, int param) const PClass *menuclass = PClass::FindClass(menu); if (menuclass != nullptr) { - if (menuclass->IsDescendantOf(RUNTIME_CLASS(DMenu))) + if (menuclass->IsDescendantOf("GenericMenu")) { DMenu *newmenu = (DMenu*)menuclass->CreateNew(); - newmenu->mParentMenu = CurrentMenu; + + IFVIRTUALPTRNAME(newmenu, "GenericMenu", Init) + { + VMValue params[3] = { newmenu, CurrentMenu }; + GlobalVMStack.Call(func, params, 2, nullptr, 0); + } M_ActivateMenu(newmenu); return; } @@ -928,7 +751,7 @@ void M_Drawer (void) if (CurrentMenu != nullptr && menuactive != MENU_Off) { - if (CurrentMenu->DimAllowed()) + if (!CurrentMenu->DontDim) { screen->Dim(fade); V_SetBorderNeedRefresh(); @@ -943,13 +766,14 @@ void M_Drawer (void) // //============================================================================= -void M_ClearMenus () +void M_ClearMenus() { M_DemoNoPlay = false; - if (CurrentMenu != nullptr) + while (CurrentMenu != nullptr) { + DMenu* parent = CurrentMenu->mParentMenu; CurrentMenu->Destroy(); - CurrentMenu = nullptr; + CurrentMenu = parent; } V_SetBorderNeedRefresh(); menuactive = MENU_Off; @@ -1203,6 +1027,7 @@ CCMD(undocolorpic) DEFINE_FIELD(DMenu, mParentMenu) DEFINE_FIELD(DMenu, mMouseCapture); DEFINE_FIELD(DMenu, mBackbuttonSelected); +DEFINE_FIELD(DMenu, DontDim); DEFINE_FIELD(DMenuDescriptor, mMenuName) DEFINE_FIELD(DMenuDescriptor, mNetgameMessage) @@ -1292,7 +1117,7 @@ DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBi return (DMenuItemBase*)p; } -DMenuItemBase * CreateListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID tex, FName command, int param) +DMenuItemBase * CreateListMenuItemPatch(double x, double y, int height, int hotkey, FTextureID tex, FName command, int param) { auto c = PClass::FindClass("ListMenuItemPatchItem"); auto p = c->CreateNew(); @@ -1302,7 +1127,7 @@ DMenuItemBase * CreateListMenuItemPatch(int x, int y, int height, int hotkey, FT return (DMenuItemBase*)p; } -DMenuItemBase * CreateListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param) +DMenuItemBase * CreateListMenuItemText(double x, double y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param) { auto c = PClass::FindClass("ListMenuItemTextItem"); auto p = c->CreateNew(); diff --git a/src/menu/menu.h b/src/menu/menu.h index 415e3dff6..8922147e1 100644 --- a/src/menu/menu.h +++ b/src/menu/menu.h @@ -138,11 +138,11 @@ class DListMenuDescriptor : public DMenuDescriptor public: TArray mItems; int mSelectedItem; - int mSelectOfsX; - int mSelectOfsY; + double mSelectOfsX; + double mSelectOfsY; FTextureID mSelector; int mDisplayTop; - int mXpos, mYpos; + double mXpos, mYpos; int mWLeft, mWRight; int mLinespacing; // needs to be stored for dynamically created menus int mAutoselect; // this can only be set by internal menu creation functions @@ -263,42 +263,19 @@ public: MOUSE_Release }; - enum - { - BACKBUTTON_TIME = 4*TICRATE - }; - TObjPtr mParentMenu; bool mMouseCapture; bool mBackbuttonSelected; + bool DontDim; DMenu(DMenu *parent = NULL); - virtual bool Responder (event_t *ev); - virtual bool MenuEvent (int mkey, bool fromcontroller); - virtual void Ticker (); - virtual void Drawer (); - virtual bool DimAllowed (); bool TranslateKeyboardEvents(); virtual void Close(); - virtual bool MouseEvent(int type, int x, int y); - - virtual void SetFocus(DMenuItemBase *fc) {} - virtual bool CheckFocus(DMenuItemBase *fc) { return false; } - virtual void ReleaseFocus() {} bool CallResponder(event_t *ev); bool CallMenuEvent(int mkey, bool fromcontroller); - bool CallMouseEvent(int type, int x, int y); void CallTicker(); void CallDrawer(); - - bool MouseEventBack(int type, int x, int y); - void SetCapture(); - void ReleaseCapture(); - bool HasCapture() - { - return mMouseCapture; - } }; //============================================================================= @@ -311,7 +288,7 @@ class DMenuItemBase : public DObject { DECLARE_CLASS(DMenuItemBase, DObject) public: - int mXpos, mYpos; + double mXpos, mYpos; FNameNoInit mAction; bool mEnabled; @@ -321,7 +298,7 @@ public: bool SetValue(int i, int value); bool GetValue(int i, int *pvalue); void OffsetPositionY(int ydelta) { mYpos += ydelta; } - int GetY() { return mYpos; } + double GetY() { return mYpos; } }; //============================================================================= @@ -378,7 +355,7 @@ DMenuItemBase * CreateOptionMenuItemStaticText(const char *name, bool v); DMenuItemBase * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center); DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings); DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy); -DMenuItemBase * CreateListMenuItemPatch(int x, int y, int height, int hotkey, FTextureID tex, FName command, int param); -DMenuItemBase * CreateListMenuItemText(int x, int y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param); +DMenuItemBase * CreateListMenuItemPatch(double x, double y, int height, int hotkey, FTextureID tex, FName command, int param); +DMenuItemBase * CreateListMenuItemText(double x, double y, int height, int hotkey, const char *text, FFont *font, PalEntry color1, PalEntry color2, FName command, int param); #endif diff --git a/src/menu/menudef.cpp b/src/menu/menudef.cpp index f9ecbd808..16b567166 100644 --- a/src/menu/menudef.cpp +++ b/src/menu/menudef.cpp @@ -305,11 +305,11 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) sc.MustGetString(); desc->mSelector = GetMenuTexture(sc.String); sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mSelectOfsX = sc.Number; + sc.MustGetFloat(); + desc->mSelectOfsX = sc.Float; sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mSelectOfsY = sc.Number; + sc.MustGetFloat(); + desc->mSelectOfsY = sc.Float; } else if (sc.Compare("Linespacing")) { @@ -318,11 +318,11 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) } else if (sc.Compare("Position")) { - sc.MustGetNumber(); - desc->mXpos = sc.Number; + sc.MustGetFloat(); + desc->mXpos = sc.Float; sc.MustGetStringName(","); - sc.MustGetNumber(); - desc->mYpos = sc.Number; + sc.MustGetFloat(); + desc->mYpos = sc.Float; } else if (sc.Compare("Centermenu")) { @@ -369,7 +369,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc) PClass *cls = PClass::FindClass(buildname); if (cls != nullptr && cls->IsDescendantOf("ListMenuItem")) { - auto func = dyn_cast(cls->Symbols.FindSymbol("Init", false)); + auto func = dyn_cast(cls->Symbols.FindSymbol("Init", true)); if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protexted init method. { auto &args = func->Variants[0].Proto->ArgumentTypes; @@ -741,7 +741,7 @@ static void ParseOptionMenuBody(FScanner &sc, DOptionMenuDescriptor *desc) PClass *cls = PClass::FindClass(buildname); if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem")) { - auto func = dyn_cast(cls->Symbols.FindSymbol("Init", false)); + auto func = dyn_cast(cls->Symbols.FindSymbol("Init", true)); if (func != nullptr && !(func->Variants[0].Flags & (VARF_Protected | VARF_Private))) // skip internal classes which have a protexted init method. { auto &args = func->Variants[0].Proto->ArgumentTypes; @@ -973,13 +973,13 @@ static void BuildEpisodeMenu() if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) { DListMenuDescriptor *ld = static_cast(*desc); - int posy = ld->mYpos; + int posy = (int)ld->mYpos; int topy = posy; // Get lowest y coordinate of any static item in the menu for(unsigned i = 0; i < ld->mItems.Size(); i++) { - int y = ld->mItems[i]->GetY(); + int y = (int)ld->mItems[i]->GetY(); if (y < topy) topy = y; } @@ -1073,13 +1073,13 @@ static void BuildPlayerclassMenu() // add player display ld->mSelectedItem = ld->mItems.Size(); - int posy = ld->mYpos; + int posy = (int)ld->mYpos; int topy = posy; // Get lowest y coordinate of any static item in the menu for(unsigned i = 0; i < ld->mItems.Size(); i++) { - int y = ld->mItems[i]->GetY(); + int y = (int)ld->mItems[i]->GetY(); if (y < topy) topy = y; } @@ -1333,8 +1333,8 @@ void M_StartupSkillMenu(FGameStartup *gs) if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) { DListMenuDescriptor *ld = static_cast(*desc); - int x = ld->mXpos; - int y = ld->mYpos; + int x = (int)ld->mXpos; + int y = (int)ld->mYpos; // Delete previous contents for(unsigned i=0; imItems.Size(); i++) @@ -1363,7 +1363,7 @@ void M_StartupSkillMenu(FGameStartup *gs) // Get lowest y coordinate of any static item in the menu for(unsigned i = 0; i < ld->mItems.Size(); i++) { - int y = ld->mItems[i]->GetY(); + int y = (int)ld->mItems[i]->GetY(); if (y < topy) topy = y; } @@ -1380,7 +1380,7 @@ void M_StartupSkillMenu(FGameStartup *gs) { ld->mItems[i]->OffsetPositionY(topdelta); } - y = ld->mYpos = posy - topdelta; + ld->mYpos = y = posy - topdelta; } } else diff --git a/src/namedef.h b/src/namedef.h index ebd9f3e8e..efc70273f 100644 --- a/src/namedef.h +++ b/src/namedef.h @@ -653,6 +653,7 @@ xx(Link) xx(Goodbye) xx(Require) xx(Exclude) +xx(Userstring) // Special menus xx(Mainmenu) diff --git a/src/p_conversation.cpp b/src/p_conversation.cpp index 9dc0d4ae6..449f9e286 100644 --- a/src/p_conversation.cpp +++ b/src/p_conversation.cpp @@ -61,6 +61,7 @@ #include "p_local.h" #include "menu/menu.h" #include "g_levellocals.h" +#include "virtual.h" // The conversations as they exist inside a SCRIPTxx lump. struct Response @@ -112,7 +113,6 @@ static FDialogueMap ClassRoots; static int ConversationMenuY; static int ConversationPauseTic; -static bool ShowGold; static int StaticLastReply; static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type); @@ -125,9 +125,6 @@ static void TerminalResponse (const char *str); static FStrifeDialogueNode *PrevNode; -#define NUM_RANDOM_LINES 10 -#define NUM_RANDOM_GOODBYES 3 - //============================================================================ // // GetStrifeType @@ -352,7 +349,7 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker // The speaker's portrait, if any. speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0; - node->Backdrop = TexMan.CheckForTexture (speech.Backdrop, FTexture::TEX_MiscPatch); + node->Backdrop = speech.Backdrop; // The speaker's voice for this node, if any. speech.Backdrop[0] = 0; //speech.Sound[8] = 0; @@ -426,7 +423,7 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker node->Dialogue = speech.Dialogue; // The Teaser version doesn't have portraits. - node->Backdrop.SetInvalid(); + node->Backdrop = ""; // The speaker's voice for this node, if any. if (speech.VoiceNumber != 0) @@ -690,8 +687,18 @@ static bool ShouldSkipReply(FStrifeDialogueReply *reply, player_t *player) return false; } -static void SendConversationReply(int node, int reply) +DEFINE_ACTION_FUNCTION(FStrifeDialogueReply, ShouldSkipReply) { + PARAM_SELF_STRUCT_PROLOGUE(FStrifeDialogueReply); + PARAM_POINTER(player, player_t); + ACTION_RETURN_BOOL(ShouldSkipReply(self, player)); +} + +DEFINE_ACTION_FUNCTION(DConversationMenu, SendConversationReply) +{ + PARAM_PROLOGUE; + PARAM_INT(node); + PARAM_INT(reply); switch (node) { case -1: @@ -709,6 +716,7 @@ static void SendConversationReply(int node, int reply) break; } StaticLastReply = reply; + return 0; } @@ -733,438 +741,6 @@ public: } }; -//============================================================================ -// -// The conversation menu -// -//============================================================================ - -class DConversationMenu : public DMenu -{ - DECLARE_CLASS(DConversationMenu, DMenu) - -public: - FString mSpeaker; - DBrokenLines *mDialogueLines; - TArray mResponseLines; - TArray mResponses; - bool mShowGold; - FStrifeDialogueNode *mCurNode; - int mYpos; - player_t *mPlayer; - int mSelection; - - //============================================================================= - // - // - // - //============================================================================= - - DConversationMenu(FStrifeDialogueNode *CurNode, player_t *player, int activereply) - { - mCurNode = CurNode; - mPlayer = player; - mDialogueLines = NULL; - mShowGold = false; - - // Format the speaker's message. - const char * toSay = CurNode->Dialogue; - if (strncmp (toSay, "RANDOM_", 7) == 0) - { - FString dlgtext; - - dlgtext.Format("TXT_%s_%02d", toSay, 1+(pr_randomspeech() % NUM_RANDOM_LINES)); - toSay = GStrings[dlgtext]; - if (toSay == NULL) - { - toSay = GStrings["TXT_GOAWAY"]; // Ok, it's lame - but it doesn't look like an error to the player. ;) - } - } - else - { - // handle string table replacement - if (toSay[0] == '$') - { - toSay = GStrings(toSay + 1); - } - } - if (toSay == NULL) - { - toSay = "."; - } - unsigned int count; - auto bl = V_BreakLines (SmallFont, screen->GetWidth()/CleanXfac - 24*2, toSay, true, &count); - mDialogueLines = new DBrokenLines(bl, count); - - mSelection = -1; - - FStrifeDialogueReply *reply; - int r = -1; - int i,j; - for (reply = CurNode->Children, i = 1; reply != NULL; reply = reply->Next) - { - r++; - if (ShouldSkipReply(reply, mPlayer)) - { - continue; - } - if (activereply == r) mSelection = i - 1; - - mShowGold |= reply->NeedsGold; - - const char *ReplyText = reply->Reply; - if (ReplyText[0] == '$') - { - ReplyText = GStrings(ReplyText + 1); - } - FString ReplyString = ReplyText; - if (reply->NeedsGold) ReplyString.AppendFormat(" for %u", reply->PrintAmount); - - FBrokenLines *ReplyLines = V_BreakLines (SmallFont, 320-50-10, ReplyString); - - mResponses.Push(mResponseLines.Size()); - for (j = 0; ReplyLines[j].Width >= 0; ++j) - { - mResponseLines.Push(ReplyLines[j].Text); - } - - ++i; - V_FreeBrokenLines (ReplyLines); - } - if (mSelection == -1) - { - mSelection = r < activereply ? r + 1 : 0; - } - const char *goodbyestr = CurNode->Goodbye; - if (*goodbyestr == 0) - { - char goodbye[25]; - mysnprintf(goodbye, countof(goodbye), "TXT_RANDOMGOODBYE_%d", 1 + (pr_randomspeech() % NUM_RANDOM_GOODBYES)); - goodbyestr = GStrings[goodbye]; - } - else if (strncmp(goodbyestr, "RANDOM_", 7) == 0) - { - FString byetext; - - byetext.Format("TXT_%s_%02d", goodbyestr, 1 + (pr_randomspeech() % NUM_RANDOM_LINES)); - goodbyestr = GStrings[byetext]; - } - else if (goodbyestr[0] == '$') - { - goodbyestr = GStrings(goodbyestr + 1); - } - if (goodbyestr == nullptr) goodbyestr = "Bye."; - mResponses.Push(mResponseLines.Size()); - mResponseLines.Push(FString(goodbyestr)); - - // Determine where the top of the reply list should be positioned. - mYpos = MIN (140, 192 - mResponseLines.Size() * OptionSettings.mLinespacing); - i = 44 + count * (OptionSettings.mLinespacing + 2); - if (mYpos - 100 < i - screen->GetHeight() / CleanYfac / 2) - { - mYpos = i - screen->GetHeight() / CleanYfac / 2 + 100; - } - ConversationMenuY = mYpos; - //ConversationMenu.indent = 50; - - // Because replies can be selectively hidden mResponses.Size() won't be consistent. - // So make sure mSelection doesn't exceed mResponses.Size(). [FishyClockwork] - if (mSelection >= (int)mResponses.Size()) - { - mSelection = mResponses.Size() - 1; - } - } - - //============================================================================= - // - // - // - //============================================================================= - - void OnDestroy() override - { - mDialogueLines->Destroy(); - mDialogueLines = NULL; - I_SetMusicVolume (1.f); - Super::OnDestroy(); - } - - bool DimAllowed() - { - return false; - } - - int GetReplyNum() - { - assert((unsigned)mCurNode->ThisNodeNum < StrifeDialogues.Size()); - assert(StrifeDialogues[mCurNode->ThisNodeNum] == mCurNode); - - // This is needed because mSelection represents the replies currently being displayed which will - // not match up with what's supposed to be selected if there are any hidden/skipped replies. [FishyClockwork] - FStrifeDialogueReply *reply = mCurNode->Children; - int replynum = mSelection; - for (int i = 0; i <= mSelection && reply != nullptr; reply = reply->Next) - { - if (ShouldSkipReply(reply, mPlayer)) - replynum++; - else - i++; - } - return replynum; - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MenuEvent(int mkey, bool fromcontroller) - { - if (demoplayback) - { // During demo playback, don't let the user do anything besides close this menu. - if (mkey == MKEY_Back) - { - Close(); - return true; - } - return false; - } - if (mkey == MKEY_Up) - { - if (--mSelection < 0) mSelection = mResponses.Size() - 1; - return true; - } - else if (mkey == MKEY_Down) - { - if (++mSelection >= (int)mResponses.Size()) mSelection = 0; - return true; - } - else if (mkey == MKEY_Back) - { - SendConversationReply(-1, GetReplyNum()); - Close(); - return true; - } - else if (mkey == MKEY_Enter) - { - int replynum = GetReplyNum(); - if ((unsigned)mSelection >= mResponses.Size()) - { - SendConversationReply(-1, replynum); - } - else - { - // Send dialogue and reply numbers across the wire. - SendConversationReply(mCurNode->ThisNodeNum, replynum); - } - Close(); - return true; - } - return false; - } - - //============================================================================= - // - // - // - //============================================================================= - - bool MouseEvent(int type, int x, int y) - { - int sel = -1; - int fh = OptionSettings.mLinespacing; - - // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture - x = ((x - (screen->GetWidth() / 2)) / CleanXfac) + 160; - y = ((y - (screen->GetHeight() / 2)) / CleanYfac) + 100; - - if (x >= 24 && x <= 320-24 && y >= mYpos && y < mYpos + fh * (int)mResponseLines.Size()) - { - sel = (y - mYpos) / fh; - for(unsigned i=0;i sel) - { - sel = i-1; - break; - } - } - } - if (sel != -1 && sel != mSelection) - { - //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); - } - mSelection = sel; - if (type == MOUSE_Release) - { - return MenuEvent(MKEY_Enter, true); - } - return true; - } - - - //============================================================================= - // - // - // - //============================================================================= - - bool Responder(event_t *ev) - { - if (demoplayback) - { // No interaction during demo playback - return false; - } - if (ev->type == EV_GUI_Event && ev->subtype == EV_GUI_Char && ev->data1 >= '0' && ev->data1 <= '9') - { // Activate an item of type numberedmore (dialogue only) - mSelection = ev->data1 == '0' ? 9 : ev->data1 - '1'; - return MenuEvent(MKEY_Enter, false); - } - return Super::Responder(ev); - } - - //============================================================================ - // - // DrawConversationMenu - // - //============================================================================ - - void Drawer() - { - const char *speakerName; - int x, y, linesize; - int width, fontheight; - - player_t *cp = &players[consoleplayer]; - - assert (mDialogueLines != NULL); - assert (mCurNode != NULL); - - FStrifeDialogueNode *CurNode = mCurNode; - - if (CurNode == NULL) - { - Close (); - return; - } - - // [CW] Freeze the game depending on MAPINFO options. - if (ConversationPauseTic < gametic && !multiplayer && !(level.flags2 & LEVEL2_CONV_SINGLE_UNFREEZE)) - { - menuactive = MENU_On; - } - - if (CurNode->Backdrop.isValid()) - { - screen->DrawTexture (TexMan(CurNode->Backdrop), 0, 0, DTA_320x200, true, TAG_DONE); - } - x = 16 * screen->GetWidth() / 320; - y = 16 * screen->GetHeight() / 200; - linesize = 10 * CleanYfac; - - // Who is talking to you? - if (CurNode->SpeakerName.IsNotEmpty()) - { - speakerName = CurNode->SpeakerName; - if (speakerName[0] == '$') speakerName = GStrings(speakerName+1); - } - else - { - speakerName = cp->ConversationNPC->GetTag("Person"); - } - - // Dim the screen behind the dialogue (but only if there is no backdrop). - if (!CurNode->Backdrop.isValid()) - { - int i = mDialogueLines->mCount; - screen->Dim (0, 0.45f, 14 * screen->GetWidth() / 320, 13 * screen->GetHeight() / 200, - 308 * screen->GetWidth() / 320 - 14 * screen->GetWidth () / 320, - speakerName == NULL ? linesize * i + 6 * CleanYfac - : linesize * i + 6 * CleanYfac + linesize * 3/2); - } - - // Dim the screen behind the PC's choices. - - screen->Dim (0, 0.45f, (24-160) * CleanXfac + screen->GetWidth()/2, - (mYpos - 2 - 100) * CleanYfac + screen->GetHeight()/2, - 272 * CleanXfac, - MIN(mResponseLines.Size() * OptionSettings.mLinespacing + 4, 200 - mYpos) * CleanYfac); - - if (speakerName != NULL) - { - screen->DrawText (SmallFont, CR_WHITE, x, y, speakerName, - DTA_CleanNoMove, true, TAG_DONE); - y += linesize * 3 / 2; - } - x = 24 * screen->GetWidth() / 320; - for (int i = 0; i < mDialogueLines->mCount; ++i) - { - screen->DrawText (SmallFont, CR_UNTRANSLATED, x, y, mDialogueLines->mBroken[i].Text, - DTA_CleanNoMove, true, TAG_DONE); - y += linesize; - } - - if (ShowGold) - { - auto cointype = PClass::FindActor("Coin"); - if (cointype) - { - AInventory *coin = cp->ConversationPC->FindInventory(cointype); - char goldstr[32]; - - mysnprintf(goldstr, countof(goldstr), "%d", coin != NULL ? coin->Amount : 0); - screen->DrawText(SmallFont, CR_GRAY, 21, 191, goldstr, DTA_320x200, true, - DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); - screen->DrawTexture(TexMan(((AInventory *)GetDefaultByType(cointype))->Icon), - 3, 190, DTA_320x200, true, - DTA_FillColor, 0, DTA_Alpha, HR_SHADOW, TAG_DONE); - screen->DrawText(SmallFont, CR_GRAY, 20, 190, goldstr, DTA_320x200, true, TAG_DONE); - screen->DrawTexture(TexMan(((AInventory *)GetDefaultByType(cointype))->Icon), - 2, 189, DTA_320x200, true, TAG_DONE); - } - } - - y = mYpos; - fontheight = OptionSettings.mLinespacing; - - int response = 0; - for (unsigned i = 0; i < mResponseLines.Size(); i++, y += fontheight) - { - width = SmallFont->StringWidth(mResponseLines[i]); - x = 64; - - screen->DrawText (SmallFont, CR_GREEN, x, y, mResponseLines[i], DTA_Clean, true, TAG_DONE); - - if (i == mResponses[response]) - { - char tbuf[16]; - - response++; - mysnprintf (tbuf, countof(tbuf), "%d.", response); - x = 50 - SmallFont->StringWidth (tbuf); - screen->DrawText (SmallFont, CR_GREY, x, y, tbuf, DTA_Clean, true, TAG_DONE); - - if (response == mSelection+1) - { - int color = ((MenuTime%8) < 4) || CurrentMenu != this ? CR_RED:CR_GREY; - - x = (50 + 3 - 160) * CleanXfac + screen->GetWidth() / 2; - int yy = (y + fontheight/2 - 5 - 100) * CleanYfac + screen->GetHeight() / 2; - screen->DrawText (ConFont, color, x, yy, "\xd", - DTA_CellX, 8 * CleanXfac, - DTA_CellY, 8 * CleanYfac, - TAG_DONE); - } - } - } - } - -}; - -IMPLEMENT_CLASS(DConversationMenu, true, false) - - //============================================================================ // // P_FreeStrifeConversations @@ -1184,7 +760,7 @@ void P_FreeStrifeConversations () ClassRoots.Clear(); PrevNode = NULL; - if (CurrentMenu != NULL && CurrentMenu->IsKindOf(RUNTIME_CLASS(DConversationMenu))) + if (CurrentMenu != NULL && CurrentMenu->IsKindOf("ConversationMenu")) { CurrentMenu->Close(); } @@ -1282,8 +858,21 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang S_Sound (npc, CHAN_VOICE|CHAN_NOPAUSE, CurNode->SpeakerVoice, 1, ATTN_NORM); } - DConversationMenu *cmenu = new DConversationMenu(CurNode, pc->player, StaticLastReply); + // Create the menu. This may be a user-defined class so check if it is good to use. + FName cls = CurNode->MenuClassName; + if (cls == NAME_None) cls = gameinfo.DefaultConversationMenuClass; + if (cls == NAME_None) cls = "ConversationMenu"; + auto mcls = PClass::FindClass(cls); + if (mcls == nullptr || !mcls->IsDescendantOf("ConversationMenu")) mcls = PClass::FindClass("ConversationMenu"); + assert(mcls); + auto cmenu = mcls->CreateNew(); + IFVIRTUALPTRNAME(cmenu, "ConversationMenu", Init) + { + VMValue params[] = { cmenu, CurNode, pc->player, StaticLastReply }; + VMReturn ret(&ConversationMenuY); + GlobalVMStack.Call(func, params, countof(params), &ret, 1); + } if (CurNode != PrevNode) { // Only reset the selection if showing a different menu. @@ -1293,8 +882,7 @@ void P_StartConversation (AActor *npc, AActor *pc, bool facetalker, bool saveang // And open the menu M_StartControlPanel (false); - M_ActivateMenu(cmenu); - ConversationPauseTic = gametic + 20; + M_ActivateMenu((DMenu*)cmenu); menuactive = MENU_OnNoPause; } } @@ -1531,8 +1119,7 @@ void P_ConversationCommand (int netcode, int pnum, BYTE **stream) // The conversation menus are normally closed by the menu code, but that // doesn't happen during demo playback, so we need to do it here. - if (demoplayback && CurrentMenu != NULL && - CurrentMenu->IsKindOf(RUNTIME_CLASS(DConversationMenu))) + if (demoplayback && CurrentMenu != NULL && CurrentMenu->IsKindOf("ConversationMenu")) { CurrentMenu->Close(); } @@ -1598,3 +1185,28 @@ static void TerminalResponse (const char *str) } } +DEFINE_FIELD(FStrifeDialogueNode, DropType); +DEFINE_FIELD(FStrifeDialogueNode, ThisNodeNum); +DEFINE_FIELD(FStrifeDialogueNode, ItemCheckNode); +DEFINE_FIELD(FStrifeDialogueNode, SpeakerType); +DEFINE_FIELD(FStrifeDialogueNode, SpeakerName); +DEFINE_FIELD(FStrifeDialogueNode, SpeakerVoice); +DEFINE_FIELD(FStrifeDialogueNode, Backdrop); +DEFINE_FIELD(FStrifeDialogueNode, Dialogue); +DEFINE_FIELD(FStrifeDialogueNode, Goodbye); +DEFINE_FIELD(FStrifeDialogueNode, Children); +DEFINE_FIELD(FStrifeDialogueNode, MenuClassName); +DEFINE_FIELD(FStrifeDialogueNode, UserData); + +DEFINE_FIELD(FStrifeDialogueReply, Next); +DEFINE_FIELD(FStrifeDialogueReply, GiveType); +DEFINE_FIELD(FStrifeDialogueReply, ActionSpecial); +DEFINE_FIELD(FStrifeDialogueReply, Args); +DEFINE_FIELD(FStrifeDialogueReply, PrintAmount); +DEFINE_FIELD(FStrifeDialogueReply, Reply); +DEFINE_FIELD(FStrifeDialogueReply, QuickYes); +DEFINE_FIELD(FStrifeDialogueReply, QuickNo); +DEFINE_FIELD(FStrifeDialogueReply, LogString); +DEFINE_FIELD(FStrifeDialogueReply, NextNode); +DEFINE_FIELD(FStrifeDialogueReply, LogNumber); +DEFINE_FIELD(FStrifeDialogueReply, NeedsGold); diff --git a/src/p_conversation.h b/src/p_conversation.h index c10c4c697..302203bd2 100644 --- a/src/p_conversation.h +++ b/src/p_conversation.h @@ -28,11 +28,13 @@ struct FStrifeDialogueNode PClassActor *SpeakerType; FString SpeakerName; FSoundID SpeakerVoice; - FTextureID Backdrop; + FString Backdrop; FString Dialogue; FString Goodbye; // must init to null for binary scripts to work as intended FStrifeDialogueReply *Children; + FName MenuClassName; + FString UserData; }; // FStrifeDialogueReply holds responses the player can give to the NPC diff --git a/src/p_usdf.cpp b/src/p_usdf.cpp index b8e290deb..d2a1bb25e 100644 --- a/src/p_usdf.cpp +++ b/src/p_usdf.cpp @@ -316,7 +316,14 @@ class USDFParser : public UDMFParserBase break; case NAME_Panel: - node->Backdrop = TexMan.CheckForTexture (CheckString(key), FTexture::TEX_MiscPatch); + node->Backdrop = CheckString(key); + break; + + case NAME_Userstring: + if (namespace_bits == Zd) + { + node->UserData = CheckString(key); + } break; case NAME_Voice: @@ -391,6 +398,7 @@ class USDFParser : public UDMFParserBase { PClassActor *type = NULL; int dlgid = -1; + FName clsid; unsigned int startpos = StrifeDialogues.Size(); while (!sc.CheckToken('}')) @@ -415,6 +423,13 @@ class USDFParser : public UDMFParserBase dlgid = CheckInt(key); } break; + + case NAME_Class: + if (namespace_bits == Zd) + { + clsid = CheckString(key); + } + break; } } else @@ -440,6 +455,7 @@ class USDFParser : public UDMFParserBase for(;startpos < StrifeDialogues.Size(); startpos++) { StrifeDialogues[startpos]->SpeakerType = type; + StrifeDialogues[startpos]->MenuClassName = clsid; } return true; } diff --git a/src/scripting/thingdef_data.cpp b/src/scripting/thingdef_data.cpp index 3808194e1..378c6c927 100644 --- a/src/scripting/thingdef_data.cpp +++ b/src/scripting/thingdef_data.cpp @@ -63,6 +63,9 @@ static TArray properties; static TArray AFTable; static TArray FieldTable; +extern int BackbuttonTime; +extern float BackbuttonAlpha; +static AWeapon *wpnochg; //========================================================================== // @@ -909,11 +912,20 @@ void InitThingdef() fieldptr = new PField("gametic", TypeSInt32, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&gametic); Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); + fieldptr = new PField("demoplayback", TypeSInt32, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&demoplayback); + Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); + + fieldptr = new PField("BackbuttonTime", TypeSInt32, VARF_Native | VARF_Static, (intptr_t)&BackbuttonTime); + Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); + + fieldptr = new PField("BackbuttonAlpha", TypeFloat32, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&BackbuttonAlpha); + Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); + // Argh. It sucks when bad hacks need to be supported. WP_NOCHANGE is just a bogus pointer but it used everywhere as a special flag. // It cannot be defined as constant because constants can either be numbers or strings but nothing else, so the only 'solution' // is to create a static variable from it and reference that in the script. Yuck!!! - static AWeapon *wpnochg = WP_NOCHANGE; + wpnochg = WP_NOCHANGE; fieldptr = new PField("WP_NOCHANGE", NewPointer(RUNTIME_CLASS(AWeapon), false), VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&wpnochg); Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); @@ -1216,6 +1228,14 @@ DEFINE_ACTION_FUNCTION(FStringStruct, Mid) ACTION_RETURN_STRING(s); } +DEFINE_ACTION_FUNCTION(FStringStruct, Left) +{ + PARAM_SELF_STRUCT_PROLOGUE(FString); + PARAM_UINT(len); + FString s = self->Left(len); + ACTION_RETURN_STRING(s); +} + DEFINE_ACTION_FUNCTION(FStringStruct, Truncate) { PARAM_SELF_STRUCT_PROLOGUE(FString); diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index cbe913fa3..528431575 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -23,6 +23,7 @@ #include "zscript/menu/textentermenu.txt" #include "zscript/menu/videomenu.txt" #include "zscript/menu/readthis.txt" +#include "zscript/menu/conversationmenu.txt" #include "zscript/inventory/inventory.txt" #include "zscript/inventory/inv_misc.txt" diff --git a/wadsrc/static/zscript/base.txt b/wadsrc/static/zscript/base.txt index fcad86814..e9a89b0ab 100644 --- a/wadsrc/static/zscript/base.txt +++ b/wadsrc/static/zscript/base.txt @@ -300,6 +300,7 @@ struct GameInfoStruct native native int gametype; native bool norandomplayerclass; native Array infoPages; + native String mBackButton; } class Object native @@ -463,6 +464,7 @@ struct LevelLocals native native bool nomonsters; native bool frozen; native bool infinite_flight; + native bool no_dlg_freeze; // level_info_t *info cannot be done yet. native String GetUDMFString(int type, int index, Name key); @@ -579,6 +581,7 @@ struct StringStruct native native vararg void AppendFormat(String fmt, ...); native void Replace(String pattern, String replacement); + native String Left(int len); native String Mid(int pos = 0, int len = 2147483647); native void Truncate(int newlen); native String CharAt(int pos); diff --git a/wadsrc/static/zscript/menu/conversationmenu.txt b/wadsrc/static/zscript/menu/conversationmenu.txt new file mode 100644 index 000000000..dba162ad7 --- /dev/null +++ b/wadsrc/static/zscript/menu/conversationmenu.txt @@ -0,0 +1,516 @@ +/* +** conversationmenu.txt +** The Strife dialogue display +** +**--------------------------------------------------------------------------- +** Copyright 2010-2017 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 StrifeDialogueNode native +{ + native Class DropType; + native int ThisNodeNum; + native int ItemCheckNode; + + native Class SpeakerType; + native String SpeakerName; + native Sound SpeakerVoice; + native String Backdrop; + native String Dialogue; + native String Goodbye; + + native StrifeDialogueReply Children; +} + +// FStrifeDialogueReply holds responses the player can give to the NPC +struct StrifeDialogueReply native +{ + native StrifeDialogueReply Next; + native Class GiveType; + native int ActionSpecial; + native int Args[5]; + native int PrintAmount; + native String Reply; + native String QuickYes; + native String QuickNo; + native String LogString; + native int NextNode; // index into StrifeDialogues + native int LogNumber; + native bool NeedsGold; + + native bool ShouldSkipReply(PlayerInfo player); +} + + +class ConversationMenu : Menu +{ + String mSpeaker; + BrokenLines mDialogueLines; + Array mResponseLines; + Array mResponses; + bool mShowGold; + StrifeDialogueNode mCurNode; + int mYpos; + PlayerInfo mPlayer; + int mSelection; + int ConversationPauseTic; + + int SpeechWidth; + int ReplyWidth; + + native static void SendConversationReply(int node, int reply); + + const NUM_RANDOM_LINES = 10; + const NUM_RANDOM_GOODBYES = 3; + + //============================================================================= + // + // returns the y position of the replies boy for positioning the terminal response. + // + //============================================================================= + + virtual int Init(StrifeDialogueNode CurNode, PlayerInfo player, int activereply) + { + mCurNode = CurNode; + mPlayer = player; + mShowGold = false; + ConversationPauseTic = gametic + 20; + DontDim = true; + + ReplyWidth = 320-50-10; + SpeechWidth = screen.GetWidth()/CleanXfac - 24*2; + + FormatSpeakerMessage(); + return FormatReplies(activereply); + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual int FormatReplies(int activereply) + { + mSelection = -1; + + StrifeDialogueReply reply; + int r = -1; + int i = 1,j; + for (reply = mCurNode.Children; reply != NULL; reply = reply.Next) + { + r++; + if (reply.ShouldSkipReply(mPlayer)) + { + continue; + } + if (activereply == r) mSelection = i - 1; + + mShowGold |= reply.NeedsGold; + + let ReplyText = Stringtable.Localize(reply.Reply); + if (reply.NeedsGold) ReplyText.AppendFormat(" for %u", reply.PrintAmount); + + let ReplyLines = SmallFont.BreakLines (ReplyText, ReplyWidth); + + mResponses.Push(mResponseLines.Size()); + for (j = 0; j < ReplyLines.Count(); ++j) + { + mResponseLines.Push(ReplyLines.StringAt(j)); + } + + ++i; + ReplyLines.Destroy(); + } + if (mSelection == -1) + { + mSelection = r < activereply ? r + 1 : 0; + } + let goodbyestr = mCurNode.Goodbye; + if (goodbyestr.Length() == 0) + { + goodbyestr = String.Format("$TXT_RANDOMGOODBYE_%d", Random[RandomSpeech](1, NUM_RANDOM_GOODBYES)); + } + else if (goodbyestr.Left(7) == "RANDOM_") + { + goodbyestr = String.Format("$TXT_%s_%02d", goodbyestr, Random[RandomSpeech](1, NUM_RANDOM_LINES)); + } + goodbyestr = Stringtable.Localize(goodbyestr); + if (goodbyestr.Length() == 0 || goodbyestr.CharAt(0) == "$") goodbyestr = "Bye."; + mResponses.Push(mResponseLines.Size()); + mResponseLines.Push(goodbyestr); + + // Determine where the top of the reply list should be positioned. + mYpos = MIN (140, 192 - mResponseLines.Size() * OptionMenuSettings.mLinespacing); + i = 44 + mResponseLines.Size() * OptionMenuSettings.mLinespacing; + if (mYpos - 100 < i - screen.GetHeight() / CleanYfac / 2) + { + mYpos = i - screen.GetHeight() / CleanYfac / 2 + 100; + } + + if (mSelection >= mResponses.Size()) + { + mSelection = mResponses.Size() - 1; + } + return mYpos; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual void FormatSpeakerMessage() + { + // Format the speaker's message. + String toSay = mCurNode.Dialogue; + if (toSay.Left(7) == "RANDOM_") + { + let dlgtext = String.Format("$TXT_%s_%02d", toSay, random[RandomSpeech](1, NUM_RANDOM_LINES)); + toSay = Stringtable.Localize(dlgtext); + if (toSay.CharAt(0) == "$") toSay = Stringtable.Localize("$TXT_GOAWAY"); + } + else + { + // handle string table replacement + toSay = Stringtable.Localize(toSay); + } + if (toSay.Length() == 0) + { + toSay = "."; + } + mDialogueLines = SmallFont.BreakLines(toSay, SpeechWidth); + } + + //============================================================================= + // + // + // + //============================================================================= + + override void OnDestroy() + { + mDialogueLines.Destroy(); + SetMusicVolume (1); + Super.OnDestroy(); + } + + protected int GetReplyNum() + { + // This is needed because mSelection represents the replies currently being displayed which will + // not match up with what's supposed to be selected if there are any hidden/skipped replies. [FishyClockwork] + let reply = mCurNode.Children; + int replynum = mSelection; + for (int i = 0; i <= mSelection && reply != null; reply = reply.Next) + { + if (reply.ShouldSkipReply(mPlayer)) + replynum++; + else + i++; + } + return replynum; + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MenuEvent(int mkey, bool fromcontroller) + { + if (demoplayback) + { // During demo playback, don't let the user do anything besides close this menu. + if (mkey == MKEY_Back) + { + Close(); + return true; + } + return false; + } + if (mkey == MKEY_Up) + { + if (--mSelection < 0) mSelection = mResponses.Size() - 1; + return true; + } + else if (mkey == MKEY_Down) + { + if (++mSelection >= mResponses.Size()) mSelection = 0; + return true; + } + else if (mkey == MKEY_Back) + { + SendConversationReply(-1, GetReplyNum()); + Close(); + return true; + } + else if (mkey == MKEY_Enter) + { + int replynum = GetReplyNum(); + if (mSelection >= mResponses.Size()) + { + SendConversationReply(-2, replynum); + } + else + { + // Send dialogue and reply numbers across the wire. + SendConversationReply(mCurNode.ThisNodeNum, replynum); + } + Close(); + return true; + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + override bool MouseEvent(int type, int x, int y) + { + int sel = -1; + int fh = OptionMenuSettings.mLinespacing; + + // convert x/y from screen to virtual coordinates, according to CleanX/Yfac use in DrawTexture + x = ((x - (screen.GetWidth() / 2)) / CleanXfac) + 160; + y = ((y - (screen.GetHeight() / 2)) / CleanYfac) + 100; + + if (x >= 24 && x <= 320-24 && y >= mYpos && y < mYpos + fh * mResponseLines.Size()) + { + sel = (y - mYpos) / fh; + for(int i = 0; i < mResponses.Size(); i++) + { + if (mResponses[i] > sel) + { + sel = i-1; + break; + } + } + } + if (sel != -1 && sel != mSelection) + { + //S_Sound (CHAN_VOICE | CHAN_UI, "menu/cursor", snd_menuvolume, ATTN_NONE); + } + mSelection = sel; + if (type == MOUSE_Release) + { + return MenuEvent(MKEY_Enter, true); + } + return true; + } + + + //============================================================================= + // + // + // + //============================================================================= + + override bool Responder(InputEventData ev) + { + if (demoplayback) + { // No interaction during demo playback + return false; + } + if (ev.type == InputEventData.GUI_Event && ev.subtype == InputEventData.GUI_Char && ev.data1 >= 48 && ev.data1 <= 57) + { // Activate an item of type numberedmore (dialogue only) + mSelection = ev.data1 == 48 ? 9 : ev.data1 - 49; + return MenuEvent(MKEY_Enter, false); + } + return Super.Responder(ev); + } + + //============================================================================ + // + // Draw the backdrop, returns true if the text background should be dimmed + // + //============================================================================ + + virtual bool DrawBackdrop() + { + let tex = TexMan.CheckForTexture (mCurNode.Backdrop, TexMan.Type_MiscPatch); + if (tex.isValid()) + { + screen.DrawTexture(tex, false, 0, 0, DTA_320x200, true); + return false; + } + return true; + } + + //============================================================================ + // + // Draw the speaker text + // + //============================================================================ + + virtual void DrawSpeakerText(bool dimbg) + { + String speakerName; + int linesize = OptionMenuSettings.mLinespacing * CleanYfac; + int cnt = mDialogueLines.Count(); + + // Who is talking to you? + if (mCurNode.SpeakerName.Length() > 0) + { + speakerName = Stringtable.Localize(mCurNode.SpeakerName); + } + else + { + speakerName = players[consoleplayer].ConversationNPC.GetTag("Person"); + } + + + // Dim the screen behind the dialogue (but only if there is no backdrop). + if (dimbg) + { + int x = 14 * screen.GetWidth() / 320; + int y = 13 * screen.GetHeight() / 200; + int w = 294 * screen.GetWidth() / 320; + int h = linesize * cnt + 6 * CleanYfac; + if (speakerName.Length() > 0) h += linesize * 3 / 2; + screen.Dim(0, 0.45f, x, y, w, h); + } + + int x = 16 * screen.GetWidth() / 320; + int y = 16 * screen.GetHeight() / 200; + + if (speakerName.Length() > 0) + { + screen.DrawText(SmallFont, Font.CR_WHITE, x, y, speakerName, DTA_CleanNoMove, true); + y += linesize * 3 / 2; + } + x = 24 * screen.GetWidth() / 320; + for (int i = 0; i < cnt; ++i) + { + screen.DrawText(SmallFont, Font.CR_UNTRANSLATED, x, y, mDialogueLines.StringAt(i), DTA_CleanNoMove, true); + y += linesize; + } + } + + + //============================================================================ + // + // Draw the replies + // + //============================================================================ + + virtual void DrawReplies() + { + // Dim the screen behind the PC's choices. + screen.Dim(0, 0.45, (24 - 160) * CleanXfac + screen.GetWidth() / 2, (mYpos - 2 - 100) * CleanYfac + screen.GetHeight() / 2, + 272 * CleanXfac, MIN(mResponseLines.Size() * OptionMenuSettings.mLinespacing + 4, 200 - mYpos) * CleanYfac); + + int y = mYpos; + int fontheight = OptionMenuSettings.mLinespacing; + + int response = 0; + for (int i = 0; i < mResponseLines.Size(); i++) + { + int width = SmallFont.StringWidth(mResponseLines[i]); + int x = 64; + + screen.DrawText(SmallFont, Font.CR_GREEN, x, y, mResponseLines[i], DTA_Clean, true); + + if (i == mResponses[response]) + { + String tbuf; + + response++; + tbuf = String.Format("%d.", response); + x = 50 - SmallFont.StringWidth(tbuf); + screen.DrawText(SmallFont, Font.CR_GREY, x, y, tbuf, DTA_Clean, true); + + if (response == mSelection + 1) + { + int colr = ((MenuTime() % 8) < 4) || GetCurrentMenu() != self ? Font.CR_RED : Font.CR_GREY; + + x = (50 + 3 - 160) * CleanXfac + screen.GetWidth() / 2; + int yy = (y + fontheight / 2 - 5 - 100) * CleanYfac + screen.GetHeight() / 2; + screen.DrawText(ConFont, colr, x, yy, "\xd", DTA_CellX, 8 * CleanXfac, DTA_CellY, 8 * CleanYfac); + } + } + y += fontheight; + } + } + + virtual void DrawGold() + { + if (mShowGold) + { + let coin = players[consoleplayer].ConversationPC.FindInventory("Coin"); + let icon = GetDefaultByType("Coin").Icon; + let goldstr = String.Format("%d", coin != NULL ? coin.Amount : 0); + screen.DrawText(SmallFont, Font.CR_GRAY, 21, 191, goldstr, DTA_320x200, true, DTA_FillColor, 0, DTA_Alpha, HR_SHADOW); + screen.DrawTexture(icon, false, 3, 190, DTA_320x200, true, DTA_FillColor, 0, DTA_Alpha, HR_SHADOW); + screen.DrawText(SmallFont, Font.CR_GRAY, 20, 190, goldstr, DTA_320x200, true); + screen.DrawTexture(icon, false, 2, 189, DTA_320x200, true); + } + + } + + //============================================================================ + // + // DrawConversationMenu + // + //============================================================================ + + override void Drawer() + { + if (mCurNode == NULL) + { + Close (); + return; + } + + bool dimbg = DrawBackdrop(); + DrawSpeakerText(dimbg); + DrawReplies(); + DrawGold(); + } + + + //============================================================================ + // + // + // + //============================================================================ + + override void Ticker() + { + // [CW] Freeze the game depending on MAPINFO options. + if (ConversationPauseTic < gametic && !multiplayer && !level.no_dlg_freeze) + { + menuactive = Menu.On; + } + } + +} diff --git a/wadsrc/static/zscript/menu/listmenu.txt b/wadsrc/static/zscript/menu/listmenu.txt index db1a4a19c..a7a9f71ae 100644 --- a/wadsrc/static/zscript/menu/listmenu.txt +++ b/wadsrc/static/zscript/menu/listmenu.txt @@ -4,11 +4,11 @@ class ListMenuDescriptor : MenuDescriptor native { native Array mItems; native int mSelectedItem; - native int mSelectOfsX; - native int mSelectOfsY; + native double mSelectOfsX; + native double mSelectOfsY; native TextureID mSelector; native int mDisplayTop; - native int mXpos, mYpos; + native double mXpos, mYpos; native int mWLeft, mWRight; native int mLinespacing; // needs to be stored for dynamically created menus native int mAutoselect; // this can only be set by internal menu creation functions @@ -47,20 +47,20 @@ class ListMenu : Menu virtual void Init(Menu parent = NULL, ListMenuDescriptor desc = NULL) { - mParentMenu = parent; + Super.Init(parent); mDesc = desc; if (desc.mCenter) { - int center = 160; + double center = 160; for(int i=0; i < mDesc.mItems.Size(); i++) { - int xpos = mDesc.mItems[i].GetX(); + double xpos = mDesc.mItems[i].GetX(); int width = mDesc.mItems[i].GetWidth(); - int curx = mDesc.mSelectOfsX; + double curx = mDesc.mSelectOfsX; if (width > 0 && mDesc.mItems[i].Selectable()) { - int left = 160 - (width - curx) / 2 - curx; + double left = 160 - (width - curx) / 2 - curx; if (left < center) center = left; } } diff --git a/wadsrc/static/zscript/menu/listmenuitems.txt b/wadsrc/static/zscript/menu/listmenuitems.txt index e3eec6dd1..c1bafeaf9 100644 --- a/wadsrc/static/zscript/menu/listmenuitems.txt +++ b/wadsrc/static/zscript/menu/listmenuitems.txt @@ -35,7 +35,7 @@ class ListMenuItem : MenuItemBase { - void DrawSelector(int xofs, int yofs, TextureID tex) + void DrawSelector(double xofs, double yofs, TextureID tex) { if (tex.isNull()) { @@ -68,7 +68,7 @@ class ListMenuItemStaticPatch : ListMenuItem TextureID mTexture; bool mCentered; - void Init(int x, int y, TextureID patch, bool centered = false) + void Init(double x, double y, TextureID patch, bool centered = false) { Super.Init(x, y); mTexture = patch; @@ -82,17 +82,17 @@ class ListMenuItemStaticPatch : ListMenuItem return; } - int x = mXpos; + double x = mXpos; Vector2 vec = TexMan.GetScaledSize(mTexture); if (mYpos >= 0) { - if (mCentered) x -= int(vec.X) / 2; + if (mCentered) x -= vec.X / 2; screen.DrawTexture (mTexture, true, x, mYpos, DTA_Clean, true); } else { - int x = (mXpos - 160) * CleanXfac + (Screen.GetWidth()>>1); - if (mCentered) x -= (int(vec.X) * CleanXfac)/2; + x = (mXpos - 160) * CleanXfac + (Screen.GetWidth()>>1); + if (mCentered) x -= (vec.X * CleanXfac)/2; screen.DrawTexture (mTexture, true, x, -mYpos*CleanYfac, DTA_CleanNoMove, true); } } @@ -100,7 +100,7 @@ class ListMenuItemStaticPatch : ListMenuItem class ListMenuItemStaticPatchCentered : ListMenuItemStaticPatch { - void Init(int x, int y, TextureID patch) + void Init(double x, double y, TextureID patch) { Super.Init(x, y, patch, true); } @@ -119,7 +119,7 @@ class ListMenuItemStaticText : ListMenuItem int mColor; bool mCentered; - void Init(ListMenuDescriptor desc, int x, int y, String text, int color = Font.CR_UNTRANSLATED) + void Init(ListMenuDescriptor desc, double x, double y, String text, int color = Font.CR_UNTRANSLATED) { Super.Init(x, y); mText = text; @@ -128,7 +128,7 @@ class ListMenuItemStaticText : ListMenuItem mCentered = false; } - void InitDirect(int x, int y, String text, Font font, int color = Font.CR_UNTRANSLATED, bool centered = false) + void InitDirect(double x, double y, String text, Font font, int color = Font.CR_UNTRANSLATED, bool centered = false) { Super.Init(x, y); mText = text; @@ -144,13 +144,13 @@ class ListMenuItemStaticText : ListMenuItem String text = Stringtable.Localize(mText); if (mYpos >= 0) { - int x = mXpos; + double x = mXpos; if (mCentered) x -= mFont.StringWidth(text)/2; screen.DrawText(mFont, mColor, x, mYpos, text, DTA_Clean, true); } else { - int x = (mXpos - 160) * CleanXfac + (Screen.GetWidth() >> 1); + double x = (mXpos - 160) * CleanXfac + (Screen.GetWidth() >> 1); if (mCentered) x -= (mFont.StringWidth(text) * CleanXfac)/2; screen.DrawText (mFont, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true); } @@ -160,7 +160,7 @@ class ListMenuItemStaticText : ListMenuItem class ListMenuItemStaticTextCentered : ListMenuItemStaticText { - void Init(ListMenuDescriptor desc, int x, int y, String text, int color = -1) + void Init(ListMenuDescriptor desc, double x, double y, String text, int color = -1) { Super.Init(desc, x, y, text, color); mCentered = true; @@ -179,7 +179,7 @@ class ListMenuItemSelectable : ListMenuItem int mHeight; int mParam; - protected void Init(int x, int y, int height, Name childmenu, int param = -1) + protected void Init(double x, double y, int height, Name childmenu, int param = -1) { Super.Init(x, y, childmenu); mHeight = height; @@ -250,7 +250,7 @@ class ListMenuItemTextItem : ListMenuItemSelectable mHotkey = hotkey.CharCodeAt(0); } - void InitDirect(int x, int y, int height, String hotkey, String text, Font font, int color, int color2, Name child, int param = 0) + void InitDirect(double x, double y, int height, String hotkey, String text, Font font, int color, int color2, Name child, int param = 0) { Super.Init(x, y, height, child, param); mText = text; @@ -288,7 +288,7 @@ class ListMenuItemPatchItem : ListMenuItemSelectable mTexture = patch; } - void InitDirect(int x, int y, int height, TextureID patch, String hotkey, Name child, int param = 0) + void InitDirect(double x, double y, int height, TextureID patch, String hotkey, Name child, int param = 0) { Super.Init(x, y, height, child, param); mHotkey = hotkey.CharCodeAt(0); diff --git a/wadsrc/static/zscript/menu/menu.txt b/wadsrc/static/zscript/menu/menu.txt index 2f5d9454a..84478ba81 100644 --- a/wadsrc/static/zscript/menu/menu.txt +++ b/wadsrc/static/zscript/menu/menu.txt @@ -89,32 +89,192 @@ class Menu : Object native native Menu mParentMenu; native bool mMouseCapture; native bool mBackbuttonSelected; + native bool DontDim; - void Init(Menu parent) - { - mParentMenu = parent; - } - native static int MenuTime(); native static void SetVideoMode(); native static Menu GetCurrentMenu(); native static void SetMenu(Name mnu, int param = 0); native static void StartMessage(String msg, int mode = 0, Name command = 'none'); + native static void SetMouseCapture(bool on); + native void Close(); + native void ActivateMenu(); + //============================================================================= + // + // + // + //============================================================================= + + void Init(Menu parent) + { + mParentMenu = parent; + mMouseCapture = false; + mBackbuttonSelected = false; + DontDim = false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual bool MenuEvent (int mkey, bool fromcontroller) + { + switch (mkey) + { + case MKEY_Back: + Close(); + MenuSound (GetCurrentMenu() != null? "menu/backup" : "menu/clear"); + return true; + } + return false; + } + + + //============================================================================= + // + // + // + //============================================================================= + + protected bool MouseEventBack(int type, int x, int y) + { + if (m_show_backbutton >= 0) + { + let tex = TexMan.CheckForTexture(gameinfo.mBackButton, TexMan.Type_MiscPatch); + if (tex.IsValid()) + { + Vector2 v = TexMan.GetScaledSize(tex); + int w = int(v.X + 0.5) * CleanXfac; + int h = int(v.Y + 0.5) * CleanYfac; + if (m_show_backbutton&1) x -= screen.GetWidth() - w; + if (m_show_backbutton&2) y -= screen.GetHeight() - h; + mBackbuttonSelected = ( x >= 0 && x < w && y >= 0 && y < h); + if (mBackbuttonSelected && type == MOUSE_Release) + { + if (m_use_mouse == 2) mBackbuttonSelected = false; + MenuEvent(MKEY_Back, true); + } + return mBackbuttonSelected; + } + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual bool Responder(InputEventData ev) + { + bool res = false; + if (ev.type == InputEventData.GUI_Event) + { + if (ev.subtype == InputEventData.GUI_LButtonDown) + { + res = MouseEventBack(MOUSE_Click, ev.data1, ev.data2); + // make the menu's mouse handler believe that the current coordinate is outside the valid range + if (res) ev.data2 = -1; + res |= MouseEvent(MOUSE_Click, ev.data1, ev.data2); + if (res) + { + SetCapture(true); + } + + } + else if (ev.subtype == InputEventData.GUI_MouseMove) + { + BackbuttonTime = 4*Thinker.TICRATE; + if (mMouseCapture || m_use_mouse == 1) + { + res = MouseEventBack(MOUSE_Move, ev.data1, ev.data2); + if (res) ev.data2 = -1; + res |= MouseEvent(MOUSE_Move, ev.data1, ev.data2); + } + } + else if (ev.subtype == InputEventData.GUI_LButtonUp) + { + if (mMouseCapture) + { + SetCapture(false); + res = MouseEventBack(MOUSE_Release, ev.data1, ev.data2); + if (res) ev.data2 = -1; + res |= MouseEvent(MOUSE_Release, ev.data1, ev.data2); + } + } + } + return false; + } + + //============================================================================= + // + // + // + //============================================================================= + + virtual void Drawer () + { + if (self == GetCurrentMenu() && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) + { + let tex = TexMan.CheckForTexture(gameinfo.mBackButton, TexMan.Type_MiscPatch); + if (tex.IsValid()) + { + Vector2 v = TexMan.GetScaledSize(tex); + int w = int(v.X + 0.5) * CleanXfac; + int h = int(v.Y + 0.5) * CleanYfac; + int x = (!(m_show_backbutton&1))? 0:screen.GetWidth() - w; + int y = (!(m_show_backbutton&2))? 0:screen.GetHeight() - h; + if (mBackbuttonSelected && (mMouseCapture || m_use_mouse == 1)) + { + screen.DrawTexture(tex, true, x, y, DTA_CleanNoMove, true, DTA_ColorOverlay, Color(40, 255,255,255)); + } + else + { + screen.DrawTexture(tex, true, x, y, DTA_CleanNoMove, true, DTA_Alpha, BackbuttonAlpha); + } + } + } + } + + //============================================================================= + // + // + // + //============================================================================= + + void SetCapture(bool on) + { + if (mMouseCapture != on) + { + mMouseCapture = on; + SetMouseCapture(on); + } + } + + //============================================================================= + // + // + // + //============================================================================= + virtual bool TranslateKeyboardEvents() { return true; } virtual void SetFocus(MenuItemBase fc) {} virtual bool CheckFocus(MenuItemBase fc) { return false; } virtual void ReleaseFocus() {} virtual void ResetColor() {} + virtual bool MouseEvent(int type, int mx, int my) { return false; } + virtual void Ticker() {} + + //============================================================================= + // + // + // + //============================================================================= - native virtual bool Responder(InputEventData ev); - native virtual bool MenuEvent (int mkey, bool fromcontroller); - native virtual bool MouseEvent(int type, int mx, int my); - native virtual void Ticker(); - native virtual void Drawer(); - native void Close(); - native void ActivateMenu(); - static void MenuSound(Sound snd) { S_Sound (snd, CHAN_VOICE | CHAN_UI, snd_menuvolume, ATTN_NONE); @@ -136,3 +296,11 @@ class MenuDescriptor : Object native native static MenuDescriptor GetDescriptor(Name n); } +// This class is only needed to give it a virtual Init method that doesn't belong to Menu itself +class GenericMenu : Menu +{ + virtual void Init(Menu parent) + { + Super.Init(parent); + } +} \ No newline at end of file diff --git a/wadsrc/static/zscript/menu/menuitembase.txt b/wadsrc/static/zscript/menu/menuitembase.txt index de5f05a42..35663d8b2 100644 --- a/wadsrc/static/zscript/menu/menuitembase.txt +++ b/wadsrc/static/zscript/menu/menuitembase.txt @@ -6,11 +6,11 @@ class MenuItemBase : Object native { - protected native int mXpos, mYpos; + protected native double mXpos, mYpos; protected native Name mAction; native bool mEnabled; - void Init(int xpos = 0, int ypos = 0, Name actionname = 'None') + void Init(double xpos = 0, double ypos = 0, Name actionname = 'None') { mXpos = xpos; mYpos = ypos; @@ -36,10 +36,10 @@ class MenuItemBase : Object native virtual int GetIndent() { return 0; } virtual int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) { return indent; } - void OffsetPositionY(int ydelta) { mYpos += ydelta; } - int GetY() { return mYpos; } - int GetX() { return mXpos; } - void SetX(int x) { mXpos = x; } + void OffsetPositionY(double ydelta) { mYpos += ydelta; } + double GetY() { return mYpos; } + double GetX() { return mXpos; } + void SetX(double x) { mXpos = x; } } // this is only used to parse font color ranges in MENUDEF diff --git a/wadsrc/static/zscript/menu/messagebox.txt b/wadsrc/static/zscript/menu/messagebox.txt index d95298546..b7b611f41 100644 --- a/wadsrc/static/zscript/menu/messagebox.txt +++ b/wadsrc/static/zscript/menu/messagebox.txt @@ -181,12 +181,12 @@ class MessageBoxMenu : Menu int ch = ev.data1; ch = ch >= 65 && ch <91? ch + 32 : ch; - if (ch == 78 /*'n'*/ || ch == 32) + if (ch == 110 /*'n'*/ || ch == 32) { HandleResult(false); return true; } - else if (ch == 89 /*'y'*/) + else if (ch == 121 /*'y'*/) { HandleResult(true); return true; diff --git a/wadsrc/static/zscript/menu/optionmenu.txt b/wadsrc/static/zscript/menu/optionmenu.txt index c6f0247a7..ed6189335 100644 --- a/wadsrc/static/zscript/menu/optionmenu.txt +++ b/wadsrc/static/zscript/menu/optionmenu.txt @@ -104,6 +104,7 @@ class OptionMenu : Menu { mParentMenu = parent; mDesc = desc; + DontDim = desc.mDontDim; if (mDesc != NULL && mDesc.mSelectedItem == -1) mDesc.mSelectedItem = FirstSelectable(); mDesc.CalcIndent(); } diff --git a/wadsrc/static/zscript/menu/optionmenuitems.txt b/wadsrc/static/zscript/menu/optionmenuitems.txt index 9ac308372..404ce5b36 100644 --- a/wadsrc/static/zscript/menu/optionmenuitems.txt +++ b/wadsrc/static/zscript/menu/optionmenuitems.txt @@ -1129,6 +1129,7 @@ class OptionMenuItemTextField : OptionMenuFieldBase { Menu.MenuSound("menu/choose"); mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), GetCVarString(), -1, 2, fromcontroller); + mEnter.ActivateMenu(); return true; } else if (mkey == Menu.MKEY_Input) diff --git a/wadsrc/static/zscript/menu/playercontrols.txt b/wadsrc/static/zscript/menu/playercontrols.txt index 389c34043..b0f97cdbc 100644 --- a/wadsrc/static/zscript/menu/playercontrols.txt +++ b/wadsrc/static/zscript/menu/playercontrols.txt @@ -71,7 +71,7 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable // //============================================================================= - void InitDirect(int x, int y, int height, int frameofs, String text, Font font, int color, Name command) + void InitDirect(double x, double y, int height, int frameofs, String text, Font font, int color, Name command) { Super.Init(x, y, height, command); mText = text; @@ -113,7 +113,7 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable // //============================================================================= - protected void DrawBorder (int x, int y, int len) + protected void DrawBorder (double x, double y, int len) { let left = TexMan.CheckForTexture("M_LSLEFT", TexMan.Type_MiscPatch); let mid = TexMan.CheckForTexture("M_LSCNTR", TexMan.Type_MiscPatch); @@ -141,7 +141,9 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable } else { - screen.Clear(x, y, x + len, y + SmallFont.GetHeight() * 3/2, 0); + int xx = int(x - 160) * CleanXfac + screen.GetWidth()/2; + int yy = int(y - 100) * CleanXfac + screen.GetHeight()/2; + screen.Clear(xx, yy, xx + len*CleanXfac, yy + SmallFont.GetHeight() * CleanYfac * 3/2, 0); } } } @@ -161,7 +163,7 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable } // Draw player name box - int x = mXpos + mFont.StringWidth(text) + 16 + mFrameSize; + double x = mXpos + mFont.StringWidth(text) + 16 + mFrameSize; DrawBorder (x, mYpos - mFrameSize, MAXPLAYERNAME+1); if (!mEnter) { @@ -186,6 +188,7 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable { Menu.MenuSound ("menu/choose"); mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), mPlayerName, MAXPLAYERNAME, 2, fromcontroller); + mEnter.ActivateMenu(); return true; } else if (mkey == Menu.MKEY_Input) @@ -246,7 +249,7 @@ class ListMenuItemValueText : ListMenuItemSelectable // //============================================================================= - void InitDirect(int x, int y, int height, String text, Font font, int color, int valuecolor, Name command, Name values) + void InitDirect(double x, double y, int height, String text, Font font, int color, int valuecolor, Name command, Name values) { Super.Init(x, y, height, command); mText = text; @@ -337,7 +340,7 @@ class ListMenuItemValueText : ListMenuItemSelectable String text = Stringtable.Localize(mText); screen.DrawText(mFont, selected? OptionMenuSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true); - int x = mXpos + mFont.StringWidth(text) + 8; + double x = mXpos + mFont.StringWidth(text) + 8; if (mSelections.Size() > 0) { screen.DrawText(mFont, mFontColor2, x, mYpos, mSelections[mSelection], DTA_Clean, true); @@ -385,7 +388,7 @@ class ListMenuItemSlider : ListMenuItemSelectable // //============================================================================= - void InitDirect(int x, int y, int height, String text, Font font, int color, Name command, int min, int max, int step) + void InitDirect(double x, double y, int height, String text, Font font, int color, Name command, int min, int max, int step) { Super.Init(x, y, height, command); mText = text; @@ -463,7 +466,7 @@ class ListMenuItemSlider : ListMenuItemSelectable lm.ReleaseFocus(); } - int slide_left = SmallFont.StringWidth ("Green") + 8 + mXpos; + int slide_left = SmallFont.StringWidth ("Green") + 8 + int(mXpos); int slide_right = slide_left + 12*8; // 12 char cells with 8 pixels each. if (type == Menu.MOUSE_Click) @@ -491,7 +494,7 @@ class ListMenuItemSlider : ListMenuItemSelectable // //============================================================================= - protected void DrawSlider (int x, int y) + protected void DrawSlider (double x, double y) { int range = mMaxrange - mMinrange; int cur = mSelection - mMinrange; @@ -515,8 +518,8 @@ class ListMenuItemSlider : ListMenuItemSelectable screen.DrawText(mFont, selected? OptionMenuSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true); - int x = SmallFont.StringWidth ("Green") + 8 + mXpos; - int x2 = SmallFont.StringWidth (text) + 8 + mXpos; + double x = SmallFont.StringWidth ("Green") + 8 + mXpos; + double x2 = SmallFont.StringWidth (text) + 8 + mXpos; DrawSlider (MAX(x2, x), mYpos); } } \ No newline at end of file diff --git a/wadsrc/static/zscript/menu/playerdisplay.txt b/wadsrc/static/zscript/menu/playerdisplay.txt index 492ba978c..da64559c8 100644 --- a/wadsrc/static/zscript/menu/playerdisplay.txt +++ b/wadsrc/static/zscript/menu/playerdisplay.txt @@ -252,8 +252,8 @@ class ListMenuItemPlayerDisplay : ListMenuItem } else { - int x = (mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1); - int y = (mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1); + int x = int(mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1); + int y = int(mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1); screen.DrawTexture(mBackdrop, false, x, y - 1, DTA_DestWidth, 72 * CleanXfac, diff --git a/wadsrc/static/zscript/menu/readthis.txt b/wadsrc/static/zscript/menu/readthis.txt index a3db8a8a7..604fb489e 100644 --- a/wadsrc/static/zscript/menu/readthis.txt +++ b/wadsrc/static/zscript/menu/readthis.txt @@ -33,7 +33,7 @@ ** */ -class ReadThisMenu : Menu +class ReadThisMenu : GenericMenu { int mScreen; int mInfoTic; @@ -43,18 +43,19 @@ class ReadThisMenu : Menu // // //============================================================================= + + override void Init(Menu parent) + { + Super.Init(parent); + mScreen = 1; + mInfoTic = gametic; + } override void Drawer() { double alpha; TextureID tex, prevpic; - if (mScreen == 0) - { - mScreen = 1; - mInfoTic = gametic; - } - // Did the mapper choose a custom help page via MAPINFO? if (level.F1Pic.Length() != 0) {