// inventory listing tab Class DemolitionistInventoryTab : DemolitionistMenuTab { DemolitionistMenuList invlist; int ofs, maxofs; double smofs; bool drag; 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 = -4; 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; else if ( a.bINVBAR ) ta = -3; 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 = -4; 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; else if ( b.bINVBAR ) tb = -3; if ( ta == tb ) { if ( a is 'Weapon' ) { let [dummya, slota] = players[consoleplayer].weapons.LocateWeapon(Weapon(a).GetClass()); if ( slota == 0 ) slota = 10; let [dummyb, 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; foreach ( cls:AllActorClasses ) { let w = (Class)(cls); 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 ) { let [dummya, slota] = players[consoleplayer].weapons.LocateWeapon(usesa); if ( slota == 0 ) slota = 10; let [dummyb, 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; foreach ( cls:AllActorClasses ) { let w = (Class)(cls); 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 ) { let [dummya, slota] = players[consoleplayer].weapons.LocateWeapon(usesa); if ( slota == 0 ) slota = 10; let [dummyb, 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 int partition_inventory( Array a, int l, int h ) { DemolitionistMenuListItem pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpInventory(DemolitionistMenuInvItem(pv).inv,DemolitionistMenuInvItem(a[j]).inv) ) { i++; DemolitionistMenuListItem tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } DemolitionistMenuListItem 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); } override DemolitionistMenuTab Init( DemolitionistMenu master ) { title = StringTable.Localize("$SWWM_INVTAB"); return Super.Init(master); } override void OnDestroy() { Super.OnDestroy(); if ( invlist ) invlist.Destroy(); } override void OnSelect() { smofs = ofs; } override void OnDeselect() { smofs = ofs; } private bool FilterInventory( Inventory inv ) { if ( !inv || (inv.Owner != players[consoleplayer].mo) ) return true; 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') ) return true; // no hidden weapons if ( (inv is 'SWWMWeapon') && SWWMWeapon(inv).bHIDEINMENU ) return true; return false; } // notified that this item has already been used up, so remove it from the list void UsedUp( Class itm ) { for ( int i=0; i i ) invlist.selected = max(0,invlist.selected-1); i--; } } override void Ticker() { bool mustsort = false; bool skipsel = false; if ( !invlist ) { mustsort = true; skipsel = true; // initialize invlist = new('DemolitionistMenuList'); invlist.master = master; invlist.selected = 0; for ( Inventory i=players[consoleplayer].mo.inv; i; i=i.inv ) { if ( FilterInventory(i) ) continue; // make sure we sort alphabetically on first insert String tag = i.GetTag(); bool greater = false; for ( int j=0; j tag2 ) continue; greater = true; invlist.items.Insert(j,new('DemolitionistMenuInvItem').Init(master,i)); break; } if ( greater ) continue; invlist.items.Push(new('DemolitionistMenuInvItem').Init(master,i)); } } else { // check if current entries must be deleted for ( int i=0; i i ) invlist.selected = max(0,invlist.selected-1); i--; } // check if new entries must be added for ( Inventory inv=players[consoleplayer].mo.inv; inv; inv=inv.inv ) { if ( FilterInventory(inv) ) continue; // skip if it's already there bool skipme = false; foreach ( i:invlist.items ) { if ( DemolitionistMenuInvItem(i).inv != inv ) continue; skipme = true; break; } if ( skipme ) continue; // make sure we sort alphabetically on first insert String tag = inv.GetTag(); bool greater = false; for ( int j=0; j tag2 ) continue; greater = true; invlist.items.Insert(j,new('DemolitionistMenuInvItem').Init(master,inv)); mustsort = true; break; } if ( greater ) continue; invlist.items.Push(new('DemolitionistMenuInvItem').Init(master,inv)); mustsort = true; } } // don't do anything if empty if ( invlist.items.Size() <= 0 ) return; // sort inventory if ( mustsort ) { DemolitionistMenuListItem csel = invlist.items[invlist.selected]; qsort_inventory(invlist.items,0,invlist.items.Size()-1); let idx = invlist.items.Find(csel); if ( !skipsel && (idx != invlist.items.Size()) && (idx != invlist.selected) ) invlist.selected = idx; } // rearrange all item positions maxofs = 0; int maxw = invlist.GetWidth(); int xx = 0; int yy = 0; foreach ( i:invlist.items ) { maxofs = max(maxofs,int(xx+i.GetWidth())); i.xpos = xx; i.ypos = yy; yy += 16; if ( yy > (master.ws.y-62) ) { xx += maxw+16; // pad yy = 0; } } maxofs = max(0,maxofs-int(master.ws.x-18)); if ( ofs > maxofs ) smofs = ofs = maxofs; // update smooth scroll smofs = (smofs*.6)+(ofs*.4); if ( abs(smofs-ofs) < (1./master.hs) ) smofs = ofs; // tick the list invlist.Ticker(); } // called when sending a scroll input // returns true if the position actually changed // speed: how many pixels to move (either back or forward) bool Scroll( int speed ) { if ( maxofs <= 0 ) return false; int oldofs = ofs; ofs = clamp(ofs+speed,0,maxofs); return (ofs != oldofs); } // called when clicking on our scrollbar // returns true if the position actually changed // x: relative click position bool SetOffset( double x ) { if ( maxofs <= 0 ) return false; int oldofs = ofs; ofs = clamp(int(round((x-5.)/((master.ws.x-10.)/maxofs))),0,maxofs); return (ofs != oldofs); } // called when keyboard scrolling void KBScroll() { if ( maxofs <= 0 ) return; int minx = (invlist.items[invlist.selected].xpos+invlist.items[invlist.selected].GetWidth())-int(master.ws.x-18); int maxx = invlist.items[invlist.selected].xpos; if ( ofs < minx ) ofs = clamp(minx,0,maxofs); else if ( ofs > maxx ) ofs = clamp(maxx,0,maxofs); } override void MenuInput( int key ) { if ( !invlist || (invlist.items.Size() <= 0) ) return; switch ( key ) { case MK_LEFT: int prev = max(0,invlist.selected-22); if ( prev == invlist.selected ) break; invlist.selected = prev; master.MenuSound("menu/demoscroll"); KBScroll(); break; case MK_RIGHT: int next = min(invlist.items.Size()-1,invlist.selected+22); if ( next == invlist.selected ) break; invlist.selected = next; master.MenuSound("menu/demoscroll"); KBScroll(); break; case MK_DOWN: if ( invlist.selected < invlist.items.Size()-1 ) { invlist.selected++; master.MenuSound("menu/demoscroll"); KBScroll(); } break; case MK_UP: if ( invlist.selected > 0 ) { invlist.selected--; master.MenuSound("menu/demoscroll"); KBScroll(); } break; case MK_ENTER: if ( invlist.selected >= invlist.items.Size() ) break; DemolitionistMenuInvItem(invlist.items[invlist.selected]).UseItem(); break; case MK_BACK: if ( invlist.selected >= invlist.items.Size() ) break; DemolitionistMenuInvItem(invlist.items[invlist.selected]).DropItem(); break; } } override void MouseInput( Vector2 pos, int btn ) { if ( !invlist || (invlist.items.Size() <= 0) ) return; switch ( btn ) { case MB_LEFT: // see if we're clicking the scrollbar (if it exists) if ( pos.y > (master.ws.y-21) ) { if ( SetOffset(pos.x) ) { master.MenuSound("menu/demoscroll"); drag = true; } break; } // find which element we clicked for ( int i=0; i used; bool skipresult; } extend Class DemolitionistMenu { Array checklist; int lasttuid; void ItemUsed( int uid, bool result, bool usedup ) { for ( int i=0; i)(checklist[i].used); if ( w ) { // play if actually switching if ( result ) { String snd = GetDefaultByType(w).UpSound; MenuSound(snd); } } else if ( (checklist[i].used is 'SWWMCollectible') || (checklist[i].used is 'Key') ) MenuSound("demolitionist/handsup"); else { String snd = GetDefaultByType(checklist[i].used).UseSound; MenuSound(snd); } } } else { tmsg = StringTable.Localize("$SWWM_INVFAIL"); tmsgtic = MenuTime()+70; MenuSound("menu/noinvuse"); } if ( usedup ) { int j = FindTabType('DemolitionistInventoryTab'); if ( j != -1 ) DemolitionistInventoryTab(tabs[j]).UsedUp(checklist[i].used); } checklist.Delete(i); i--; } } void ItemDropped( int uid, bool result, bool usedup ) { for ( int i=0; i 1) || (!(inv is 'PuzzleItem') && !(inv is 'Weapon') && (inv.MaxAmount > 1)) ) label = String.Format("%dx %s",inv.Amount,inv.GetTag()); else label = inv.GetTag(); if ( inv is 'SWWMCollectible' ) label.AppendFormat(" \cj(\cg¥\cf%d\cj)\c-",inv.default.Stamina); } override void Ticker() { UpdateLabel(); } override void Drawer( Vector2 pos, bool selected ) { // the simplest drawer Screen.DrawText(master.mSmallFont,col,master.origin.x+pos.x,master.origin.y+pos.y,label,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,selected?Color(0,0,0,0):Color(96,0,0,0)); } void UseItem() { if ( !inv || (inv is 'Ammo') || (inv is 'MagAmmo') || (inv is 'HammerspaceEmbiggener') ) return; // weapons can't be switched to if they have no ammo if ( (inv is 'Weapon') && (((inv is 'SWWMWeapon') && !SWWMWeapon(inv).ReportHUDAmmo()) || (!(inv is 'SWWMWeapon') && ((!Weapon(inv).Ammo1 || (Weapon(inv).Ammo1.Amount > 0) || Weapon(inv).bAMMO_OPTIONAL) || (Weapon(inv).Ammo2 && ((Weapon(inv).Ammo2.Amount > 0) || Weapon(inv).bALT_AMMO_OPTIONAL))))) ) { master.MenuSound("menu/noinvuse"); master.tmsg = StringTable.Localize("$SWWM_INVNAMMO"); master.tmsgtic = Menu.MenuTime()+70; return; } let t = new('MenuTransaction'); t.uid = master.GenTUID(); t.type = MenuTransaction.TT_ITEMUSE; t.used = inv.GetClass(); // don't check weapons, keys or collectibles, always assume check succeeded t.skipresult = ((inv is 'Weapon') || (inv is 'Key') || (inv is 'SWWMCollectible')); master.checklist.Push(t); EventHandler.SendNetworkCommand("swwmuseitem",NET_INT,t.uid,NET_INT,consoleplayer,NET_INT,inv.GetNetworkID()); } void DropItem() { if ( !inv || (inv is 'Key') || (inv is 'SWWMCollectible') ) return; let t = new('MenuTransaction'); t.uid = master.GenTUID(); t.type = MenuTransaction.TT_ITEMDROP; t.skipresult = false; master.checklist.Push(t); EventHandler.SendNetworkCommand("swwmdropitem",NET_INT,t.uid,NET_INT,consoleplayer,NET_INT,inv.GetNetworkID()); } }