This commit is contained in:
Rachael Alexanderson 2017-02-19 13:05:32 -05:00
commit 11d6f46e2d
25 changed files with 934 additions and 785 deletions

View file

@ -38,6 +38,7 @@ conversation
page page
{ {
drop = <string>; drop = <string>;
userstring = <string>; New field which can be used to pass data to custom conversation menu classes.
ifitem ifitem
{ {
item = <string>; item = <string>;
@ -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 outright abort on incompatible namespaces fail with a type mismatch error on
one of the specified propeties. one of the specified propeties.
In addition ZDoom defines one new field in the top level of a conversation block:
id = <integer>; 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: ZDoom-format dialogues need to start with the line:
namespace = "ZDoom"; namespace = "ZDoom";
@ -86,6 +83,7 @@ conversation // Starts a dialog.
// the standard conversation ID ('actor' property) is used instead // the standard conversation ID ('actor' property) is used instead
// for this purpose but since 'ZDoom' namespace requires the actor // for this purpose but since 'ZDoom' namespace requires the actor
// to be a class name it needs a separate field for this. // to be a class name it needs a separate field for this.
class = <string>; //Override the default conversation menu class for this conversation.
page page
{ {

View file

@ -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, nomonsters, LEVEL2_NOMONSTERS)
DEFINE_FIELD_BIT(FLevelLocals, flags2, frozen, LEVEL2_FROZEN) DEFINE_FIELD_BIT(FLevelLocals, flags2, frozen, LEVEL2_FROZEN)
DEFINE_FIELD_BIT(FLevelLocals, flags2, infinite_flight, LEVEL2_INFINITE_FLIGHT) DEFINE_FIELD_BIT(FLevelLocals, flags2, infinite_flight, LEVEL2_INFINITE_FLIGHT)
DEFINE_FIELD_BIT(FLevelLocals, flags2, no_dlg_freeze, LEVEL2_CONV_SINGLE_UNFREEZE)
//========================================================================== //==========================================================================
// //

View file

@ -52,6 +52,7 @@ DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, ArmorIcon2)
DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, gametype) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, gametype)
DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, norandomplayerclass) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, norandomplayerclass)
DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, infoPages) DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, infoPages)
DEFINE_FIELD_X(GameInfoStruct, gameinfo_t, mBackButton)
const char *GameNames[17] = const char *GameNames[17] =
@ -362,6 +363,7 @@ void FMapInfoParser::ParseGameInfo()
GAMEINFOKEY_INT(TextScreenX, "textscreenx") GAMEINFOKEY_INT(TextScreenX, "textscreenx")
GAMEINFOKEY_INT(TextScreenY, "textscreeny") GAMEINFOKEY_INT(TextScreenY, "textscreeny")
GAMEINFOKEY_STRING(DefaultEndSequence, "defaultendsequence") GAMEINFOKEY_STRING(DefaultEndSequence, "defaultendsequence")
GAMEINFOKEY_STRING(DefaultConversationMenuClass, "defaultconversationmenuclass")
GAMEINFOKEY_FONT(mStatscreenMapNameFont, "statscreen_mapnamefont") GAMEINFOKEY_FONT(mStatscreenMapNameFont, "statscreen_mapnamefont")
GAMEINFOKEY_FONT(mStatscreenFinishedFont, "statscreen_finishedfont") GAMEINFOKEY_FONT(mStatscreenFinishedFont, "statscreen_finishedfont")
GAMEINFOKEY_FONT(mStatscreenEnteringFont, "statscreen_enteringfont") GAMEINFOKEY_FONT(mStatscreenEnteringFont, "statscreen_enteringfont")

View file

@ -172,6 +172,7 @@ struct gameinfo_t
double gibfactor; double gibfactor;
int TextScreenX; int TextScreenX;
int TextScreenY; int TextScreenY;
FName DefaultConversationMenuClass;
FName DefaultEndSequence; FName DefaultEndSequence;
FString mMapArrow, mCheatMapArrow; FString mMapArrow, mCheatMapArrow;
FString mEasyKey, mCheatKey; FString mEasyKey, mCheatKey;

View file

