// THE LORE Class DemolitionistLibraryTab : DemolitionistMenuTab { DemolitionistMenuList lists[4]; int section; String sname[4]; int lwidth; SWWMLoreLibrary lore; int loresz; int ofs[4], maxofs[4]; double smofs[4]; bool drag[2]; int sel[4]; DemolitionistMenuLoreItem active; SWWMLore clore; DemolitionistMenuTextBox ltext; private int partition_lore( Array a, int l, int h ) { DemolitionistMenuListItem pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( StringTable.Localize(DemolitionistMenuLoreItem(pv).ent.tag) > StringTable.Localize(DemolitionistMenuLoreItem(a[j]).ent.tag) ) { 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_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); } override DemolitionistMenuTab Init( DemolitionistMenu master ) { title = StringTable.Localize("$SWWM_KBASETAB"); lore = SWWMLoreLibrary.Find(players[consoleplayer]); section = 0; lwidth = 0; for ( int i=0; i<4; i++ ) { sname[i] = StringTable.Localize("$SWWM_LORETAB"..i); lwidth = max(lwidth,master.mSmallFont.StringWidth("<‼ "..sname[i].." ‼>")+6); lists[i] = new("DemolitionistMenuList"); lists[i].master = master; } loresz = lore.ent.Size(); for ( int i=0; i loresz ) { int olwidth = lwidth; int olmaxofs = maxofs[section]; // append the new entries for ( int i=loresz; i maxofs[i] ) smofs[i] = ofs[i] = maxofs[i]; for ( int j=0; j0)?8:0)); } // update smooth scroll smofs[section] = (smofs[section]*.6)+(ofs[section]*.4); if ( abs(smofs[section]-ofs[section]) < (1./master.hs) ) smofs[section] = ofs[section]; // tick the current list lists[section].Ticker(); // tick the text box if ( ltext ) ltext.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[section] <= 0 ) return false; int oldofs = ofs[section]; ofs[section] = clamp(ofs[section]+speed,0,maxofs[section]); return (ofs[section] != oldofs); } // called when clicking on our scrollbar // returns true if the position actually changed // y: relative click position bool SetOffset( double y ) { if ( maxofs[section] <= 0 ) return false; int oldofs = ofs[section]; ofs[section] = clamp(int(round((y-20.5)/((master.ws.y-41.)/maxofs[section]))),0,maxofs[section]); return (ofs[section] != oldofs); } // called when keyboard scrolling void KBScroll() { if ( maxofs[section] <= 0 ) return; int miny = (lists[section].selected+1)*14-int(master.ws.y-50); int maxy = lists[section].selected*14; if ( ofs[section] < miny ) ofs[section] = clamp(miny,0,maxofs[section]); else if ( ofs[section] > maxy ) ofs[section] = clamp(maxy,0,maxofs[section]); } override void MenuInput( int key ) { switch ( key ) { case MK_LEFT: int prev = max(0,section-1); while ( prev > 0 ) { if ( lists[prev].items.Size() > 0 ) break; prev--; } if ( prev == section ) break; master.MenuSound("menu/demoscroll"); smofs[section] = ofs[section]; section = prev; smofs[section] = ofs[section]; if ( clore && ltext ) ltext.Reinit(StringTable.Localize(clore.text),lwidth+((maxofs[section]>0)?8:0)); break; case MK_RIGHT: int next = min(3,section+1); while ( next < 3 ) { if ( lists[next].items.Size() > 0 ) break; next++; } if ( next == section ) break; master.MenuSound("menu/demoscroll"); smofs[section] = ofs[section]; section = next; smofs[section] = ofs[section]; if ( clore && ltext ) ltext.Reinit(StringTable.Localize(clore.text),lwidth+((maxofs[section]>0)?8:0)); break; case MK_DOWN: if ( clore && ltext ) { if ( ltext.Scroll(16) ) master.MenuSound("menu/demoscroll"); break; } if ( sel[section] < lists[section].items.Size()-1 ) { lists[section].selected = ++sel[section]; master.MenuSound("menu/demoscroll"); KBScroll(); } break; case MK_UP: if ( clore && ltext ) { if ( ltext.Scroll(-16) ) master.MenuSound("menu/demoscroll"); break; } if ( sel[section] > 0 ) { lists[section].selected = --sel[section]; master.MenuSound("menu/demoscroll"); KBScroll(); } break; case MK_ENTER: if ( clore ) break; if ( active ) active.bActive = false; active = DemolitionistMenuLoreItem(lists[section].items[sel[section]]); active.bActive = true; clore = active.ent; if ( !ltext ) ltext = new("DemolitionistMenuTextBox").Init(master,StringTable.Localize(clore.text),lwidth+((maxofs[section]>0)?8:0)); else ltext.Reinit(StringTable.Localize(clore.text),lwidth+((maxofs[section]>0)?8:0)); ltext.img = clore.img; master.MenuSound("menu/demosel"); active.MarkRead(); break; case MK_BACK: if ( !clore ) break; if ( active ) active.bActive = false; clore = null; master.MenuSound("menu/democlose"); break; } } override void MouseInput( Vector2 pos, int btn ) { // global events switch ( btn ) { case MB_DRAG: if ( clore && ltext && drag[1] ) ltext.SetOffset(pos.y); else if ( drag[0] ) SetOffset(pos.y); break; case MB_RELEASE: drag[0] = drag[1] = false; break; } // mouse on left side if ( pos.x < (lwidth+((maxofs[section]>0)?8:0)) ) { // see if we're clicking the scrollbar (if it exists) if ( (btn == MB_LEFT) && (maxofs[section] > 0) && (pos.x >= lwidth) ) { SetOffset(pos.y); master.MenuSound("menu/demoscroll"); drag[0] = true; return; } // mouse on the category sub-tab if ( pos.y < 28 ) { switch ( btn ) { case MB_LEFT: if ( pos.x < (lwidth/2) ) MenuInput(MK_LEFT); else MenuInput(MK_RIGHT); break; case MB_WHEELUP: MenuInput(MK_LEFT); break; case MB_WHEELDOWN: MenuInput(MK_RIGHT); break; } return; } switch ( btn ) { case MB_LEFT: // find which element we clicked for ( int i=0; i (master.ws.x-8)) ) { ltext.SetOffset(pos.y); master.MenuSound("menu/demoscroll"); drag[1] = true; } break; case MB_WHEELUP: if ( ltext.Scroll(-8) ) master.MenuSound("menu/demoscroll"); break; case MB_WHEELDOWN: if ( ltext.Scroll(8) ) master.MenuSound("menu/demoscroll"); break; } } override void Drawer( double fractic ) { double xx = 0; double yy = 15; String str = sname[section]; master.DrawVSeparator(lwidth,14,master.ws.y-28); master.DrawHSeparator(0,28,lwidth); Screen.DrawText(master.mSmallFont,Font.CR_FIRE,master.origin.x+(lwidth-master.mSmallFont.StringWidth(str))/2,master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); bool hasprev = false; bool hasunreadprev = false; for ( int i=0; i",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); if ( hasunreadnext ) Screen.DrawText(master.mSmallFont,Font.CR_GOLD,master.origin.x+lwidth-15,master.origin.y+yy,"‼",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); xx = 3; yy = 32; double ssmofs = (smofs[section]~==ofs[section])?smofs[section]:(smofs[section]*(1.-fractic)+((smofs[section]*.6)+(ofs[section]*.4))*fractic); Screen.SetClipRect(int((master.origin.x+3)*master.hs),int((master.origin.y+32)*master.hs),int((lwidth-6)*master.hs),int((master.ws.y-50)*master.hs)); lists[section].Drawer((xx,yy-ssmofs)); Screen.ClearClipRect(); if ( maxofs[section] > 0 ) { xx = lwidth; master.DrawVSeparator(xx+8,14,master.ws.y-28); xx += 2; yy = floor(ssmofs*((master.ws.y-39)/maxofs[section]))+14; Screen.DrawText(master.mSmallFont,Font.CR_FIRE,master.origin.x+xx,master.origin.y+yy,"▮",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } if ( clore && ltext ) ltext.Drawer(fractic); else { str = StringTable.Localize("$SWWM_LOREUNSEL"); int lwx = lwidth+((maxofs[section]>0)?8:0); xx = lwx+int((master.ws.x-lwx)-master.mSmallFont.StringWidth(str))/2; yy = int(master.ws.y-master.mSmallFont.GetHeight())/2; Screen.DrawText(master.mSmallFont,Font.CR_FIRE,master.origin.x+xx,master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } } } // lore entry Class DemolitionistMenuLoreItem : DemolitionistMenuListItem { SWWMLore ent; bool bActive; // currently being read DemolitionistMenuLoreItem Init( DemolitionistMenu master, SWWMLore e ) { Super.Init(master,""); ent = e; label = StringTable.Localize(ent.tag); return self; } override int GetWidth() { if ( !ent.read ) return master.mSmallFont.StringWidth("‼"..label); return master.mSmallFont.StringWidth(label); } // marks this entry as read void MarkRead() { if ( ent.read ) return; EventHandler.SendNetworkEvent(String.Format("swwmmarkloreread.%s",ent.tag),consoleplayer); } override void Drawer( Vector2 pos, bool selected ) { String str = label; if ( !ent.read ) str = "\cf‼\c-"..label; Screen.DrawText(master.mSmallFont,bActive?Font.CR_FIRE:Font.CR_WHITE,master.origin.x+pos.x,master.origin.y+pos.y,str,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)); } }