// internal "knowledge base" and more enum EKMenuKey { MK_DOWN, MK_UP, MK_LEFT, MK_RIGHT, MK_ENTER, MK_BACK }; enum EKMouseButton { MB_LEFT, MB_RIGHT, MB_WHEELUP, MB_WHEELDOWN, MB_DRAG, MB_RELEASE } Class DemolitionistMenu : GenericMenu { TextureID FancyBg, FrameTex, VSepTex, HSepTex; // for resolution scaling and such double hs; Vector2 ss, ws, origin; // temporary bottom messages, such as "not enough money" String tmsg; int tmsgtic; // money owned, for store int muns; // other text String clockstr, munstr; // cached pointers to mod handlers SWWMHandler hnd; SWWMStaticHandler shnd; // seeeeecret int kcode; // crimey clock stuff int c_year, c_month, c_day, c_hour, c_minute; String c_tz; // mouse stuff Vector2 curmouse; bool isrclick; // somehow Drawer can be called while closing prematurely, which is big bollocks bool isclosing, forceclose; // the tabs Array tabs; int curtab; int oldtab; // used for returning from help tab // fonts Font mSmallFont, mTinyFont; // for open/close animation double animtimer; int GenTUID() { return lasttuid++; } // tab functions int FindTabType( Class t, bool nothidden = false ) { for ( int i=0; i=0; i-- ) { if ( tabs[i].bHidden ) continue; return i; } return -1; } int GetNextTab() { int lst = GetLastTab(); if ( lst == -1 ) return -1; if ( curtab >= lst ) return GetFirstTab(); for ( int i=curtab+1; i=0; i-- ) { if ( tabs[i].bHidden ) continue; return i; } return -1; } private void DoClose() { EventHandler.SendNetworkEvent("swwmclearalltransactions",consoleplayer); for ( int i=0; i deftabs[] = { 'DemolitionistMissionTab', 'DemolitionistStatsTab', 'DemolitionistInventoryTab', 'DemolitionistKeychainTab', 'DemolitionistLibraryTab', 'DemolitionistStoreTab', 'DemolitionistHelpTab', 'DemolitionistSecretTab' }; // since GZDoom 4.10 we have to "pre-tick" tabs as soon as they're initialized for ( int i=0; i)(cls); if ( !ct || (ct.GetParentClass() != 'DemolitionistMenuTabCustom') ) continue; let t = DemolitionistMenuTab(new(ct)).Init(self); t.Ticker(); tabs.Push(t); } for ( int i=deftabs.Size()-2; i saved = shnd.menustate.Get("LastTab"); if ( saved ) curtab = FindTabType(saved,true); } if ( curtab == -1 ) curtab = GetFirstTab(); tabs[curtab].OnSelect(); } override bool MenuEvent( int mkey, bool fromcontroller ) { if ( isclosing || (animtimer < 1.) ) return false; // pachinko code only handled if the tab lacks direct input if ( !tabs[curtab].bDirectInput ) { switch ( kcode ) { case 0: case 1: if ( mkey == MKEY_UP ) kcode++; else kcode = 0; break; case 2: case 3: if ( mkey == MKEY_DOWN ) kcode++; else kcode = 0; break; case 4: case 6: if ( mkey == MKEY_LEFT ) kcode++; else kcode = 0; break; case 5: case 7: if ( mkey == MKEY_RIGHT ) kcode++; else kcode = 0; break; case 10: if ( mkey == MKEY_ENTER ) { int secret = FindTabType('DemolitionistSecretTab'); if ( curtab != secret ) { MenuSound("menu/demosecret"); tabs[curtab].OnDeselect(); curtab = secret; tabs[curtab].OnSelect(); } } default: kcode = 0; break; } } switch ( mkey ) { case MKEY_BACK: isclosing = true; MenuSound("menu/democlose"); return true; case MKEY_PAGEDOWN: int next = GetNextTab(); if ( next != curtab ) { MenuSound("menu/demotab"); tabs[curtab].OnDeselect(); curtab = next; tabs[curtab].OnSelect(); } return true; case MKEY_PAGEUP: int prev = GetPrevTab(); if ( prev != curtab ) { MenuSound("menu/demotab"); tabs[curtab].OnDeselect(); curtab = prev; tabs[curtab].OnSelect(); } return true; case MKEY_DOWN: tabs[curtab].MenuInput(MK_DOWN); return true; case MKEY_UP: tabs[curtab].MenuInput(MK_UP); return true; case MKEY_LEFT: tabs[curtab].MenuInput(MK_LEFT); return true; case MKEY_RIGHT: tabs[curtab].MenuInput(MK_RIGHT); return true; case MKEY_ENTER: tabs[curtab].MenuInput(MK_ENTER); return true; case MKEY_CLEAR: tabs[curtab].MenuInput(MK_BACK); return true; } return Super.MenuEvent(mkey,fromcontroller); } override void Ticker() { Super.Ticker(); if ( (players[consoleplayer].Health <= 0) && !isclosing ) { MenuSound("menu/democlose"); isclosing = true; } if ( (gamestate != GS_LEVEL) || paused || (isclosing && (animtimer <= 0.)) ) { // ded (or force close) DoClose(); Close(); return; } if ( swwm_menupause ) menuactive = Menu.On; else menuactive = Menu.OnNoPause; // forcibly tick hud (mainly so interpolators can still update in the background) if ( !multiplayer && (menuactive == Menu.On) ) { let bar = SWWMStatusBar(StatusBar); if ( bar ) bar.TickInterpolators(); } // update time string clockstr = CrimeTime(c_year,c_month,c_day,c_hour,c_minute,c_tz); // update money muns = SWWMCredits.Get(players[consoleplayer]); munstr = String.Format("\cg„\c-%09d",muns); if ( isclosing ) { if ( animtimer >= 1. ) DontDim = DontBlur = true; animtimer -= .1; return; } if ( animtimer < 1. ) { animtimer += .1; if ( animtimer < 1. ) return; // ensure tabs tick before first draw else DontDim = DontBlur = false; } if ( (tabs.Size() <= 0) || (curtab == -1) || !tabs[curtab] ) return; tabs[curtab].Ticker(); } override bool MouseEvent( int type, int mx, int my ) { if ( isclosing || (animtimer < 1.) ) return false; bool res = Super.MouseEvent(type,mx,my); Vector2 mpos = (mx/hs,my/hs)-origin; if ( type == MOUSE_Click ) { // outside clickable area if ( (mpos.x < 0) || (mpos.x > ws.x) || (mpos.y < 0) || (mpos.y > ws.y-14) ) return res; else if ( mpos.y < 14 ) { if ( isrclick ) return res; // check which tab we're clicking int xx = 0; String str; int len; for ( int i=0; i= xx) && (mpos.x < xx+len) ) { if ( curtab == i ) break; MenuSound("menu/demotab"); tabs[curtab].OnDeselect(); curtab = i; tabs[curtab].OnSelect(); break; } xx += len; } return res; } else if ( (tabs.Size() <= 0) || (curtab == -1) || !tabs[curtab] ) return res; tabs[curtab].MouseInput(mpos,isrclick?MB_RIGHT:MB_LEFT); } else if ( type == MOUSE_Move ) { if ( (tabs.Size() <= 0) || (curtab == -1) || !tabs[curtab] ) return res; tabs[curtab].MouseInput(mpos,MB_DRAG); } else if ( type == MOUSE_Release ) { if ( (tabs.Size() <= 0) || (curtab == -1) || !tabs[curtab] ) return res; tabs[curtab].MouseInput(mpos,MB_RELEASE); } return res; } override bool OnUiEvent( UIEvent ev ) { if ( isclosing || (animtimer < 1.) ) return false; if ( tabs[curtab].bDirectInput && ((ev.type == UIEvent.Type_KeyDown) || (ev.type == UIEvent.Type_KeyUp)) ) return tabs[curtab].DirectInput(ev); switch ( ev.type ) { case UIEvent.Type_KeyDown: if ( ev.keychar == UiEvent.Key_F1 ) { int help = FindTabType('DemolitionistHelpTab'); if ( curtab == help ) { tabs[curtab].OnDeselect(); curtab = oldtab; tabs[curtab].OnSelect(); } else { int secret = FindTabType('DemolitionistSecretTab'); if ( curtab == secret ) oldtab = GetLastTab(); else oldtab = curtab; tabs[curtab].OnDeselect(); curtab = help; tabs[curtab].OnSelect(); } MenuSound("menu/demotab"); return true; } switch ( kcode ) { case 8: if ( ev.keystring ~== "B" ) kcode++; else kcode = 0; break; case 9: if ( ev.keystring ~== "A" ) kcode++; else kcode = 0; break; default: kcode = 0; break; } break; case UIEvent.Type_WheelDown: tabs[curtab].MouseInput((curmouse/hs)-origin,MB_WHEELDOWN); return true; case UIEvent.Type_WheelUp: tabs[curtab].MouseInput((curmouse/hs)-origin,MB_WHEELUP); return true; case UIEvent.Type_LButtonDown: { isrclick = false; bool res = MouseEvent(MOUSE_Click,ev.MouseX,ev.MouseY); if ( res ) SetCapture(true); return res; } case UIEvent.Type_RButtonDown: { isrclick = true; bool res = MouseEvent(MOUSE_Click,ev.MouseX,ev.MouseY); if ( res ) SetCapture(true); return res; } case UIEvent.Type_LButtonUp: case UIEvent.Type_RButtonUp: if ( mMouseCapture ) { SetCapture(false); return MouseEvent(MOUSE_Release,ev.MouseX,ev.MouseY); } break; case UIEvent.Type_MouseMove: curmouse = (ev.MouseX,ev.MouseY); if ( mMouseCapture || (m_use_mouse == 1) ) return MouseEvent(MOUSE_Move,ev.MouseX,ev.MouseY); break; } return false; } // fundamental drawing functions (good god these are hacky) void DrawFrame( double x, double y, double w, double h, bool shadow = true ) { x += origin.x; y += origin.y; Screen.DrawTexture(FrameTex,false,x,y,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,0.,DTA_SrcY,0.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,1); if ( w > 2 ) Screen.DrawTexture(FrameTex,false,x+1,y,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,1.,DTA_SrcY,0.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,int(w-2),DTA_DestHeight,1); if ( h > 2 ) Screen.DrawTexture(FrameTex,false,x,y+1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,0.,DTA_SrcY,1.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,int(h-2)); Screen.DrawTexture(FrameTex,false,(x+w)-1,y,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcY,0.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,1); Screen.DrawTexture(FrameTex,false,x,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,0.,DTA_SrcY,2.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,1); if ( shadow ) { if ( h > 2 ) Screen.DrawTexture(FrameTex,false,(x+w)-1,y+1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcY,1.,DTA_SrcWidth,2.,DTA_SrcHeight,1.,DTA_DestWidth,2,DTA_DestHeight,int(h-2)); if ( w > 2 ) Screen.DrawTexture(FrameTex,false,x+1,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,1.,DTA_SrcY,2.,DTA_SrcWidth,1.,DTA_SrcHeight,2.,DTA_DestWidth,int(w-2),DTA_DestHeight,2); Screen.DrawTexture(FrameTex,false,(x+w)-1,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcY,2.,DTA_SrcWidth,2.,DTA_SrcHeight,2.,DTA_DestWidth,2,DTA_DestHeight,2); } else { if ( h > 2 ) Screen.DrawTexture(FrameTex,false,(x+w)-1,y+1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcY,1.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,int(h-2)); if ( w > 2 ) Screen.DrawTexture(FrameTex,false,x+1,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,1.,DTA_SrcY,2.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,int(w-2),DTA_DestHeight,1); Screen.DrawTexture(FrameTex,false,(x+w)-1,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcY,2.,DTA_SrcWidth,1.,DTA_SrcHeight,1.,DTA_DestWidth,1,DTA_DestHeight,1); } } // these ones thankfully only need three drawtexture calls each void DrawVSeparator( double x, double y, double h ) { x += origin.x; y += origin.y; Screen.DrawTexture(VSepTex,false,x-1,y,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcY,0.,DTA_SrcHeight,1.,DTA_DestHeight,1); Screen.DrawTexture(VSepTex,false,x-1,y+1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcY,1.,DTA_SrcHeight,1.,DTA_DestHeight,int(h-2)); Screen.DrawTexture(VSepTex,false,x-1,(y+h)-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcY,2.,DTA_SrcHeight,1.,DTA_DestHeight,1); } void DrawHSeparator( double x, double y, double w ) { x += origin.x; y += origin.y; Screen.DrawTexture(HSepTex,false,x,y-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,0.,DTA_SrcWidth,1.,DTA_DestWidth,1); Screen.DrawTexture(HSepTex,false,x+1,y-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,1.,DTA_SrcWidth,1.,DTA_DestWidth,int(w-2)); Screen.DrawTexture(HSepTex,false,(x+w)-1,y-1,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_SrcX,2.,DTA_SrcWidth,1.,DTA_DestWidth,1); } override void Drawer() { if ( animtimer < 0 ) return; // animated frame (more math) if ( isclosing || (animtimer < 1.) ) { double intp, xfact, yfact; if ( isclosing ) { intp = animtimer-.1*System.GetTimeFrac(); xfact = clamp(intp*2.,0.,1.)**2.; yfact = clamp(intp,0.,1.)**5.; } else { intp = animtimer+.1*System.GetTimeFrac(); xfact = clamp(intp*2,0.,1.)**2.; yfact = clamp(intp,0.,1.)**5.; } double rwsx = int(SWWMUtility.Lerp(2,ws.x,xfact)); double rwsy = int(SWWMUtility.Lerp(2,ws.y,yfact)); if ( (rwsx == 2) && (rwsy == 2) ) return; double rox = int(ss.x-rwsx)/2; double roy = int(ss.y-rwsy)/2; // copy the menu dim below the window during animations, so the transition looks smoother Screen.Dim((dimamount<0)?0xFF000000:dimcolor,(dimamount<0)?.5:dimamount,int(rox*hs),int(roy*hs),int(rwsx*hs),int(rwsy*hs)); // draw the background and main frame if ( swwm_fuzz ) { // fuzz was designed for 16:10, so we'll have to extend it at taller ratios int count = int(ceil(ws.y/400.)); Screen.SetClipRect(int(rox*hs),int(roy*hs),int(rwsx*hs),int(rwsy*hs)); for ( int i=0; i