@ -160,56 +160,15 @@ DMenu::DMenu(DMenu *parent)
mParentMenu = parent; mParentMenu = parent;
mMouseCapture = false; mMouseCapture = false;
mBackbuttonSelected = false; mBackbuttonSelected = false;
DontDim = false;
GC::WriteBarrier(this, parent); 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) bool DMenu::CallResponder(event_t *ev)
{ {
@ -221,7 +180,7 @@ bool DMenu::CallResponder(event_t *ev)
GlobalVMStack.Call(func, params, 2, &ret, 1, nullptr); GlobalVMStack.Call(func, params, 2, &ret, 1, nullptr);
return !!retval; 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) bool DMenu::CallMenuEvent(int mkey, bool fromcontroller)
{ {
IFVIRTUAL(DMenu, MenuEvent) IFVIRTUAL(DMenu, MenuEvent)
@ -263,7 +199,7 @@ bool DMenu::CallMenuEvent(int mkey, bool fromcontroller)
GlobalVMStack.Call(func, params, 3, &ret, 1, nullptr); GlobalVMStack.Call(func, params, 3, &ret, 1, nullptr);
return !!retval; 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 () void DMenu::Close ()
{ {
if (CurrentMenu == nullptr) return; // double closing can happen in the save menu. if (CurrentMenu == nullptr) return; // double closing can happen in the save menu.
@ -287,108 +232,19 @@ void DMenu::Close ()
} }
} }
//============================================================================= DEFINE_ACTION_FUNCTION(DMenu, Close)
//
//
//
//=============================================================================
bool DMenu::MouseEvent(int type, int x, int y)
{
return true;
}
DEFINE_ACTION_FUNCTION(DMenu, MouseEvent)
{ {
PARAM_SELF_PROLOGUE(DMenu); PARAM_SELF_PROLOGUE(DMenu);
PARAM_INT(type); self->Close();
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();
return 0; return 0;
} }
//=============================================================================
//
//
//
//=============================================================================
void DMenu::CallTicker() void DMenu::CallTicker()
{ {
IFVIRTUAL(DMenu, Ticker) IFVIRTUAL(DMenu, Ticker)
@ -396,38 +252,9 @@ void DMenu::CallTicker()
VMValue params[] = { (DObject*)this }; VMValue params[] = { (DObject*)this };
GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); 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() void DMenu::CallDrawer()
{ {
IFVIRTUAL(DMenu, Drawer) IFVIRTUAL(DMenu, Drawer)
@ -435,19 +262,6 @@ void DMenu::CallDrawer()
VMValue params[] = { (DObject*)this }; VMValue params[] = { (DObject*)this };
GlobalVMStack.Call(func, params, 1, nullptr, 0, nullptr); 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() bool DMenu::TranslateKeyboardEvents()
@ -504,7 +318,11 @@ void M_StartControlPanel (bool makeSound)
void M_ActivateMenu(DMenu *menu) void M_ActivateMenu(DMenu *menu)
{ {
if (menuactive == MENU_Off) menuactive = MENU_On; if (menuactive == MENU_Off) menuactive = MENU_On;
if (CurrentMenu != nullptr) CurrentMenu->ReleaseCapture(); if (CurrentMenu != nullptr && CurrentMenu->mMouseCapture)
{
CurrentMenu->mMouseCapture = false;
I_ReleaseMouseCapture();
}
CurrentMenu = menu; CurrentMenu = menu;
GC::WriteBarrier(CurrentMenu); GC::WriteBarrier(CurrentMenu);
} }
@ -651,10 +469,15 @@ void M_SetMenu(FName menu, int param)
const PClass *menuclass = PClass::FindClass(menu); const PClass *menuclass = PClass::FindClass(menu);
if (menuclass != nullptr) if (menuclass != nullptr)
{ {
if (menuclass->IsDescendantOf(RUNTIME_CLASS(DMenu))) if (menuclass->IsDescendantOf("GenericMenu"))
{ {
DMenu *newmenu = (DMenu*)menuclass->CreateNew(); 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); M_ActivateMenu(newmenu);
return; return;
} }
@ -928,7 +751,7 @@ void M_Drawer (void)
if (CurrentMenu != nullptr && menuactive != MENU_Off) if (CurrentMenu != nullptr && menuactive != MENU_Off)
{ {
if (CurrentMenu->DimAllowed()) if (!CurrentMenu->DontDim)
{ {
screen->Dim(fade); screen->Dim(fade);
V_SetBorderNeedRefresh(); V_SetBorderNeedRefresh();
@ -946,10 +769,11 @@ void M_Drawer (void)
void M_ClearMenus() void M_ClearMenus()
{ {
M_DemoNoPlay = false; M_DemoNoPlay = false;
if (CurrentMenu != nullptr) while (CurrentMenu != nullptr)
{ {
DMenu* parent = CurrentMenu->mParentMenu;
CurrentMenu->Destroy(); CurrentMenu->Destroy();
CurrentMenu = nullptr; CurrentMenu = parent;
} }
V_SetBorderNeedRefresh(); V_SetBorderNeedRefresh();
menuactive = MENU_Off; menuactive = MENU_Off;
@ -1203,6 +1027,7 @@ CCMD(undocolorpic)
DEFINE_FIELD(DMenu, mParentMenu) DEFINE_FIELD(DMenu, mParentMenu)
DEFINE_FIELD(DMenu, mMouseCapture); DEFINE_FIELD(DMenu, mMouseCapture);
DEFINE_FIELD(DMenu, mBackbuttonSelected); DEFINE_FIELD(DMenu, mBackbuttonSelected);
DEFINE_FIELD(DMenu, DontDim);
DEFINE_FIELD(DMenuDescriptor, mMenuName) DEFINE_FIELD(DMenuDescriptor, mMenuName)
DEFINE_FIELD(DMenuDescriptor, mNetgameMessage) DEFINE_FIELD(DMenuDescriptor, mNetgameMessage)
@ -1292,7 +1117,7 @@ DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBi
return (DMenuItemBase*)p; 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 c = PClass::FindClass("ListMenuItemPatchItem");
auto p = c->CreateNew(); auto p = c->CreateNew();
@ -1302,7 +1127,7 @@ DMenuItemBase * CreateListMenuItemPatch(int x, int y, int height, int hotkey, FT
return (DMenuItemBase*)p; 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 c = PClass::FindClass("ListMenuItemTextItem");
auto p = c->CreateNew(); auto p = c->CreateNew();

View file

@ -138,11 +138,11 @@ class DListMenuDescriptor : public DMenuDescriptor
public: public:
TArray<DMenuItemBase *> mItems; TArray<DMenuItemBase *> mItems;
int mSelectedItem; int mSelectedItem;
int mSelectOfsX; double mSelectOfsX;
int mSelectOfsY; double mSelectOfsY;
FTextureID mSelector; FTextureID mSelector;
int mDisplayTop; int mDisplayTop;
int mXpos, mYpos; double mXpos, mYpos;
int mWLeft, mWRight; int mWLeft, mWRight;
int mLinespacing; // needs to be stored for dynamically created menus int mLinespacing; // needs to be stored for dynamically created menus
int mAutoselect; // this can only be set by internal menu creation functions int mAutoselect; // this can only be set by internal menu creation functions
@ -263,42 +263,19 @@ public:
MOUSE_Release MOUSE_Release
}; };
enum
{
BACKBUTTON_TIME = 4*TICRATE
};
TObjPtr<DMenu> mParentMenu; TObjPtr<DMenu> mParentMenu;
bool mMouseCapture; bool mMouseCapture;
bool mBackbuttonSelected; bool mBackbuttonSelected;
bool DontDim;
DMenu(DMenu *parent = NULL); 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(); bool TranslateKeyboardEvents();
virtual void Close(); 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 CallResponder(event_t *ev);
bool CallMenuEvent(int mkey, bool fromcontroller); bool CallMenuEvent(int mkey, bool fromcontroller);
bool CallMouseEvent(int type, int x, int y);
void CallTicker(); void CallTicker();
void CallDrawer(); 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) DECLARE_CLASS(DMenuItemBase, DObject)
public: public:
int mXpos, mYpos; double mXpos, mYpos;
FNameNoInit mAction; FNameNoInit mAction;
bool mEnabled; bool mEnabled;
@ -321,7 +298,7 @@ public:
bool SetValue(int i, int value); bool SetValue(int i, int value);
bool GetValue(int i, int *pvalue); bool GetValue(int i, int *pvalue);
void OffsetPositionY(int ydelta) { mYpos += ydelta; } 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 * CreateOptionMenuItemSubmenu(const char *label, FName cmd, int center);
DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings); DMenuItemBase * CreateOptionMenuItemControl(const char *label, FName cmd, FKeyBindings *bindings);
DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy); DMenuItemBase * CreateOptionMenuItemJoyConfigMenu(const char *label, IJoystickConfig *joy);
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);
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);
#endif #endif

View file

@ -305,11 +305,11 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
sc.MustGetString(); sc.MustGetString();
desc->mSelector = GetMenuTexture(sc.String); desc->mSelector = GetMenuTexture(sc.String);
sc.MustGetStringName(","); sc.MustGetStringName(",");
sc.MustGetNumber(); sc.MustGetFloat();
desc->mSelectOfsX = sc.Number; desc->mSelectOfsX = sc.Float;
sc.MustGetStringName(","); sc.MustGetStringName(",");
sc.MustGetNumber(); sc.MustGetFloat();
desc->mSelectOfsY = sc.Number; desc->mSelectOfsY = sc.Float;
} }
else if (sc.Compare("Linespacing")) else if (sc.Compare("Linespacing"))
{ {
@ -318,11 +318,11 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
} }
else if (sc.Compare("Position")) else if (sc.Compare("Position"))
{ {
sc.MustGetNumber(); sc.MustGetFloat();
desc->mXpos = sc.Number; desc->mXpos = sc.Float;
sc.MustGetStringName(","); sc.MustGetStringName(",");
sc.MustGetNumber(); sc.MustGetFloat();
desc->mYpos = sc.Number; desc->mYpos = sc.Float;
} }
else if (sc.Compare("Centermenu")) else if (sc.Compare("Centermenu"))
{ {
@ -369,7 +369,7 @@ static void ParseListMenuBody(FScanner &sc, DListMenuDescriptor *desc)
PClass *cls = PClass::FindClass(buildname); PClass *cls = PClass::FindClass(buildname);
if (cls != nullptr && cls->IsDescendantOf("ListMenuItem")) if (cls != nullptr && cls->IsDescendantOf("ListMenuItem"))
{ {
auto func = dyn_cast<PFunction>(cls->Symbols.FindSymbol("Init", false)); auto func = dyn_cast<PFunction>(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. 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; auto &args = func->Variants[0].Proto->ArgumentTypes;
@ -741,7 +741,7 @@ static void ParseOptionMenuBody(FScanner &sc, DOptionMenuDescriptor *desc)
PClass *cls = PClass::FindClass(buildname); PClass *cls = PClass::FindClass(buildname);
if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem")) if (cls != nullptr && cls->IsDescendantOf("OptionMenuItem"))
{ {
auto func = dyn_cast<PFunction>(cls->Symbols.FindSymbol("Init", false)); auto func = dyn_cast<PFunction>(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. 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; auto &args = func->Variants[0].Proto->ArgumentTypes;
@ -973,13 +973,13 @@ static void BuildEpisodeMenu()
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{ {
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc); DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
int posy = ld->mYpos; int posy = (int)ld->mYpos;
int topy = posy; int topy = posy;
// Get lowest y coordinate of any static item in the menu // Get lowest y coordinate of any static item in the menu
for(unsigned i = 0; i < ld->mItems.Size(); i++) 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; if (y < topy) topy = y;
} }
@ -1073,13 +1073,13 @@ static void BuildPlayerclassMenu()
// add player display // add player display
ld->mSelectedItem = ld->mItems.Size(); ld->mSelectedItem = ld->mItems.Size();
int posy = ld->mYpos; int posy = (int)ld->mYpos;
int topy = posy; int topy = posy;
// Get lowest y coordinate of any static item in the menu // Get lowest y coordinate of any static item in the menu
for(unsigned i = 0; i < ld->mItems.Size(); i++) 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; if (y < topy) topy = y;
} }
@ -1333,8 +1333,8 @@ void M_StartupSkillMenu(FGameStartup *gs)
if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor))) if ((*desc)->IsKindOf(RUNTIME_CLASS(DListMenuDescriptor)))
{ {
DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc); DListMenuDescriptor *ld = static_cast<DListMenuDescriptor*>(*desc);
int x = ld->mXpos; int x = (int)ld->mXpos;
int y = ld->mYpos; int y = (int)ld->mYpos;
// Delete previous contents // Delete previous contents
for(unsigned i=0; i<ld->mItems.Size(); i++) for(unsigned i=0; i<ld->mItems.Size(); i++)
@ -1363,7 +1363,7 @@ void M_StartupSkillMenu(FGameStartup *gs)
// Get lowest y coordinate of any static item in the menu // Get lowest y coordinate of any static item in the menu
for(unsigned i = 0; i < ld->mItems.Size(); i++) 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; if (y < topy) topy = y;
} }
@ -1380,7 +1380,7 @@ void M_StartupSkillMenu(FGameStartup *gs)
{ {
ld->mItems[i]->OffsetPositionY(topdelta); ld->mItems[i]->OffsetPositionY(topdelta);
} }
y = ld->mYpos = posy - topdelta; ld->mYpos = y = posy - topdelta;
} }
} }
else else

