// internal "knowledge base" and more Class MenuTransaction { enum ETransactionType { TT_ITEMUSE, TT_ITEMDROP, TT_ITEMSEND }; int uid, type; Class used; bool result, usedup; } // FIXME this whole thing needs to be refactored eventually Class DemolitionistMenu : GenericMenu { enum EDemoTab { TAB_MISSION = 0, TAB_STATS, TAB_INVENTORY, TAB_KEYS, TAB_LIBRARY, TAB_STORE, TAB_TRADING, TAB_CHAT, TAB_SECRET, TAB_HELP }; enum EStatsTab { STAT_MAIN = 0, STAT_KILLS, STAT_LEVEL, STAT_ACHIEVEMENT, }; TextureID MainWindow, TabSeparator, WindowSeparator, WindowSeparatorH, FancyBg, Drawing[3], LoreSeparator, LoreSeparatorW, AchievementUnknown; transient Font TewiFont, MPlusFont, MiniwiFont, k6x8Font; Array tabs; int curtab, oldtab; // for scrolling bool sub; int sel0, sel1, sel2; int ofs0, ofs1, ofs2; // usually equal to above, except when using mouse input int drag; // when dragging with the mouse, which scroller to affect (1 = ofs0 and so on) // mission String missionstr; Array missionbacklog; // stats SWWMStats stats; Array statlist; Array sorted_mstats; // sorted by killcount Array filtered_lstats; // filtered if necessary + entry for current map LevelStat curlstat; Array achievements; // array created once, then sorted as needed // inventory lists Array invlist; // lore stuff SWWMLoreLibrary lorelib; Array lorelist; int oldloresiz; // store stuff Array > storelist; Array storeunits; // trading SWWMTradeHistory tradelib; Array playerlist; // chat history total line count (includes breaks) int chatlines; // temporary bottom messages, such as "not enough money" String tmsg; int tmsgtic; // menu keybind int ikey[2]; String mkey[2]; // for checks (duh) Array checklist; int lasttuid; SWWMHandler hnd; // 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; // to save on calls bool nrftl, eviternity, hexdd; // returns MPlus if we're playing in Japanese, otherwise returns the requested font Font LangFont( Font req ) { if ( language ~== "jp" ) return (req==MiniwiFont)?k6x8Font:MPlusFont; return req; } int GenTUID() { return lasttuid++; } override void Init( Menu parent ) { Super.Init(parent); if ( (gamestate != GS_LEVEL) || (players[consoleplayer].Health <= 0) || !(players[consoleplayer].mo is 'Demolitionist') ) { // can't open this menu outside of the game or if dead // also can't if you're not the Demolitionist EventHandler.SendNetworkEvent("swwmclearalltransactions",consoleplayer); isclosing = true; return; } TewiFont = Font.GetFont('TewiShaded'); MPlusFont = Font.GetFont('MPlusShaded'); MiniwiFont = Font.GetFont('MiniwiShaded'); k6x8Font = Font.GetFont('k6x8Shaded'); FancyBg = TexMan.CheckForTexture("graphics/tempbg.png",TexMan.Type_MiscPatch); MainWindow = TexMan.CheckForTexture("graphics/KBase/MainWindow.png",TexMan.Type_MiscPatch); TabSeparator = TexMan.CheckForTexture("graphics/KBase/TabSeparator.png",TexMan.Type_MiscPatch); WindowSeparator = TexMan.CheckForTexture("graphics/KBase/WindowSeparator.png",TexMan.Type_MiscPatch); WindowSeparatorH = TexMan.CheckForTexture("graphics/KBase/WindowSeparatorH.png",TexMan.Type_MiscPatch); Drawing[0] = TexMan.CheckForTexture("graphics/KBase/Drawing_Saya.png",TexMan.Type_MiscPatch); Drawing[1] = TexMan.CheckForTexture("graphics/KBase/Drawing_Ibuki.png",TexMan.Type_MiscPatch); Drawing[2] = TexMan.CheckForTexture("graphics/KBase/Drawing_Kirin.png",TexMan.Type_MiscPatch); LoreSeparator = TexMan.CheckForTexture("graphics/KBase/LibraryTabSeparator.png",TexMan.Type_MiscPatch); LoreSeparatorW = TexMan.CheckForTexture("graphics/KBase/LibraryTabSeparatorJP.png",TexMan.Type_MiscPatch); MenuSound("menu/demoopen"); [ikey[0], ikey[1]] = Bindings.GetKeysForCommand("event swwmdemomenu"); mkey[0] = Bindings.NameKeys(ikey[0],0); mkey[1] = Bindings.NameKeys(ikey[1],0); lorelib = SWWMLoreLibrary.Find(players[consoleplayer]); oldloresiz = lorelib.ent.Size(); tradelib = SWWMTradeHistory.Find(players[consoleplayer]); stats = SWWMStats.Find(players[consoleplayer]); tmsg = "$SWWM_MAINCONTROLS"; tmsgtic = gametic+70; lasttuid = Random[TUID](); hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); // mission text nrftl = false; eviternity = false; hexdd = false; if ( (gameinfo.gametype&GAME_Doom) && SWWMUtility.IsKnownMap() ) { int clus = level.cluster; if ( clus == 11 ) nrftl = true; eviternity = SWWMUtility.IsEviternity(); if ( eviternity ) { // we have to do some heavy lifting here because episodes don't match clusters if ( level.levelnum <= 5 ) clus = 1; else if ( level.levelnum <= 10 ) clus = 2; else if ( level.levelnum <= 15 ) clus = 3; else if ( level.levelnum <= 20 ) clus = 4; else if ( level.levelnum <= 25 ) clus = 5; else if ( level.levelnum <= 30 ) clus = 6; else if ( level.levelnum <= 31 ) clus = 7; else if ( level.levelnum <= 32 ) clus = 8; missionstr = String.Format("$SWWM_MISSION_EVITERNITY%d",clus); } // naive method to guess if this is sigil else if ( (clus == 5) && (level.mapname.Left(2) == "E5") ) missionstr = String.Format("$SWWM_MISSION_SIGIL"); else missionstr = String.Format("$SWWM_MISSION_DOOM%d",clus); int csiz = stats.clustervisit.Size(); if ( (csiz > 0) && stats.secretdone[csiz-1] ) { String tmpstr = missionstr.."_SECRET"; if ( !(StringTable.Localize(tmpstr) ~== tmpstr.Mid(1)) ) missionstr = tmpstr; } // if we came from the doom 1 episodes, use the alt mission string for Doom 2 bool fromdoomone = false; for ( int i=0; i= 5) && (stats.clustervisit[i] != 25) ) continue; fromdoomone = true; break; } if ( !eviternity && (clus == 5) && fromdoomone ) missionstr = "$SWWM_MISSION_DOOM5_FROMDOOM1"; if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) missionstr = "$SWWM_MISSION_NONE"; bool firstskip = false; for ( int i=csiz-1; i>=0; i-- ) { if ( (stats.clustervisit[i] == clus) && !firstskip ) continue; firstskip = true; String xstr = String.Format(eviternity?"$SWWM_MISSION_EVITERNITY%d":"$SWWM_MISSION_DOOM%d",stats.clustervisit[i]); if ( !eviternity && (stats.clustervisit[i] == 5) && fromdoomone ) xstr = "$SWWM_MISSION_DOOM5_FROMDOOM1"; if ( stats.secretdone[i] ) { String tmpstr = xstr.."_SECRET"; if ( !(StringTable.Localize(tmpstr) ~== tmpstr.Mid(1)) ) xstr = tmpstr; } if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) continue; missionbacklog.Push(xstr); } } else if ( (gameinfo.gametype&GAME_Heretic) && SWWMUtility.IsKnownMap() ) { missionstr = String.Format("$SWWM_MISSION_HERETIC%d",level.cluster); if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) missionstr = "$SWWM_MISSION_NONE"; int csiz = stats.clustervisit.Size(); bool firstskip = false; for ( int i=csiz-1; i>=0; i-- ) { if ( (stats.clustervisit[i] == level.cluster) && !firstskip ) continue; firstskip = true; String xstr = String.Format("$SWWM_MISSION_HERETIC%d",stats.clustervisit[i]); if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) continue; missionbacklog.Push(xstr); } } else if ( (gameinfo.gametype&GAME_Hexen) && SWWMUtility.IsKnownMap() ) { // detect deathkings hexdd = SWWMUtility.IsDeathkings(); String gstr = hexdd?"HEXDD":"HEXEN"; missionstr = String.Format("$SWWM_MISSION_%s%d",gstr,level.cluster); if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) missionstr = "$SWWM_MISSION_NONE"; int csiz = stats.clustervisit.Size(); bool firstskip = false; for ( int i=csiz-1; i>=0; i-- ) { if ( (stats.clustervisit[i] == level.cluster) && !firstskip ) continue; firstskip = true; String xstr = String.Format("$SWWM_MISSION_%s%d",gstr,stats.clustervisit[i]); if ( StringTable.Localize(missionstr) ~== missionstr.Mid(1) ) continue; missionbacklog.Push(xstr); } } else missionstr = "$SWWM_MISSION_NONE"; if ( gameinfo.gametype&GAME_Heretic ) { // April 10th 2171, 17:34 JST // Epoch: 6351554040 c_year = 2171; c_month = 3; c_day = 9; c_hour = 17; c_minute = 34; c_tz = "JST"; } else if ( gameinfo.gametype&GAME_Hexen ) { // May 25th 2171, 16:41 JST // Epoch: 6355438860 c_year = 2171; c_month = 4; c_day = 24; c_hour = 16; c_minute = 41; c_tz = "JST"; if ( hexdd ) { // deathkings happen the day after c_day = 25; c_hour = 10; c_minute = 28; } } else if ( eviternity ) { // June 10th 2150, 20:09 +09 // (June 10th 2150, 20:09 JST) // Epoch: 5694145740 c_year = 2150; c_month = 5; c_day = 9; c_hour = 20; c_minute = 9; c_tz = "+09"; } else // Doom { // June 6th 2148, 18:37 EDT // (June 7th 2148, 07:37 JST) // Epoch: 5630769420 c_year = 2148; c_month = 5; c_day = 5; c_hour = 18; c_minute = 37; c_tz = "EDT"; if ( nrftl ) { // NRFTL just happens the next day after c_day = 6; c_hour = 15; c_minute = 48; } } if ( (missionstr != "$SWWM_MISSION_NONE") || (missionbacklog.Size() > 0) ) tabs.Push(TAB_MISSION); tabs.Push(TAB_STATS); tabs.Push(TAB_INVENTORY); if ( !deathmatch ) { tabs.Push(TAB_KEYS); tabs.Push(TAB_LIBRARY); } if ( !deathmatch && (G_SkillPropertyInt(SKILLP_ACSReturn) < 4) ) tabs.Push(TAB_STORE); for ( int i=0; i 0) ) tabs.Push(TAB_CHAT); int lasttab = swwm_lasttab; for ( int i=0; i 0 ) { leap_year = (!(y%4) && ((y%100) || !(y%400))); if ( addtime >= 86400 ) { addtime -= 86400; d++; int md = days_in_month[m]; if ( leap_year && (m == 1) ) md++; if ( d >= md ) { d = 0; m++; if ( m > 11 ) { m = 0; y++; } } } else if ( addtime >= 3600 ) { addtime -= 3600; h++; if ( h > 23 ) { h = 0; d++; int md = days_in_month[m]; if ( leap_year && (m == 1) ) md++; if ( d >= md ) { d = 0; m++; if ( m > 11 ) { m = 0; y++; } } } } else if ( addtime >= 60 ) { addtime -= 60; mn++; if ( mn > 59 ) { mn = 0; h++; if ( h > 23 ) { h = 0; d++; int md = days_in_month[m]; if ( leap_year && (m == 1) ) md++; if ( d >= md ) { d = 0; m++; if ( m > 11 ) { m = 0; y++; } } } } } else { // we finished counting addtime = 0; } } return String.Format("%04d-%02d-%02d %02d:%02d %s",y,m+1,d+1,h,mn,c_tz); } override bool MenuEvent( int mkey, bool fromcontroller ) { Font fnt = LangFont(TewiFont); 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 ) { MenuSound("misc/secret"); if ( tabs[curtab] >= TAB_SECRET ) tabs.Delete(curtab); tabs.Push(TAB_SECRET); curtab = tabs.Size()-1; sel0 = sel1 = sel2 = 0; ofs0 = ofs1 = ofs2 = 0; sub = false; } default: kcode = 0; break; } int maxtab = multiplayer?TAB_CHAT:TAB_STORE; switch ( mkey ) { case MKEY_BACK: MenuSound("menu/democlose"); EventHandler.SendNetworkEvent("swwmclearalltransactions",consoleplayer); Close(); return true; case MKEY_PAGEUP: MenuSound("menu/demotab"); if ( tabs[curtab] >= TAB_SECRET ) tabs.Delete(curtab); // hide this tab if ( curtab <= 0 ) curtab = (tabs.Size()-1); else curtab--; CVar.FindCVar('swwm_lasttab').SetInt(tabs[curtab]); sel0 = sel1 = sel2 = 0; ofs0 = ofs1 = ofs2 = 0; sub = false; invlist.Clear(); lorelist.Clear(); storelist.Clear(); storeunits.Clear(); playerlist.Clear(); return true; case MKEY_PAGEDOWN: MenuSound("menu/demotab"); if ( tabs[curtab] >= TAB_SECRET ) tabs.Delete(curtab); // hide this tab if ( curtab < (tabs.Size()-1) ) curtab++; else curtab = 0; CVar.FindCVar('swwm_lasttab').SetInt(tabs[curtab]); sel0 = sel1 = sel2 = 0; ofs0 = ofs1 = ofs2 = 0; sub = false; invlist.Clear(); lorelist.Clear(); storelist.Clear(); storeunits.Clear(); playerlist.Clear(); return true; case MKEY_DOWN: if ( tabs[curtab] == TAB_HELP ) { String kstr; if ( self.mkey[1] != "" ) kstr = self.mkey[0].."/"..self.mkey[1]; else kstr = self.mkey[0]; String str = String.Format(StringTable.Localize("$SWWM_HELPTXT"),kstr); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); if ( (l.Count() > 28) && (sel0 < l.Count()-28) ) { MenuSound("menu/demoscroll"); sel0++; } } else if ( tabs[curtab] == TAB_MISSION ) { String str; if ( sel1 == 0 ) str = StringTable.Localize(missionstr); else str = StringTable.Localize(missionbacklog[sel1-1]); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); if ( (l.Count() > 28) && (sel0 < l.Count()-28) ) { MenuSound("menu/demoscroll"); ofs0 = ++sel0; } } else if ( (tabs[curtab] == TAB_CHAT) && (StatusBar is 'SWWMStatusBar') && (sel0 > 0) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; } else if ( (tabs[curtab] == TAB_LIBRARY) && (lorelist.Size() > 1) ) { if ( sub ) { String str = StringTable.Localize(lorelist[sel0].text); int ofs = (language~=="jp")?212:132; if ( lorelist.Size() > 26 ) ofs += 8; BrokenLines l = fnt.BreakLines(str,635-ofs); if ( l.Count() > 28 ) l = fnt.BreakLines(str,626-ofs); if ( (l.Count() > 28) && (sel2 < l.Count()-28) ) { MenuSound("menu/demoscroll"); ofs2 = ++sel2; } } else { MenuSound("menu/demoscroll"); if ( sel0 >= lorelist.Size()-1 ) ofs0 = sel0 = 0; else { sel0++; // update ofs so selection stays on-screen int ofs = max(ofs0-26,0); if ( sel0 < ofs ) ofs0 = sel0+26; else if ( sel0 > ofs+26 ) ofs0 = sel0; } } } else if ( ((tabs[curtab] == TAB_INVENTORY) || (tabs[curtab] == TAB_KEYS) || ((tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1))) && (invlist.Size() > 1) ) { MenuSound("menu/demoscroll"); ofs0 = ++sel0; if ( sel0 >= invlist.Size() ) ofs0 = sel0 = 0; } else if ( (tabs[curtab] == TAB_STORE) && (storelist.Size() > 1) ) { MenuSound("menu/demoscroll"); ofs0 = ++sel0; if ( sel0 >= storelist.Size() ) ofs0 = sel0 = 0; } else if ( tabs[curtab] == TAB_TRADING ) { if ( !sub && (playerlist.Size() > 1) ) { // scroll through player list MenuSound("menu/demoscroll"); ofs0 = ++sel0; if ( sel0 >= playerlist.Size() ) ofs0 = sel0 = 0; } else if ( sub && (sel0 == -1) && (sel1 < tradelib.ent.Size()-28) ) { // scroll through trading history MenuSound("menu/demoscroll"); ofs1 = ++sel1; } } else if ( tabs[curtab] == TAB_STATS ) { int cnt = 0, num = 22; if ( sel1 == STAT_MAIN ) cnt = statlist.Size(); else if ( sel1 == STAT_KILLS ) cnt = sorted_mstats.Size(); else if ( sel1 == STAT_LEVEL ) cnt = filtered_lstats.Size(); else if ( sel1 == STAT_ACHIEVEMENT ) { cnt = achievements.Size(); num = 7; } if ( (cnt > num) && (sel0 < cnt-num) ) { MenuSound("menu/demoscroll"); ofs0 = ++sel0; } } return true; case MKEY_UP: if ( (tabs[curtab] == TAB_HELP) && (sel0 > 0) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; } else if ( (tabs[curtab] == TAB_MISSION) && (sel0 > 0) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; } else if ( (tabs[curtab] == TAB_CHAT) && (StatusBar is 'SWWMStatusBar') && (sel0 < SWWMStatusBar(StatusBar).FullHistory.Size()-1) ) { MenuSound("menu/demoscroll"); ofs0 = ++sel0; } else if ( (tabs[curtab] == TAB_LIBRARY) && (lorelist.Size() > 1) ) { if ( sub ) { if ( sel2 > 0 ) { MenuSound("menu/demoscroll"); ofs2 = --sel2; } } else { MenuSound("menu/demoscroll"); if ( sel0 <= 0 ) ofs0 = sel0 = lorelist.Size()-1; else { sel0--; // update ofs so selection stays on-screen int ofs = max(ofs0-26,0); if ( sel0 < ofs ) ofs0 = sel0+26; else if ( sel0 > ofs+26 ) ofs0 = sel0; } } } else if ( ((tabs[curtab] == TAB_INVENTORY) || (tabs[curtab] == TAB_KEYS) || ((tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1))) && (invlist.Size() > 1) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; if ( sel0 < 0 ) ofs0 = sel0 = invlist.Size()-1; } else if ( (tabs[curtab] == TAB_STORE) && (storelist.Size() > 1) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; if ( sel0 < 0 ) ofs0 = sel0 = storelist.Size()-1; } else if ( tabs[curtab] == TAB_TRADING ) { if ( !sub && (playerlist.Size() > 1) ) { // scroll through player list MenuSound("menu/demoscroll"); ofs0 = --sel0; if ( sel0 < 0 ) ofs0 = sel0 = playerlist.Size()-1; } else if ( sub && (sel0 == -1) && (sel1 > 0) ) { // scroll through trading history MenuSound("menu/demoscroll"); ofs1 = --sel1; } } else if ( (tabs[curtab] == TAB_STATS) && (sel0 > 0) ) { MenuSound("menu/demoscroll"); ofs0 = --sel0; } return true; case MKEY_RIGHT: if ( (tabs[curtab] == TAB_MISSION) && (missionbacklog.Size() > 0) ) { if ( sel1 < missionbacklog.Size() ) { sel0 = 0; sel1++; MenuSound("menu/demoscroll"); } } else if ( ((tabs[curtab] == TAB_INVENTORY) || (tabs[curtab] == TAB_KEYS) || ((tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1))) && (invlist.Size() > 21) ) { int oldsel = sel0; ofs0 = sel0 += 22; if ( sel0 >= invlist.Size() ) ofs0 = sel0 = invlist.Size()-1; if ( sel0 != oldsel ) MenuSound("menu/demoscroll"); } else if ( (tabs[curtab] == TAB_STORE) && (storelist.Size() > 21) ) { int oldsel = sel0; ofs0 = sel0 += 22; if ( sel0 >= storelist.Size() ) ofs0 = sel0 = storelist.Size()-1; if ( sel0 != oldsel ) MenuSound("menu/demoscroll"); } else if ( tabs[curtab] == TAB_LIBRARY ) { MenuSound("menu/demoscroll"); ofs0 = sel0 = 0; sub = false; ofs1 = ++sel1; if ( sel1 > LORE_LORE ) ofs1 = sel1 = LORE_ITEM; CVar.FindCVar('swwm_lastloretab').SetInt(sel1); } else if ( (tabs[curtab] == TAB_TRADING) && !sub && (playerlist.Size() > 21) ) // lol is this ever going to happen { int oldsel = sel0; ofs0 = sel0 += 22; if ( sel0 >= playerlist.Size() ) ofs0 = sel0 = playerlist.Size()-1; if ( sel0 != oldsel ) MenuSound("menu/demoscroll"); } else if ( tabs[curtab] == TAB_STATS ) { MenuSound("menu/demoscroll"); ofs0 = sel0 = 0; ofs1 = ++sel1; if ( ofs1 > STAT_ACHIEVEMENT ) ofs1 = sel1 = STAT_MAIN; CVar.FindCVar('swwm_laststattab').SetInt(sel1); } return true; case MKEY_LEFT: if ( (tabs[curtab] == TAB_MISSION) && (missionbacklog.Size() > 0) ) { if ( sel1 > 0 ) { sel0 = 0; sel1--; MenuSound("menu/demoscroll"); } } else if ( ((tabs[curtab] == TAB_INVENTORY) || (tabs[curtab] == TAB_KEYS) || ((tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1))) && (invlist.Size() > 21) && (sel0-22 >= 0) ) { MenuSound("menu/demoscroll"); ofs0 = sel0 -= 22; } else if ( (tabs[curtab] == TAB_STORE) && (storelist.Size() > 21) && (sel0-22 >= 0) ) { MenuSound("menu/demoscroll"); ofs0 = sel0 -= 22; } else if ( tabs[curtab] == TAB_LIBRARY ) { MenuSound("menu/demoscroll"); ofs0 = sel0 = 0; sub = false; ofs1 = --sel1; if ( sel1 < LORE_ITEM ) ofs1 = sel1 = LORE_LORE; CVar.FindCVar('swwm_lastloretab').SetInt(sel1); } else if ( (tabs[curtab] == TAB_TRADING) && !sub && (playerlist.Size() > 21) && (sel0-22 >= 0) ) // lol is this ever going to happen { ofs0 = sel0 -= 22; MenuSound("menu/demoscroll"); } else if ( tabs[curtab] == TAB_STATS ) { MenuSound("menu/demoscroll"); ofs0 = sel0 = 0; ofs1 = --sel1; if ( ofs1 < STAT_MAIN ) ofs1 = sel1 = STAT_ACHIEVEMENT; CVar.FindCVar('swwm_laststattab').SetInt(sel1); } return true; case MKEY_ENTER: if ( (tabs[curtab] == TAB_INVENTORY) && (invlist.Size() > 0) && (sel0 < invlist.Size()) ) { // can't use this if ( (invlist[sel0] is 'Ammo') || (invlist[sel0] is 'MagAmmo') || (invlist[sel0] is 'SWWMCollectible') || (invlist[sel0] is 'HammerspaceEmbiggener') ) return true; let t = new("MenuTransaction"); t.uid = GenTUID(); t.type = MenuTransaction.TT_ITEMUSE; t.result = false; // don't check weapons, always assume check succeeded if ( invlist[sel0] is 'Weapon' ) t.result = true; checklist.Push(t); EventHandler.SendNetworkEvent(String.Format("swwmuseitem.%s",invlist[sel0].GetClassName()),consoleplayer,t.uid); } else if ( (tabs[curtab] == TAB_KEYS) && (invlist.Size() > 0) && (sel0 < invlist.Size()) ) { // can't use this if ( !(invlist[sel0] is 'SWWMKey') && !(invlist[sel0] is 'SWWMCollectible') ) return true; let t = new("MenuTransaction"); t.uid = GenTUID(); t.type = MenuTransaction.TT_ITEMUSE; t.result = true; // always true checklist.Push(t); EventHandler.SendNetworkEvent(String.Format("swwmuseitem.%s",invlist[sel0].GetClassName()),consoleplayer,t.uid); } else if ( (tabs[curtab] == TAB_STORE) && (storelist.Size() > 0) && (sel0 < storelist.Size()) ) { let itm = GetDefaultByType(storelist[sel0]); let amt = storeunits[sel0]; int price = int(abs(itm.Stamina)*(1.+.75*(amt-1))); let cur = players[consoleplayer].mo.FindInventory(storelist[sel0]); if ( sub ) { if ( !cur ) return true; if ( storelist[sel0] is 'Weapon' ) { let w = Weapon(itm); // subtract price of given ammo, as we're only selling the weapon itself if ( w.AmmoType1 && (w.AmmoGive1 > 0) ) { let am1 = GetDefaultByType(w.AmmoType1); if ( am1.Stamina > 0 ) price -= int(am1.Stamina*(1.+.75*(w.AmmoGive1-1))); } // candygun is a special case for secondary ammo if ( w.AmmoType2 && (w.AmmoGive2 > 0) && (storelist[sel0] is 'CandyGun') ) { let am2 = GetDefaultByType(w.AmmoType2); if ( am2.Stamina > 0 ) price -= int(am2.Stamina*(1.+.75*(w.AmmoGive2-1))); } } // sell at half price if ( sub ) price /= 2; EventHandler.SendNetworkEvent(String.Format("swwmstoretake.%s",storelist[sel0].GetClassName()),consoleplayer,price,amt); MenuSound("menu/buyinv"); return true; } if ( !SWWMCredits.CanTake(players[consoleplayer],price) ) { MenuSound("menu/noinvuse"); tmsg = StringTable.Localize("$SWWM_STOREMUNS"); tmsgtic = gametic+70; return true; } int camt, max; if ( cur ) { camt = cur.Amount; max = cur.MaxAmount; } else { camt = 0; max = itm.MaxAmount; } if ( camt >= max ) { MenuSound("menu/noinvuse"); tmsg = StringTable.Localize("$SWWM_STOREFULL"); tmsgtic = gametic+70; return true; } EventHandler.SendNetworkEvent(String.Format("swwmstoregive.%s",storelist[sel0].GetClassName()),consoleplayer,price,amt); MenuSound("menu/buyinv"); } else if ( tabs[curtab] == TAB_LIBRARY ) { if ( sub ) { MenuSound("menu/democlose"); sub = false; } else if ( (lorelist.Size() > 0) && (sel0 < lorelist.Size()) ) { // mark as read if ( !lorelist[sel0].read ) EventHandler.SendNetworkEvent(String.Format("swwmmarkloreread.%s",lorelist[sel0].tag),consoleplayer); MenuSound("menu/demosel"); sub = true; ofs2 = sel2 = 0; } } else if ( tabs[curtab] == TAB_TRADING ) { if ( !sub && (playerlist.Size() > 0) ) { // pick a player MenuSound("menu/demosel"); sub = true; ofs1 = sel1 = 0; } else if ( sub && (sel0 != -1) && (invlist.Size() > 0) ) { // cannot trade these if ( ((invlist[sel1].bUNDROPPABLE || invlist[sel1].bUNTOSSABLE) && !(invlist[sel1] is 'HammerspaceEmbiggener')) || ((invlist[sel1] is 'CandyGunSpares') && !players[playerlist[sel0]].mo.FindInventory('CandyGun')) ) { MenuSound("menu/noinvuse"); tmsg = StringTable.Localize("$SWWM_TRADEFAIL"); tmsgtic = gametic+70; return true; } // trade item let t = new("MenuTransaction"); t.uid = GenTUID(); t.type = MenuTransaction.TT_ITEMSEND; t.result = false; checklist.Push(t); EventHandler.SendNetworkEvent(String.Format("swwmtrade.%s",invlist[sel1].GetClassName()),consoleplayer,playerlist[sel0],t.uid); } } return true; case MKEY_CLEAR: if ( (tabs[curtab] == TAB_INVENTORY) && (invlist.Size() > 0) ) { let t = new("MenuTransaction"); t.uid = GenTUID(); t.type = MenuTransaction.TT_ITEMDROP; t.result = false; checklist.Push(t); EventHandler.SendNetworkEvent(String.Format("swwmdropitem.%s",invlist[sel0].GetClassName()),consoleplayer,t.uid); } else if ( (tabs[curtab] == TAB_LIBRARY) && sub ) { MenuSound("menu/democlose"); sub = false; } else if ( tabs[curtab] == TAB_STORE ) { sub = !sub; MenuSound(sub?"menu/demosel":"menu/democlose"); } else if ( tabs[curtab] == TAB_TRADING ) { if ( !sub ) { // open trading history MenuSound("menu/demosel"); sub = true; ofs0 = sel0 = -1; ofs1 = sel1 = 0; } else if ( sub ) { // go back MenuSound("menu/democlose"); sub = false; ofs0 = sel0 = 0; } } return true; } return Super.MenuEvent(mkey,fromcontroller); } private int rnd( double num ) { return int(num+((num<0)?-.5:.5)); } override bool MouseEvent( int type, int mx, int my ) { bool res = Super.MouseEvent(type,mx,my); Font fnt = LangFont(TewiFont); String str; double hs = max(min(floor(Screen.GetWidth()/640.),floor(Screen.GetHeight()/400.)),1.); Vector2 ss = (Screen.GetWidth(),Screen.GetHeight())/hs; Vector2 origin = (ss.x-640,ss.y-400)/2.; Vector2 mpos = (mx/hs,my/hs)-origin; int xx, yy, len; if ( type == MOUSE_Click ) { if ( (mpos.x < 0) || (mpos.x >= 641) || (mpos.y < 0) || (mpos.y >= 386) ) return res; // outside clickable area, ignore else if ( mpos.y < 14 ) { if ( isrclick ) return res; // check which tab we're clicking static const string tabnames[] = { "$SWWM_MISSTAB", "$SWWM_STATTAB", "$SWWM_INVTAB", "$SWWM_KEYTAB", "$SWWM_KBASETAB", "$SWWM_STORETAB", "$SWWM_TRADETAB", "$SWWM_CHATTAB", "$SWWM_SECRETTAB", "$SWWM_HELPTAB" }; xx = 0; for ( int i=0; i= xx) && (mpos.x < xx+len) ) { if ( curtab == i ) break; MenuSound("menu/demotab"); if ( tabs[curtab] >= TAB_SECRET ) { tabs.Delete(curtab); if ( curtab < i ) i--; // account for shortening } curtab = i; CVar.FindCVar('swwm_lasttab').SetInt(tabs[curtab]); sel0 = sel1 = sel2 = 0; ofs0 = ofs1 = ofs2 = 0; sub = false; invlist.Clear(); lorelist.Clear(); storelist.Clear(); storeunits.Clear(); playerlist.Clear(); break; } xx += len; } } else if ( tabs[curtab] == TAB_MISSION ) { // are we clicking where the scrollbar should be? if ( mpos.x < 632 ) { int csiz = missionbacklog.Size(); if ( csiz <= 0 ) return res; if ( isrclick && (sel1 < csiz) ) { sel0 = 0; sel1++; MenuSound("menu/demoscroll"); } else if ( !isrclick && (sel1 > 0) ) { sel0 = 0; sel1--; MenuSound("menu/demoscroll"); } return res; } if ( !isrclick ) { String str; if ( sel1 == 0 ) str = StringTable.Localize(missionstr); else str = StringTable.Localize(missionbacklog[sel1-1]); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; drag = 1; } } else if ( tabs[curtab] == TAB_INVENTORY ) { // check what item we clicked if ( mpos.y < 377 ) { if ( invlist.Size() <= 0 ) return res; // clicked nothing int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (!(invlist[i] is 'PuzzleItem') && !(invlist[i] is 'Weapon') && (invlist[i].MaxAmount > 1)) ) str = String.Format("%dx %s",invlist[i].Amount,invlist[i].GetTag()); else str = invlist[i].GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); int ofs = int(max(0,(ofs0/22)-(maxcol-1))*22); xx = 9; yy = 23; int cols = 1; for ( int i=ofs; i= xx) && (mpos.x < xx+longest) && (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel0 != i ) MenuSound("menu/demoscroll"); sel0 = i; ofs0 = (sel0/22)*22; // drop on right click in inventory tab if ( (tabs[curtab] == TAB_INVENTORY) && isrclick ) MenuEvent(MKEY_CLEAR,false); else if ( !isrclick ) MenuEvent(MKEY_ENTER,false); return res; } yy += 16; if ( yy >= 370 ) { xx += longest+24; yy = 23; cols++; if ( cols > maxcol ) break; } } return res; } if ( !isrclick ) { // check that scrollbar is present if ( invlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (!(invlist[i] is 'PuzzleItem') && !(invlist[i] is 'Weapon') && (invlist[i].MaxAmount > 1)) ) str = String.Format("%dx %s",invlist[i].Amount,invlist[i].GetTag()); else str = invlist[i].GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; drag = 1; } } else if ( (tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1) ) { // check what item we clicked if ( mpos.y < 377 ) { if ( invlist.Size() <= 0 ) return res; // clicked nothing int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (!(invlist[i] is 'PuzzleItem') && !(invlist[i] is 'Weapon') && (invlist[i].MaxAmount > 1)) ) str = String.Format("%dx %s",invlist[i].Amount,invlist[i].GetTag()); else str = invlist[i].GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); int ofs = int(max(0,(ofs1/22)-(maxcol-1))*22); xx = 9; yy = 23; int cols = 1; for ( int i=ofs; i= xx) && (mpos.x < xx+longest) && (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel1 != i ) MenuSound("menu/demoscroll"); sel1 = i; ofs1 = (sel1/22)*22; // drop on right click in inventory tab if ( (tabs[curtab] == TAB_INVENTORY) && isrclick ) MenuEvent(MKEY_CLEAR,false); else if ( !isrclick ) MenuEvent(MKEY_ENTER,false); return res; } yy += 16; if ( yy >= 370 ) { xx += longest+24; yy = 23; cols++; if ( cols > maxcol ) break; } } return res; } if ( !isrclick ) { // check that scrollbar is present if ( invlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (!(invlist[i] is 'PuzzleItem') && !(invlist[i] is 'Weapon') && (invlist[i].MaxAmount > 1)) ) str = String.Format("%dx %s",invlist[i].Amount,invlist[i].GetTag()); else str = invlist[i].GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; drag = 1; } } else if ( (tabs[curtab] == TAB_KEYS) && !isrclick ) { // check what item we clicked if ( mpos.y < 377 ) { if ( invlist.Size() <= 0 ) return res; // clicked nothing int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); int ofs = int(max(0,(ofs0/22)-(maxcol-1))*22); xx = 9; yy = 23; int cols = 1; for ( int i=ofs; i= xx) && (mpos.x < xx+longest) && (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel0 != i ) MenuSound("menu/demoscroll"); sel0 = i; ofs0 = (sel0/22)*22; // try to use it if it has any special behaviour MenuEvent(MKEY_ENTER,false); return res; } yy += 16; if ( yy >= 370 ) { xx += longest+24; yy = 23; cols++; if ( cols > maxcol ) break; } } return res; } // check that scrollbar is present if ( invlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; drag = 1; } else if ( tabs[curtab] == TAB_STORE ) { if ( isrclick ) { MenuEvent(MKEY_CLEAR,false); return res; } // check what item we clicked if ( mpos.y < 377 ) { if ( storelist.Size() <= 0 ) return res; // clicked nothing xx = 9; yy = 23; int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (storelist[i] is 'Ammo') ) str = String.Format("(%d/%d) %s",storeunits[i],amt,def.GetTag()); else str = def.GetTag(); } else if ( storeunits[i] > 1 ) str = String.Format("%dx %s",storeunits[i],def.GetTag()); else str = def.GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+96); totalcol = int(ceil(storelist.Size()/22.)); // gotta check both the tag and the price for clicking int ofs = int(max(0,(ofs0/22)-(maxcol-1))*22); int cols = 1; for ( int i=ofs; i= xx) && (mpos.x < xx+longest+80) && (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel0 != i ) MenuSound("menu/demoscroll"); sel0 = i; ofs0 = (sel0/22)*22; MenuEvent(MKEY_ENTER,false); return res; } yy += 16; if ( yy >= 370 ) { xx += longest+96; yy = 23; cols++; if ( cols > maxcol ) break; } } return res; } // check that scrollbar is present if ( storelist.Size() <= 0 ) return res; // definitely no scrollbar xx = 9; yy = 23; int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1 ) str = String.Format("%dx %s",storeunits[i],def.GetTag()); else str = def.GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+96); totalcol = int(ceil(storelist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; drag = 1; } else if ( (tabs[curtab] == TAB_LIBRARY) && !isrclick ) { double midp = (language~=="jp")?206:126; if ( mpos.x < midp ) { if ( mpos.y < 28 ) { // switch category if ( mpos.x < midp/2. ) MenuEvent(MKEY_LEFT,false); else MenuEvent(MKEY_RIGHT,false); } else { // check what item we clicked int xx = 3; int yy = 32; int ofs = max(0,ofs0-26); for ( int i=ofs; i 370 ) break; // check boundary if ( (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel0 != i ) MenuSound("menu/demoscroll"); if ( (sel0 != i) || !sub ) { sel0 = i; sub = false; MenuEvent(MKEY_Enter,false); } return res; } yy += 13; } } } else if ( mpos.x < midp+8 ) { // check that scrollbar is present if ( lorelist.Size() <= 27 ) return res; // no scrollbar // calculate offset int szr = lorelist.Size()-27; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step ) step += 26; if ( step != ofs0 ) MenuSound("menu/demoscroll"); ofs0 = step; drag = 1; } else if ( sub && (mpos.x >= 632) ) { // check that scrollbar is present str = StringTable.Localize(lorelist[sel0].text); int ofs = (language~=="jp")?212:132; if ( lorelist.Size() > 26 ) ofs += 8; BrokenLines l = fnt.BreakLines(str,635-ofs); if ( l.Count() > 28 ) l = fnt.BreakLines(str,626-ofs); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel2 ) MenuSound("menu/demoscroll"); ofs2 = sel2 = step; drag = 3; } } else if ( tabs[curtab] == TAB_TRADING ) { if ( isrclick ) { MenuEvent(MKEY_CLEAR,false); return res; } if ( !sub ) { // check what item we clicked if ( mpos.y < 377 ) { if ( playerlist.Size() <= 0 ) return res; // clicked nothing int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(playerlist.Size()/22.)); int ofs = int(max(0,(ofs0/22)-(maxcol-1))*22); xx = 9; yy = 23; int cols = 1; for ( int i=ofs; i= xx) && (mpos.x < xx+len+1) && (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel0 != i ) MenuSound("menu/demoscroll"); ofs0 = sel0 = i; MenuEvent(MKEY_Enter,false); return res; } yy += 16; if ( yy >= 370 ) { xx += longest+24; yy = 23; cols++; if ( cols > maxcol ) break; } } return res; } // check that scrollbar is present (will it ever be???) if ( playerlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(playerlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; drag = 1; } else if ( sel0 == -1 ) { // are we clicking where the scrollbar should be? if ( mpos.x < 632 ) return res; if ( tradelib.ent.Size() <= 28 ) return res; // no scrollbar // calculate offset int szr = tradelib.ent.Size()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; drag = 1; } } else if ( (tabs[curtab] == TAB_CHAT) && !isrclick ) { // are we clicking where the scrollbar should be? if ( mpos.x < 632 ) return res; let bar = SWWMStatusBar(StatusBar); if ( !bar || (bar.FullHistory.Size() <= 1) ) return res; // no scrollbar // calculate offset int szr = bar.FullHistory.Size()-1; int step = szr-clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; drag = 1; } else if ( (tabs[curtab] == TAB_HELP) && !isrclick ) { // are we clicking where the scrollbar should be? String kstr; if ( mkey[1] != "" ) kstr = mkey[0].."/"..mkey[1]; else kstr = mkey[0]; str = String.Format(StringTable.Localize("$SWWM_HELPTXT"),kstr); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; drag = 1; } else if ( (tabs[curtab] == TAB_STATS) && !isrclick ) { double midp = (language~=="jp")?206:126; if ( mpos.x < midp ) { // check what item we clicked int xx = 9; int yy = 23; for ( int i=0; i<=STAT_ACHIEVEMENT; i++ ) { // check boundary if ( (mpos.y >= yy) && (mpos.y < yy+14) ) { if ( sel1 != i ) { MenuSound("menu/demoscroll"); ofs0 = sel0 = 0; sel1 = i; CVar.FindCVar('swwm_laststattab').SetInt(sel1); } return res; } yy += 16; } return res; } // are we clicking where the scrollbar should be? if ( mpos.x < 632 ) return res; int cnt = 0, num = 22; if ( sel1 == STAT_MAIN ) cnt = statlist.Size(); else if ( sel1 == STAT_KILLS ) cnt = stats.mstats.Size(); else if ( sel1 == STAT_LEVEL ) cnt = filtered_lstats.Size(); else if ( sel1 == STAT_ACHIEVEMENT ) { cnt = achievements.Size(); num = 7; } if ( cnt <= num ) return res; // no scrollbar // calculate offset int szr = cnt-num; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; drag = 1; } } else if ( (type == MOUSE_Move) && drag ) // scroll dragging { if ( tabs[curtab] == TAB_MISSION ) { String str; if ( sel1 == 0 ) str = StringTable.Localize(missionstr); else str = StringTable.Localize(missionbacklog[sel1-1]); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; } else if ( (tabs[curtab] == TAB_INVENTORY) || ((tabs[curtab] == TAB_TRADING) && sub && (sel0 != -1)) ) { // check that scrollbar is present if ( invlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (!(invlist[i] is 'PuzzleItem') && !(invlist[i] is 'Weapon') && (invlist[i].MaxAmount > 1)) ) str = String.Format("%dx %s",invlist[i].Amount,invlist[i].GetTag()); else str = invlist[i].GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; } else if ( tabs[curtab] == TAB_KEYS ) { // check that scrollbar is present if ( invlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; } else if ( tabs[curtab] == TAB_STORE ) { // check that scrollbar is present if ( storelist.Size() <= 0 ) return res; // definitely no scrollbar xx = 9; yy = 23; int longest = 0, len; int maxcol, totalcol; for ( int i=0; i 1) || (storelist[i] is 'Ammo') ) str = String.Format("(%d/%d) %s",storeunits[i],amt,def.GetTag()); else str = def.GetTag(); } else if ( storeunits[i] > 1 ) str = String.Format("%dx %s",storeunits[i],def.GetTag()); else str = def.GetTag(); len = fnt.StringWidth(str); if ( len > longest ) longest = len; } maxcol = 622/(longest+96); totalcol = int(ceil(storelist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; } else if ( tabs[curtab] == TAB_LIBRARY ) { if ( drag == 1 ) { // check that scrollbar is present if ( lorelist.Size() <= 27 ) return res; // no scrollbar // calculate offset int szr = lorelist.Size()-27; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step ) step += 26; if ( step != ofs0 ) MenuSound("menu/demoscroll"); ofs0 = step; } else if ( drag == 3 ) { // check that scrollbar is present str = StringTable.Localize(lorelist[sel0].text); int ofs = (language~=="jp")?212:132; if ( lorelist.Size() > 26 ) ofs += 8; BrokenLines l = fnt.BreakLines(str,635-ofs); if ( l.Count() > 28 ) l = fnt.BreakLines(str,626-ofs); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel2 ) MenuSound("menu/demoscroll"); ofs2 = sel2 = step; } } else if ( tabs[curtab] == TAB_TRADING ) { if ( !sub ) { // check that scrollbar is present (will it ever be???) if ( playerlist.Size() <= 0 ) return res; // definitely no scrollbar int longest = 0, len; int maxcol, totalcol; for ( int i=0; i longest ) longest = len; } maxcol = 622/(longest+24); totalcol = int(ceil(invlist.Size()/22.)); if ( maxcol >= totalcol ) return res; // no scrollbar // calculate offset int szr = totalcol-maxcol; int step = clamp(rnd((mpos.x-5.)/(630./szr)),0,szr); if ( step > 0 ) step += (maxcol-1); if ( step != (ofs0/22) ) MenuSound("menu/demoscroll"); ofs0 = step*22; } else if ( sel0 == -1 ) { if ( tradelib.ent.Size() <= 28 ) return res; // no scrollbar // calculate offset int szr = tradelib.ent.Size()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; } } else if ( tabs[curtab] == TAB_CHAT ) { let bar = SWWMStatusBar(StatusBar); if ( !bar || (bar.FullHistory.Size() <= 1) ) return res; // no scrollbar // calculate offset int szr = bar.FullHistory.Size()-1; int step = szr-clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; } else if ( tabs[curtab] == TAB_HELP ) { int k1, k2; [k1, k2] = bindings.GetKeysForCommand("openmenu SWWMKnowledgeBaseMenu"); String kstr = bindings.NameKeys(k1,k2); str = String.Format(StringTable.Localize("$SWWM_HELPTXT"),kstr); BrokenLines l = fnt.BreakLines(str,629); if ( l.Count() > 28 ) l = fnt.BreakLines(str,620); else return res; // no scrollbar // calculate offset int szr = l.Count()-28; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; } else if ( tabs[curtab] == TAB_STATS ) { int cnt = 0, num = 22; if ( sel1 == STAT_MAIN ) cnt = statlist.Size(); else if ( sel1 == STAT_KILLS ) cnt = stats.mstats.Size(); else if ( sel1 == STAT_LEVEL ) cnt = filtered_lstats.Size(); else if ( sel1 == STAT_ACHIEVEMENT ) { cnt = achievements.Size(); num = 7; } if ( cnt <= num ) return res; // no scrollbar // calculate offset int szr = cnt-num; int step = clamp(rnd((mpos.y-24.)/(353./szr)),0,szr); if ( step != sel0 ) MenuSound("menu/demoscroll"); ofs0 = sel0 = step; } } else if ( type == MOUSE_Release ) drag = 0; // stop dragging any active scrolls return res; } private bool CmpInventory( Inventory a, Inventory b ) { int ta = 0, tb = 0; if ( a is 'Weapon' ) ta = 2; else if ( (a is 'BackpackItem') || (a is 'HammerspaceEmbiggener') ) ta = 3; else if ( a is 'Ammo' ) ta = 4; else if ( a is 'MagAmmo' ) ta = 5; else if ( (a is 'PowerupGiver') || (a is 'AmmoFabricator') || a.bBIGPOWERUP ) ta = -3; else if ( (a is 'Health') || (a is 'HealthPickup') || (a is 'SWWMHealth') ) ta = -2; else if ( (a is 'Armor') || (a is 'SWWMSpareArmor') ) ta = -1; else if ( a is 'PuzzleItem' ) ta = 1; if ( b is 'Weapon' ) tb = 2; else if ( (b is 'BackpackItem') || (b is 'HammerspaceEmbiggener') ) tb = 3; else if ( b is 'Ammo' ) tb = 4; else if ( b is 'MagAmmo' ) tb = 5; else if ( (b is 'PowerupGiver') || (b is 'AmmoFabricator') || b.bBIGPOWERUP ) tb = -3; else if ( (b is 'Health') || (b is 'HealthPickup') || (b is 'SWWMHealth') ) tb = -2; else if ( (b is 'Armor') || (b is 'SWWMSpareArmor') ) tb = -1; else if ( b is 'PuzzleItem' ) tb = 1; if ( ta == tb ) { if ( a is 'Weapon' ) { bool dummy; int slota, slotb; [dummy, slota] = players[consoleplayer].weapons.LocateWeapon(Weapon(a).GetClass()); if ( slota == 0 ) slota = 10; [dummy, slotb] = players[consoleplayer].weapons.LocateWeapon(Weapon(b).GetClass()); if ( slotb == 0 ) slotb = 10; if ( slota == slotb ) return (Weapon(a).SlotPriority <= Weapon(b).SlotPriority); return slota > slotb; } else if ( a is 'Ammo' ) { Class usesa, usesb; for ( int i=0; i)(AllActorClasses[i]); if ( !w ) continue; let def = GetDefaultByType(w); if ( w is 'SWWMWeapon' ) { if ( SWWMWeapon(def).UsesAmmo(Ammo(a).GetClass()) ) usesa = w; if ( SWWMWeapon(def).UsesAmmo(Ammo(b).GetClass()) ) usesb = w; } else { if ( (def.AmmoType1 == Ammo(a).GetClass()) || (def.AmmoType2 == Ammo(a).GetClass()) ) usesa = w; if ( (def.AmmoType1 == Ammo(b).GetClass()) || (def.AmmoType2 == Ammo(b).GetClass()) ) usesb = w; } if ( usesa && usesb ) break; } if ( usesa && usesb ) { bool dummy; int slota, slotb; [dummy, slota] = players[consoleplayer].weapons.LocateWeapon(usesa); if ( slota == 0 ) slota = 10; [dummy, slotb] = players[consoleplayer].weapons.LocateWeapon(usesb); if ( slotb == 0 ) slotb = 10; if ( slota == slotb ) { // sort by unit value int vala = abs(a.default.Stamina); int valb = abs(b.default.Stamina); if ( vala == valb ) { // sort alphabetically return a.GetTag() > b.GetTag(); } return vala > valb; } return slota > slotb; } } else if ( a is 'MagAmmo' ) { // oh boy this one gets complicated Class pamoa, pamob; pamoa = MagAmmo(a).ParentAmmo; pamob = MagAmmo(b).ParentAmmo; Class usesa, usesb; for ( int i=0; i)(AllActorClasses[i]); if ( !w ) continue; let def = GetDefaultByType(w); if ( w is 'SWWMWeapon' ) { if ( SWWMWeapon(def).UsesAmmo(pamoa) ) usesa = w; if ( SWWMWeapon(def).UsesAmmo(pamob) ) usesb = w; } else { if ( (def.AmmoType1 == pamoa) || (def.AmmoType2 == pamoa) ) usesa = w; if ( (def.AmmoType1 == pamob) || (def.AmmoType2 == pamob) ) usesb = w; } if ( usesa && usesb ) break; } if ( usesa && usesb ) { bool dummy; int slota, slotb; [dummy, slota] = players[consoleplayer].weapons.LocateWeapon(usesa); if ( slota == 0 ) slota = 10; [dummy, slotb] = players[consoleplayer].weapons.LocateWeapon(usesb); if ( slotb == 0 ) slotb = 10; if ( slota == slotb ) { // sort by unit value int vala = GetDefaultByType(pamoa).Stamina; int valb = GetDefaultByType(pamob).Stamina; if ( vala == valb ) { // sort alphabetically return a.GetTag() > b.GetTag(); } return vala > valb; } return slota > slotb; } } else { // sort by unit value int vala = abs(a.default.Stamina); int valb = abs(b.default.Stamina); if ( vala == valb ) { // sort alphabetically return a.GetTag() > b.GetTag(); } return vala > valb; } } return ta > tb; } private bool CmpInventoryClass( Class a, Class b ) { int ta = 0, tb = 0; let da = GetDefaultByType(a); let db = GetDefaultByType(b); if ( a is 'Weapon' ) ta = 4; else if ( a is 'HammerspaceEmbiggener' ) ta = 3; else if ( a is 'Ammo' ) ta = 2; else if ( a is 'MagAmmo' ) ta = 1; else if ( (a is 'PowerupGiver') || (a is 'AmmoFabricator') || da.bBIGPOWERUP ) ta = 5; else if ( (a is 'Health') || (a is 'HealthPickup') || (a is 'SWWMHealth') ) ta = 7; else if ( (a is 'Armor') || (a is 'SWWMSpareArmor') ) ta = 6; if ( b is 'Weapon' ) tb = 4; else if ( b is 'HammerspaceEmbiggener' ) tb = 3; else if ( b is 'Ammo' ) tb = 2; else if ( b is 'MagAmmo' ) ta = 1; else if ( (b is 'PowerupGiver') || (b is 'AmmoFabricator') || db.bBIGPOWERUP ) tb = 5; else if ( (b is 'Health') || (b is 'HealthPickup') || (b is 'SWWMHealth') ) tb = 7; else if ( (b is 'Armor') || (b is 'SWWMSpareArmor') ) tb = 6; if ( ta == tb ) { // sort by unit price if ( abs(da.Stamina) == abs(db.Stamina) ) { // sort alphabetically return da.GetTag() > db.GetTag(); } return abs(da.Stamina) > abs(db.Stamina); } return ta < tb; } private int partition_inventory( Array a, int l, int h ) { Inventory pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpInventory(pv,a[j]) ) { i++; Inventory tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Inventory tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_inventory( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_inventory(a,l,h); qsort_inventory(a,l,p-1); qsort_inventory(a,p+1,h); } private int partition_store( Array > a, Array b, int l, int h ) { Class pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpInventoryClass(pv,a[j]) ) { i++; Class tmp = a[j]; a[j] = a[i]; a[i] = tmp; int tmpi = b[j]; b[j] = b[i]; b[i] = tmpi; } } Class tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; int tmpi = b[h]; b[h] = b[i+1]; b[i+1] = tmpi; return i+1; } private void qsort_store( Array > a, Array b, int l, int h ) { if ( l >= h ) return; int p = partition_store(a,b,l,h); qsort_store(a,b,l,p-1); qsort_store(a,b,p+1,h); } private bool CmpMonsterKill( MonsterKill a, MonsterKill b ) { if ( a.kills == b.kills ) { // sort by name instead String taga, tagb; taga = GetDefaultByType(a.m).GetTag(); tagb = GetDefaultByType(b.m).GetTag(); // beautify if there's no tag if ( taga == FallbackTag ) { taga = a.m.GetClassName(); SWWMUtility.BeautifyClassName(taga); } if ( tagb == FallbackTag ) { tagb = a.m.GetClassName(); SWWMUtility.BeautifyClassName(tagb); } return taga > tagb; } return a.kills < b.kills; } private int partition_mstats( Array a, int l, int h ) { MonsterKill pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpMonsterKill(pv,a[j]) ) { i++; MonsterKill tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } MonsterKill tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_mstats( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_mstats(a,l,h); qsort_mstats(a,l,p-1); qsort_mstats(a,p+1,h); } private int partition_lore( Array a, int l, int h ) { SWWMLore pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( StringTable.Localize(pv.tag) > StringTable.Localize(a[j].tag) ) { i++; SWWMLore tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } SWWMLore tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_lore( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_lore(a,l,h); qsort_lore(a,l,p-1); qsort_lore(a,p+1,h); } private bool CmpCollectible( Inventory a, Inventory b ) { if ( a.Stamina == b.Stamina ) { // alphabetic return a.GetTag() > b.GetTag(); } // descending value return a.default.Stamina < b.default.Stamina; } private int partition_collectible( Array a, int l, int h ) { Inventory pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpCollectible(pv,a[j]) ) { i++; Inventory tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Inventory tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_collectible( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_collectible(a,l,h); qsort_collectible(a,l,p-1); qsort_collectible(a,p+1,h); } private bool CmpAchievement( SWWMAchievement a, SWWMAchievement b ) { bool adone = !!(a.state.GetInt()), bdone = !!(b.state.GetInt()); double afactor = adone?1.:0., bfactor = bdone?1.:0.; if ( a.progress ) { int cur = clamp(a.progress.GetInt(),0,a.maxval); afactor = cur/double(a.maxval); } if ( b.progress ) { int cur = clamp(b.progress.GetInt(),0,b.maxval); bfactor = cur/double(b.maxval); } // sort by base index if ( adone && bdone ) return a.baseindex > b.baseindex; if ( !adone && !bdone ) { // progress sort? if ( afactor != bfactor ) return afactor < bfactor; // sort by base index return a.baseindex > b.baseindex; } // state sort return bdone; } private int partition_achievements( Array a, int l, int h ) { SWWMAchievement pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpAchievement(pv,a[j]) ) { i++; SWWMAchievement tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } SWWMAchievement tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_achievements( Array a, int l, int h ) { if ( l >= h ) return; int p = partition_achievements(a,l,h); qsort_achievements(a,l,p-1); qsort_achievements(a,p+1,h); } override void Ticker() { Super.Ticker(); if ( (players[consoleplayer].Health <= 0) || isclosing ) { // ded (or force close) EventHandler.SendNetworkEvent("swwmclearalltransactions",consoleplayer); 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) ) StatusBar.Tick(); if ( tabs[curtab] == TAB_INVENTORY ) { invlist.Clear(); // alphabetically sorted inventory for ( Inventory inv=players[consoleplayer].mo.Inv; inv; inv=inv.Inv ) { if ( (inv.Amount <= 0) || !inv.SpawnState.ValidateSpriteFrame() || (inv is 'Key') || (inv is 'BasicArmor') || (inv is 'HexenArmor') || (inv is 'Powerup') || (inv is 'SWWMArmor') || (!(inv is 'Ammo') && !(inv is 'Weapon') && !inv.bINVBAR) && !(inv is 'HammerspaceEmbiggener') && !(inv is 'SWWMCollectible') && !(inv is 'MagAmmo') || (inv is 'SWWMCollectible') ) continue; // no hidden weapons if ( (inv is 'SWWMWeapon') && SWWMWeapon(inv).bHIDEINMENU ) continue; String tag = inv.GetTag(); bool greater = false; for ( int i=0; i tag2 ) continue; greater = true; invlist.Insert(i,inv); break; } if ( greater ) continue; invlist.Push(inv); } // re-sort by category qsort_inventory(invlist,0,invlist.Size()-1); // crop selection if ( sel0 >= invlist.Size() ) sel0 = max(0,invlist.Size()-1); } else if ( (tabs[curtab] == TAB_STATS) && (sel1 == STAT_KILLS) ) { sorted_mstats.Copy(stats.mstats); qsort_mstats(sorted_mstats,0,sorted_mstats.Size()-1); } else if ( tabs[curtab] == TAB_KEYS ) { invlist.Clear(); if ( !deathmatch ) { // keys sorted by their index int n = Key.GetKeyTypeCount(); for ( int i=0; i pc = invlist[i].Species; if ( !pc ) continue; for ( int j=0; j= j ) i--; } } } // collectibles, sorted by name Array cols; for ( Inventory inv=players[consoleplayer].mo.Inv; inv; inv=inv.Inv ) { if ( inv is 'SWWMCollectible' ) cols.Push(inv); } qsort_collectible(cols,0,cols.Size()-1); invlist.Append(cols); // crop selection if ( sel0 >= invlist.Size() ) sel0 = max(0,invlist.Size()-1); } else if ( tabs[curtab] == TAB_STORE ) { Class lastsel = null; if ( storelist.Size() > 0 ) lastsel = storelist[sel0]; storelist.Clear(); storeunits.Clear(); // price-sorted buyables/sellables for ( int i=0; i)(AllActorClasses[i]); if ( !type ) continue; // no collectibles if ( type is 'SWWMCollectible' ) continue; // no barriers outside doom if ( !(gameinfo.gametype&GAME_DOOM) && (type is 'EBarrier') ) continue; // no gravity outside raven if ( !(gameinfo.gametype&GAME_RAVEN) && (type is 'GravitySuppressor') ) continue; // can't sell candygun spares if ( sub && (type is 'CandyGunSpares') ) continue; let cur = players[consoleplayer].mo.FindInventory(type); // skip items we don't own or are depleted if selling if ( sub && (!cur || (cur.Amount <= 0)) ) continue; else if ( !sub ) { // skip maxed items if buying if ( cur && (cur.Amount >= cur.MaxAmount) ) continue; // ignore ammo for weapons not owned if buying bool notownedammo = false; if ( type is 'Ammo' ) { notownedammo = true; let amo = (Class)(type); for ( Inventory inv=players[consoleplayer].mo.Inv; inv; inv=inv.inv ) { if ( !(inv is 'Weapon') ) continue; if ( (Weapon(inv).AmmoType1 == amo) || (Weapon(inv).AmmoType2 == amo) ) { notownedammo = false; break; } if ( (inv is 'SWWMWeapon') && SWWMWeapon(inv).UsesAmmo(amo) ) { notownedammo = false; break; } } } if ( notownedammo ) continue; } let inv = GetDefaultByType(type); // skip unimplemented weapons if ( type is 'Weapon' ) { let ready = inv.FindState("Ready"); if ( !ready || !ready.ValidateSpriteFrame() ) continue; } // ignore child ammos if ( (type is 'Ammo') && (type.GetParentClass() != 'Ammo') ) continue; if ( inv.Stamina == 0 ) continue; // items with negative stamina can only be sold if ( (inv.Stamina < 0) && !sub ) continue; int amt = inv.Amount; int price = int(abs(inv.Stamina)*(1.+.75*(amt-1))); if ( type is 'Ammo' ) { int maxamt; if ( sub ) maxamt = cur.Amount; // we can sell ALL our ammo else maxamt = cur?(cur.MaxAmount-cur.Amount):inv.MaxAmount; // get the largest affordable child pickup amount (that we need, or we can sell) for ( int j=0; j)(AllActorClasses[j]); if ( !type2 || (type2.GetParentClass() != type) ) continue; let inv2 = GetDefaultByType(type2); int cprice = int(abs(inv.Stamina)*(1.+.75*(inv2.Amount-1))); if ( (inv2.Amount > amt) && (inv2.Amount <= maxamt) && (sub || SWWMCredits.CanTake(players[consoleplayer],cprice)) ) { price = cprice; amt = inv2.Amount; } } } // nuggets can be bought/sold in bulk else if ( type is 'HealthNuggetItem' ) { let inv2 = GetDefaultByType(SWWMHealth(inv).giveme); int maxamt; if ( sub ) maxamt = cur.Amount; else { maxamt = (inv2.MaxAmount-players[consoleplayer].Health); maxamt += cur?(cur.MaxAmount-cur.Amount):inv.MaxAmount; } for ( int j=5; j<=20; j+=5 ) { int cprice = int(inv.Stamina*(1.+.75*(j-1))); if ( (j <= maxamt) && (sub || SWWMCredits.CanTake(players[consoleplayer],cprice)) ) { price = cprice; amt = j; } } } else if ( type is 'ArmorNuggetItem' ) { let inv2 = GetDefaultByType(SWWMSpareArmor(inv).giveme); let cur2 = players[consoleplayer].mo.FindInventory(SWWMSpareArmor(inv).giveme); int maxamt; if ( sub ) maxamt = cur.Amount; else { maxamt = cur2?(cur2.MaxAmount-cur2.Amount):inv2.MaxAmount; maxamt += cur?(cur.MaxAmount-cur.Amount):inv.MaxAmount; } for ( int j=5; j<=20; j+=5 ) { int cprice = int(inv.Stamina*(1.+.75*(j-1))); if ( (j <= maxamt) && (sub || SWWMCredits.CanTake(players[consoleplayer],cprice)) ) { price = cprice; amt = j; } } } // sort by unit price bool greater = false; for ( int j=0; j abs(inv2.Stamina) ) continue; greater = true; storelist.Insert(j,type); storeunits.Insert(j,amt); break; } if ( greater ) continue; storelist.Push(type); storeunits.Push(amt); } // re-sort by category qsort_store(storelist,storeunits,0,storelist.Size()-1); // shift back if the last selected item was removed if ( lastsel && (storelist.Find(lastsel)) >= storelist.Size() ) sel0 = max(0,sel0-1); // crop selection if ( sel0 >= storelist.Size() ) sel0 = max(0,storelist.Size()-1); } else if ( tabs[curtab] == TAB_LIBRARY ) { // remember last position/size SWWMLore oldone = null; int oldsiz = lorelist.Size(); if ( lorelist.Size() > 0 ) oldone = lorelist[sel0]; // lore lore lore lorelist.Clear(); for ( int i=0; i 0) && (idx < lorelist.Size()) ) sel0 = idx; // update ofs so selection stays on-screen int ofs = max(ofs0-26,0); if ( sel0 < ofs ) ofs0 = sel0+26; else if ( sel0 > ofs+26 ) ofs0 = sel0; } // add "new entries" message if ( lorelib.ent.Size() != oldloresiz ) { tmsg = StringTable.Localize("$SWWM_NEWLORE"); tmsgtic = gametic+70; } oldloresiz = lorelib.ent.Size(); } else if ( tabs[curtab] == TAB_TRADING ) { // build player list int oldnplayers = playerlist.Size(); playerlist.Clear(); for ( int i=0; i