457 lines
13 KiB
Text
457 lines
13 KiB
Text
// 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<DemolitionistMenuListItem> 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<DemolitionistMenuListItem> 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; i++ )
|
|
{
|
|
let ent = lore.ent[i];
|
|
let le = new("DemolitionistMenuLoreItem").Init(master,ent);
|
|
lists[ent.tab].items.Push(le);
|
|
lwidth = max(lwidth,master.mSmallFont.StringWidth("‼"..le.label)+6);
|
|
}
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
qsort_lore(lists[i].items,0,lists[i].items.Size()-1);
|
|
maxofs[i] = max(0,lists[i].items.Size()*14-int(master.ws.y-50));
|
|
for ( int j=0; j<lists[i].items.Size(); j++ )
|
|
lists[i].items[j].ypos = j*14;
|
|
}
|
|
return Super.Init(master);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
if ( !lists[i] ) continue;
|
|
lists[i].Destroy();
|
|
}
|
|
if ( ltext ) ltext.Destroy();
|
|
}
|
|
override void OnSelect()
|
|
{
|
|
section = master.shnd.menustate.At("LastLoreSection").ToInt();
|
|
smofs[section] = ofs[section];
|
|
}
|
|
override void OnDeselect()
|
|
{
|
|
master.shnd.menustate.Insert("LastLoreSection",String.Format("%d",section));
|
|
smofs[section] = ofs[section];
|
|
}
|
|
override void Ticker()
|
|
{
|
|
// update library (if anything changes)
|
|
if ( lore.ent.Size() > loresz )
|
|
{
|
|
int olwidth = lwidth;
|
|
int olmaxofs = maxofs[section];
|
|
// append the new entries
|
|
for ( int i=loresz; i<lore.ent.Size(); i++ )
|
|
{
|
|
let ent = lore.ent[i];
|
|
let le = new("DemolitionistMenuLoreItem").Init(master,ent);
|
|
lists[ent.tab].items.Push(le);
|
|
lwidth = max(lwidth,master.mSmallFont.StringWidth("‼"..le.label)+6);
|
|
}
|
|
// re-sort the lists, ensuring we are still selecting the same entry
|
|
for ( int i=0; i<4; i++ )
|
|
{
|
|
if ( lists[i].items.Size() <= 0 ) continue;
|
|
let osel = lists[i].items[lists[i].selected];
|
|
qsort_lore(lists[i].items,0,lists[i].items.Size()-1);
|
|
let idx = lists[i].items.Find(osel);
|
|
if ( (idx != lists[i].items.Size()) && (idx != lists[i].selected) )
|
|
{
|
|
sel[i] = lists[i].selected = idx;
|
|
KBScroll();
|
|
}
|
|
maxofs[i] = max(0,lists[i].items.Size()*14-int(master.ws.y-50));
|
|
if ( ofs[i] > maxofs[i] ) smofs[i] = ofs[i] = maxofs[i];
|
|
for ( int j=0; j<lists[i].items.Size(); j++ )
|
|
lists[i].items[j].ypos = j*14;
|
|
}
|
|
loresz = lore.ent.Size();
|
|
// append the notification for new lore
|
|
master.tmsg = StringTable.Localize("$SWWM_NEWLORE");
|
|
master.tmsgtic = gametic+70;
|
|
if ( ((lwidth != olwidth) || (maxofs[section] != olmaxofs)) && clore && ltext )
|
|
ltext.Reinit(StringTable.Localize(clore.text),lwidth+((maxofs[section]>0)?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<lists[section].items.Size(); i++ )
|
|
{
|
|
if ( !lists[section].items[i].CheckBounds(pos.x-3,(pos.y-32)+smofs[section]) )
|
|
continue;
|
|
sel[section] = lists[section].selected = i;
|
|
KBScroll();
|
|
if ( active && (lists[section].items[i] == active) )
|
|
MenuInput(MK_BACK);
|
|
else
|
|
{
|
|
if ( active ) active.bActive = false;
|
|
clore = null;
|
|
MenuInput(MK_ENTER);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
case MB_WHEELUP:
|
|
if ( sel[section] > 0 )
|
|
{
|
|
lists[section].selected = --sel[section];
|
|
master.MenuSound("menu/demoscroll");
|
|
KBScroll();
|
|
}
|
|
break;
|
|
case MB_WHEELDOWN:
|
|
if ( sel[section] < lists[section].items.Size()-1 )
|
|
{
|
|
lists[section].selected = ++sel[section];
|
|
master.MenuSound("menu/demoscroll");
|
|
KBScroll();
|
|
}
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
if ( !clore || !ltext ) return;
|
|
switch ( btn )
|
|
{
|
|
case MB_LEFT:
|
|
// see if we're clicking the scrollbar (if it exists)
|
|
if ( ltext.scrollbar && (pos.x > (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 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<section; i++ )
|
|
{
|
|
if ( lists[i].items.Size() <= 0 )
|
|
continue;
|
|
hasprev = true;
|
|
for ( int j=0; j<lists[i].items.Size(); j++ )
|
|
{
|
|
if ( DemolitionistMenuLoreItem(lists[i].items[j]).ent.read ) continue;
|
|
hasunreadprev = true;
|
|
break;
|
|
}
|
|
}
|
|
bool hasnext = false;
|
|
bool hasunreadnext = false;
|
|
for ( int i=section+1; i<4; i++ )
|
|
{
|
|
if ( lists[i].items.Size() <= 0 )
|
|
continue;
|
|
hasnext = true;
|
|
for ( int j=0; j<lists[i].items.Size(); j++ )
|
|
{
|
|
if ( DemolitionistMenuLoreItem(lists[i].items[j]).ent.read ) continue;
|
|
hasunreadnext = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( hasprev )
|
|
Screen.DrawText(master.mSmallFont,Font.CR_WHITE,master.origin.x+3,master.origin.y+yy,"<",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true);
|
|
if ( hasunreadprev )
|
|
Screen.DrawText(master.mSmallFont,Font.CR_GOLD,master.origin.x+9,master.origin.y+yy,"‼",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true);
|
|
if ( hasnext )
|
|
Screen.DrawText(master.mSmallFont,Font.CR_WHITE,master.origin.x+lwidth-9,master.origin.y+yy,">",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;
|
|
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-smofs[section]));
|
|
Screen.ClearClipRect();
|
|
if ( maxofs[section] > 0 )
|
|
{
|
|
xx = lwidth;
|
|
master.DrawVSeparator(xx+8,14,master.ws.y-28);
|
|
xx += 2;
|
|
yy = floor(smofs[section]*((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();
|
|
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));
|
|
}
|
|
}
|