View file

@ -653,6 +653,7 @@ xx(Link)
xx(Goodbye) xx(Goodbye)
xx(Require) xx(Require)
xx(Exclude) xx(Exclude)
xx(Userstring)
// Special menus // Special menus
xx(Mainmenu) xx(Mainmenu)

View file

@ -61,6 +61,7 @@
#include "p_local.h" #include "p_local.h"
#include "menu/menu.h" #include "menu/menu.h"
#include "g_levellocals.h" #include "g_levellocals.h"
#include "virtual.h"
// The conversations as they exist inside a SCRIPTxx lump. // The conversations as they exist inside a SCRIPTxx lump.
struct Response struct Response
@ -112,7 +113,6 @@ static FDialogueMap ClassRoots;
static int ConversationMenuY; static int ConversationMenuY;
static int ConversationPauseTic; static int ConversationPauseTic;
static bool ShowGold;
static int StaticLastReply; static int StaticLastReply;
static bool LoadScriptFile(int lumpnum, FileReader *lump, int numnodes, bool include, int type); 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; static FStrifeDialogueNode *PrevNode;
#define NUM_RANDOM_LINES 10
#define NUM_RANDOM_GOODBYES 3
//============================================================================ //============================================================================
// //
// GetStrifeType // GetStrifeType
@ -352,7 +349,7 @@ static FStrifeDialogueNode *ReadRetailNode (FileReader *lump, DWORD &prevSpeaker
// The speaker's portrait, if any. // The speaker's portrait, if any.
speech.Dialogue[0] = 0; //speech.Backdrop[8] = 0; 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. // The speaker's voice for this node, if any.
speech.Backdrop[0] = 0; //speech.Sound[8] = 0; speech.Backdrop[0] = 0; //speech.Sound[8] = 0;
@ -426,7 +423,7 @@ static FStrifeDialogueNode *ReadTeaserNode (FileReader *lump, DWORD &prevSpeaker
node->Dialogue = speech.Dialogue; node->Dialogue = speech.Dialogue;
// The Teaser version doesn't have portraits. // The Teaser version doesn't have portraits.
node->Backdrop.SetInvalid(); node->Backdrop = "";
// The speaker's voice for this node, if any. // The speaker's voice for this node, if any.
if (speech.VoiceNumber != 0) if (speech.VoiceNumber != 0)
@ -690,8 +687,18 @@ static bool ShouldSkipReply(FStrifeDialogueReply *reply, player_t *player)
return false; 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) switch (node)
{ {
case -1: case -1:
@ -709,6 +716,7 @@ static void SendConversationReply(int node, int reply)
break; break;
} }
StaticLastReply = reply; 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<FString> mResponseLines;
TArray<unsigned int> 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<int> (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<mResponses.Size(); i++)
{
if ((int)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;
}
//=============================================================================
//
//
//
//=============================================================================
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<int>(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 // P_FreeStrifeConversations
@ -1184,7 +760,7 @@ void P_FreeStrifeConversations ()
ClassRoots.Clear(); ClassRoots.Clear();
PrevNode = NULL; PrevNode = NULL;
if (CurrentMenu != NULL && CurrentMenu->IsKindOf(RUNTIME_CLASS(DConversationMenu))) if (CurrentMenu != NULL && CurrentMenu->IsKindOf("ConversationMenu"))
{ {
CurrentMenu->Close(); 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); 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) if (CurNode != PrevNode)
{ // Only reset the selection if showing a different menu. { // 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 // And open the menu
M_StartControlPanel (false); M_StartControlPanel (false);
M_ActivateMenu(cmenu); M_ActivateMenu((DMenu*)cmenu);
ConversationPauseTic = gametic + 20;
menuactive = MENU_OnNoPause; 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 // 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. // doesn't happen during demo playback, so we need to do it here.
if (demoplayback && CurrentMenu != NULL && if (demoplayback && CurrentMenu != NULL && CurrentMenu->IsKindOf("ConversationMenu"))
CurrentMenu->IsKindOf(RUNTIME_CLASS(DConversationMenu)))
{ {
CurrentMenu->Close(); 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);

View file

@ -28,11 +28,13 @@ struct FStrifeDialogueNode
PClassActor *SpeakerType; PClassActor *SpeakerType;
FString SpeakerName; FString SpeakerName;
FSoundID SpeakerVoice; FSoundID SpeakerVoice;
FTextureID Backdrop; FString Backdrop;
FString Dialogue; FString Dialogue;
FString Goodbye; // must init to null for binary scripts to work as intended FString Goodbye; // must init to null for binary scripts to work as intended
FStrifeDialogueReply *Children; FStrifeDialogueReply *Children;
FName MenuClassName;
FString UserData;
}; };
// FStrifeDialogueReply holds responses the player can give to the NPC // FStrifeDialogueReply holds responses the player can give to the NPC

View file

@ -316,7 +316,14 @@ class USDFParser : public UDMFParserBase
break; break;
case NAME_Panel: 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; break;
case NAME_Voice: case NAME_Voice:
@ -391,6 +398,7 @@ class USDFParser : public UDMFParserBase
{ {
PClassActor *type = NULL; PClassActor *type = NULL;
int dlgid = -1; int dlgid = -1;
FName clsid;
unsigned int startpos = StrifeDialogues.Size(); unsigned int startpos = StrifeDialogues.Size();
while (!sc.CheckToken('}')) while (!sc.CheckToken('}'))
@ -415,6 +423,13 @@ class USDFParser : public UDMFParserBase
dlgid = CheckInt(key); dlgid = CheckInt(key);
} }
break; break;
case NAME_Class:
if (namespace_bits == Zd)
{
clsid = CheckString(key);
}
break;
} }
} }
else else
@ -440,6 +455,7 @@ class USDFParser : public UDMFParserBase
for(;startpos < StrifeDialogues.Size(); startpos++) for(;startpos < StrifeDialogues.Size(); startpos++)
{ {
StrifeDialogues[startpos]->SpeakerType = type; StrifeDialogues[startpos]->SpeakerType = type;
StrifeDialogues[startpos]->MenuClassName = clsid;
} }
return true; return true;
} }

View file

@ -63,6 +63,9 @@
static TArray<FPropertyInfo*> properties; static TArray<FPropertyInfo*> properties;
static TArray<AFuncDesc> AFTable; static TArray<AFuncDesc> AFTable;
static TArray<FieldDesc> FieldTable; static TArray<FieldDesc> 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); fieldptr = new PField("gametic", TypeSInt32, VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&gametic);
Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); 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. // 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' // 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!!! // 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); fieldptr = new PField("WP_NOCHANGE", NewPointer(RUNTIME_CLASS(AWeapon), false), VARF_Native | VARF_Static | VARF_ReadOnly, (intptr_t)&wpnochg);
Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr); Namespaces.GlobalNamespace->Symbols.AddSymbol(fieldptr);
@ -1216,6 +1228,14 @@ DEFINE_ACTION_FUNCTION(FStringStruct, Mid)
ACTION_RETURN_STRING(s); 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) DEFINE_ACTION_FUNCTION(FStringStruct, Truncate)
{ {
PARAM_SELF_STRUCT_PROLOGUE(FString); PARAM_SELF_STRUCT_PROLOGUE(FString);

View file

@ -23,6 +23,7 @@
#include "zscript/menu/textentermenu.txt" #include "zscript/menu/textentermenu.txt"
#include "zscript/menu/videomenu.txt" #include "zscript/menu/videomenu.txt"
#include "zscript/menu/readthis.txt" #include "zscript/menu/readthis.txt"
#include "zscript/menu/conversationmenu.txt"
#include "zscript/inventory/inventory.txt" #include "zscript/inventory/inventory.txt"
#include "zscript/inventory/inv_misc.txt" #include "zscript/inventory/inv_misc.txt"

View file

@ -300,6 +300,7 @@ struct GameInfoStruct native
native int gametype; native int gametype;
native bool norandomplayerclass; native bool norandomplayerclass;
native Array<Name> infoPages; native Array<Name> infoPages;
native String mBackButton;
} }
class Object native class Object native
@ -463,6 +464,7 @@ struct LevelLocals native
native bool nomonsters; native bool nomonsters;
native bool frozen; native bool frozen;
native bool infinite_flight; native bool infinite_flight;
native bool no_dlg_freeze;
// level_info_t *info cannot be done yet. // level_info_t *info cannot be done yet.
native String GetUDMFString(int type, int index, Name key); native String GetUDMFString(int type, int index, Name key);
@ -579,6 +581,7 @@ struct StringStruct native
native vararg void AppendFormat(String fmt, ...); native vararg void AppendFormat(String fmt, ...);
native void Replace(String pattern, String replacement); native void Replace(String pattern, String replacement);
native String Left(int len);
native String Mid(int pos = 0, int len = 2147483647); native String Mid(int pos = 0, int len = 2147483647);
native void Truncate(int newlen); native void Truncate(int newlen);
native String CharAt(int pos); native String CharAt(int pos);

View file

@ -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<Actor> DropType;
native int ThisNodeNum;
native int ItemCheckNode;
native Class<Actor> 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<Actor> 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<String> mResponseLines;
Array<uint> 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;
}
}
}

View file

@ -4,11 +4,11 @@ class ListMenuDescriptor : MenuDescriptor native
{ {
native Array<ListMenuItem> mItems; native Array<ListMenuItem> mItems;
native int mSelectedItem; native int mSelectedItem;
native int mSelectOfsX; native double mSelectOfsX;
native int mSelectOfsY; native double mSelectOfsY;
native TextureID mSelector; native TextureID mSelector;
native int mDisplayTop; native int mDisplayTop;
native int mXpos, mYpos; native double mXpos, mYpos;
native int mWLeft, mWRight; native int mWLeft, mWRight;
native int mLinespacing; // needs to be stored for dynamically created menus native int mLinespacing; // needs to be stored for dynamically created menus
native int mAutoselect; // this can only be set by internal menu creation functions 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) virtual void Init(Menu parent = NULL, ListMenuDescriptor desc = NULL)
{ {
mParentMenu = parent; Super.Init(parent);
mDesc = desc; mDesc = desc;
if (desc.mCenter) if (desc.mCenter)
{ {
int center = 160; double center = 160;
for(int i=0; i < mDesc.mItems.Size(); i++) 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 width = mDesc.mItems[i].GetWidth();
int curx = mDesc.mSelectOfsX; double curx = mDesc.mSelectOfsX;
if (width > 0 && mDesc.mItems[i].Selectable()) 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; if (left < center) center = left;
} }
} }

View file

@ -35,7 +35,7 @@
class ListMenuItem : MenuItemBase class ListMenuItem : MenuItemBase
{ {
void DrawSelector(int xofs, int yofs, TextureID tex) void DrawSelector(double xofs, double yofs, TextureID tex)
{ {
if (tex.isNull()) if (tex.isNull())
{ {
@ -68,7 +68,7 @@ class ListMenuItemStaticPatch : ListMenuItem
TextureID mTexture; TextureID mTexture;
bool mCentered; 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); Super.Init(x, y);
mTexture = patch; mTexture = patch;
@ -82,17 +82,17 @@ class ListMenuItemStaticPatch : ListMenuItem
return; return;
} }
int x = mXpos; double x = mXpos;
Vector2 vec = TexMan.GetScaledSize(mTexture); Vector2 vec = TexMan.GetScaledSize(mTexture);
if (mYpos >= 0) 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); screen.DrawTexture (mTexture, true, x, mYpos, DTA_Clean, true);
} }
else else
{ {
int x = (mXpos - 160) * CleanXfac + (Screen.GetWidth()>>1); x = (mXpos - 160) * CleanXfac + (Screen.GetWidth()>>1);
if (mCentered) x -= (int(vec.X) * CleanXfac)/2; if (mCentered) x -= (vec.X * CleanXfac)/2;
screen.DrawTexture (mTexture, true, x, -mYpos*CleanYfac, DTA_CleanNoMove, true); screen.DrawTexture (mTexture, true, x, -mYpos*CleanYfac, DTA_CleanNoMove, true);
} }
} }
@ -100,7 +100,7 @@ class ListMenuItemStaticPatch : ListMenuItem
class ListMenuItemStaticPatchCentered : ListMenuItemStaticPatch 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); Super.Init(x, y, patch, true);
} }
@ -119,7 +119,7 @@ class ListMenuItemStaticText : ListMenuItem
int mColor; int mColor;
bool mCentered; 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); Super.Init(x, y);
mText = text; mText = text;
@ -128,7 +128,7 @@ class ListMenuItemStaticText : ListMenuItem
mCentered = false; 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); Super.Init(x, y);
mText = text; mText = text;
@ -144,13 +144,13 @@ class ListMenuItemStaticText : ListMenuItem
String text = Stringtable.Localize(mText); String text = Stringtable.Localize(mText);
if (mYpos >= 0) if (mYpos >= 0)
{ {
int x = mXpos; double x = mXpos;
if (mCentered) x -= mFont.StringWidth(text)/2; if (mCentered) x -= mFont.StringWidth(text)/2;
screen.DrawText(mFont, mColor, x, mYpos, text, DTA_Clean, true); screen.DrawText(mFont, mColor, x, mYpos, text, DTA_Clean, true);
} }
else 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; if (mCentered) x -= (mFont.StringWidth(text) * CleanXfac)/2;
screen.DrawText (mFont, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true); screen.DrawText (mFont, mColor, x, -mYpos*CleanYfac, text, DTA_CleanNoMove, true);
} }
@ -160,7 +160,7 @@ class ListMenuItemStaticText : ListMenuItem
class ListMenuItemStaticTextCentered : ListMenuItemStaticText 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); Super.Init(desc, x, y, text, color);
mCentered = true; mCentered = true;
@ -179,7 +179,7 @@ class ListMenuItemSelectable : ListMenuItem
int mHeight; int mHeight;
int mParam; 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); Super.Init(x, y, childmenu);
mHeight = height; mHeight = height;
@ -250,7 +250,7 @@ class ListMenuItemTextItem : ListMenuItemSelectable
mHotkey = hotkey.CharCodeAt(0); 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); Super.Init(x, y, height, child, param);
mText = text; mText = text;
@ -288,7 +288,7 @@ class ListMenuItemPatchItem : ListMenuItemSelectable
mTexture = patch; 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); Super.Init(x, y, height, child, param);
mHotkey = hotkey.CharCodeAt(0); mHotkey = hotkey.CharCodeAt(0);

View file

@ -89,31 +89,191 @@ class Menu : Object native
native Menu mParentMenu; native Menu mParentMenu;
native bool mMouseCapture; native bool mMouseCapture;
native bool mBackbuttonSelected; native bool mBackbuttonSelected;
native bool DontDim;
void Init(Menu parent)
{
mParentMenu = parent;
}
native static int MenuTime(); native static int MenuTime();
native static void SetVideoMode(); native static void SetVideoMode();
native static Menu GetCurrentMenu(); native static Menu GetCurrentMenu();
native static void SetMenu(Name mnu, int param = 0); native static void SetMenu(Name mnu, int param = 0);
native static void StartMessage(String msg, int mode = 0, Name command = 'none'); 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 bool TranslateKeyboardEvents() { return true; }
virtual void SetFocus(MenuItemBase fc) {} virtual void SetFocus(MenuItemBase fc) {}
virtual bool CheckFocus(MenuItemBase fc) { return false; } virtual bool CheckFocus(MenuItemBase fc) { return false; }
virtual void ReleaseFocus() {} virtual void ReleaseFocus() {}
virtual void ResetColor() {} 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) static void MenuSound(Sound snd)
{ {
@ -136,3 +296,11 @@ class MenuDescriptor : Object native
native static MenuDescriptor GetDescriptor(Name n); 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);
}
}

View file

@ -6,11 +6,11 @@
class MenuItemBase : Object native class MenuItemBase : Object native
{ {
protected native int mXpos, mYpos; protected native double mXpos, mYpos;
protected native Name mAction; protected native Name mAction;
native bool mEnabled; 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; mXpos = xpos;
mYpos = ypos; mYpos = ypos;
@ -36,10 +36,10 @@ class MenuItemBase : Object native
virtual int GetIndent() { return 0; } virtual int GetIndent() { return 0; }
virtual int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) { return indent; } virtual int Draw(OptionMenuDescriptor desc, int y, int indent, bool selected) { return indent; }
void OffsetPositionY(int ydelta) { mYpos += ydelta; } void OffsetPositionY(double ydelta) { mYpos += ydelta; }
int GetY() { return mYpos; } double GetY() { return mYpos; }
int GetX() { return mXpos; } double GetX() { return mXpos; }
void SetX(int x) { mXpos = x; } void SetX(double x) { mXpos = x; }
} }
// this is only used to parse font color ranges in MENUDEF // this is only used to parse font color ranges in MENUDEF

View file

@ -181,12 +181,12 @@ class MessageBoxMenu : Menu
int ch = ev.data1; int ch = ev.data1;
ch = ch >= 65 && ch <91? ch + 32 : ch; ch = ch >= 65 && ch <91? ch + 32 : ch;
if (ch == 78 /*'n'*/ || ch == 32) if (ch == 110 /*'n'*/ || ch == 32)
{ {
HandleResult(false); HandleResult(false);
return true; return true;
} }
else if (ch == 89 /*'y'*/) else if (ch == 121 /*'y'*/)
{ {
HandleResult(true); HandleResult(true);
return true; return true;

View file

@ -104,6 +104,7 @@ class OptionMenu : Menu
{ {
mParentMenu = parent; mParentMenu = parent;
mDesc = desc; mDesc = desc;
DontDim = desc.mDontDim;
if (mDesc != NULL && mDesc.mSelectedItem == -1) mDesc.mSelectedItem = FirstSelectable(); if (mDesc != NULL && mDesc.mSelectedItem == -1) mDesc.mSelectedItem = FirstSelectable();
mDesc.CalcIndent(); mDesc.CalcIndent();
} }

View file

@ -1129,6 +1129,7 @@ class OptionMenuItemTextField : OptionMenuFieldBase
{ {
Menu.MenuSound("menu/choose"); Menu.MenuSound("menu/choose");
mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), GetCVarString(), -1, 2, fromcontroller); mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), GetCVarString(), -1, 2, fromcontroller);
mEnter.ActivateMenu();
return true; return true;
} }
else if (mkey == Menu.MKEY_Input) else if (mkey == Menu.MKEY_Input)

View file

@ -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); Super.Init(x, y, height, command);
mText = text; 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 left = TexMan.CheckForTexture("M_LSLEFT", TexMan.Type_MiscPatch);
let mid = TexMan.CheckForTexture("M_LSCNTR", TexMan.Type_MiscPatch); let mid = TexMan.CheckForTexture("M_LSCNTR", TexMan.Type_MiscPatch);
@ -141,7 +141,9 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable
} }
else 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 // 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); DrawBorder (x, mYpos - mFrameSize, MAXPLAYERNAME+1);
if (!mEnter) if (!mEnter)
{ {
@ -186,6 +188,7 @@ class ListMenuItemPlayerNameBox : ListMenuItemSelectable
{ {
Menu.MenuSound ("menu/choose"); Menu.MenuSound ("menu/choose");
mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), mPlayerName, MAXPLAYERNAME, 2, fromcontroller); mEnter = TextEnterMenu.Open(Menu.GetCurrentMenu(), mPlayerName, MAXPLAYERNAME, 2, fromcontroller);
mEnter.ActivateMenu();
return true; return true;
} }
else if (mkey == Menu.MKEY_Input) 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); Super.Init(x, y, height, command);
mText = text; mText = text;
@ -337,7 +340,7 @@ class ListMenuItemValueText : ListMenuItemSelectable
String text = Stringtable.Localize(mText); String text = Stringtable.Localize(mText);
screen.DrawText(mFont, selected? OptionMenuSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true); 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) if (mSelections.Size() > 0)
{ {
screen.DrawText(mFont, mFontColor2, x, mYpos, mSelections[mSelection], DTA_Clean, true); 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); Super.Init(x, y, height, command);
mText = text; mText = text;
@ -463,7 +466,7 @@ class ListMenuItemSlider : ListMenuItemSelectable
lm.ReleaseFocus(); 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. int slide_right = slide_left + 12*8; // 12 char cells with 8 pixels each.
if (type == Menu.MOUSE_Click) 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 range = mMaxrange - mMinrange;
int cur = mSelection - 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); screen.DrawText(mFont, selected? OptionMenuSettings.mFontColorSelection : mFontColor, mXpos, mYpos, text, DTA_Clean, true);
int x = SmallFont.StringWidth ("Green") + 8 + mXpos; double x = SmallFont.StringWidth ("Green") + 8 + mXpos;
int x2 = SmallFont.StringWidth (text) + 8 + mXpos; double x2 = SmallFont.StringWidth (text) + 8 + mXpos;
DrawSlider (MAX(x2, x), mYpos); DrawSlider (MAX(x2, x), mYpos);
} }
} }

View file

@ -252,8 +252,8 @@ class ListMenuItemPlayerDisplay : ListMenuItem
} }
else else
{ {
int x = (mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1); int x = int(mXpos - 160) * CleanXfac + (screen.GetWidth() >> 1);
int y = (mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1); int y = int(mYpos - 100) * CleanYfac + (screen.GetHeight() >> 1);
screen.DrawTexture(mBackdrop, false, x, y - 1, screen.DrawTexture(mBackdrop, false, x, y - 1,
DTA_DestWidth, 72 * CleanXfac, DTA_DestWidth, 72 * CleanXfac,

View file

@ -33,7 +33,7 @@
** **
*/ */
class ReadThisMenu : Menu class ReadThisMenu : GenericMenu
{ {
int mScreen; int mScreen;
int mInfoTic; int mInfoTic;
@ -44,17 +44,18 @@ class ReadThisMenu : Menu
// //
//============================================================================= //=============================================================================
override void Init(Menu parent)
{
Super.Init(parent);
mScreen = 1;
mInfoTic = gametic;
}
override void Drawer() override void Drawer()
{ {
double alpha; double alpha;
TextureID tex, prevpic; TextureID tex, prevpic;
if (mScreen == 0)
{
mScreen = 1;
mInfoTic = gametic;
}
// Did the mapper choose a custom help page via MAPINFO? // Did the mapper choose a custom help page via MAPINFO?
if (level.F1Pic.Length() != 0) if (level.F1Pic.Length() != 0)
{ {