Achievement Stats tab now fully functional.

Optimize menu item list drawing by skipping off-screen items.
This commit is contained in:
Mari the Deer 2022-02-10 21:23:31 +01:00
commit cce76b52ec
7 changed files with 213 additions and 14 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 B

View file

@ -1,3 +1,3 @@
[default]
SWWM_MODVER="\cyDEMOLITIONIST \cw1.2pre r141 \cu(Thu 10 Feb 13:38:58 CET 2022)\c-";
SWWM_SHORTVER="\cw1.2pre r141 \cu(2022-02-10 13:38:58)\c-";
SWWM_MODVER="\cyDEMOLITIONIST \cw1.2pre r142 \cu(Thu 10 Feb 21:23:31 CET 2022)\c-";
SWWM_SHORTVER="\cw1.2pre r142 \cu(2022-02-10 21:23:31)\c-";

View file

@ -5,6 +5,7 @@ Class DemolitionistMenuList ui
{
Array<DemolitionistMenuListItem> items;
int selected;
DemolitionistMenu master;
override void OnDestroy()
{
@ -49,8 +50,19 @@ Class DemolitionistMenuList ui
// draw all items with a specific offset and clip region
void Drawer( Vector2 offset, int cliptop, int clipbottom, int clipleft, int clipright )
{
Vector2 pos, rpos, rsiz;
for ( int i=0; i<items.Size(); i++ )
items[i].Drawer((items[i].xpos,items[i].ypos)+offset,(i==selected),cliptop,clipbottom,clipleft,clipright);
{
pos = (items[i].xpos,items[i].ypos)+offset;
rpos = (master.origin+pos)*master.hs;
rsiz = (items[i].GetWidth(),items[i].GetHeight())*master.hs;
// skip draw if out of bounds
if ( rpos.y+rsiz.y < cliptop ) continue;
if ( rpos.y > clipbottom ) continue;
if ( rpos.x+rsiz.x < clipleft ) continue;
if ( rpos.x > clipright ) continue;
items[i].Drawer(pos,(i==selected),cliptop,clipbottom,clipleft,clipright);
}
// scrollbar drawing is handled by the tab, as the list itself is entirely unaware of its frame dimensions
}
}
@ -123,8 +135,16 @@ Class DemolitionistMenuKillItem : DemolitionistMenuListItem
return self;
}
override int GetWidth()
{
return width;
}
override void Drawer( Vector2 pos, bool selected, int cliptop, int clipbottom, int clipleft, int clipright )
{
// skip draw when out of bounds
if ( (master.origin.y+pos.y > clipbottom) || (master.origin.y+pos.y+smallfont.GetHeight() < cliptop) )
return;
Screen.DrawText(smallfont,Font.CR_FIRE,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_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
String str = String.Format("%d",s.kills);
Screen.DrawText(smallfont,Font.CR_WHITE,master.origin.x+pos.x+width-smallfont.StringWidth(str),master.origin.y+pos.y,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
@ -147,6 +167,11 @@ Class DemolitionistMenuMapStatItem : DemolitionistMenuListItem
return self;
}
override int GetWidth()
{
return width;
}
override void Drawer( Vector2 pos, bool selected, int cliptop, int clipbottom, int clipleft, int clipright )
{
String str = label;
@ -187,6 +212,9 @@ Class DemolitionistMenuMapStatItem : DemolitionistMenuListItem
Class DemolitionistMenuAchievementItem : DemolitionistMenuListItem
{
SWWMAchievementInfo a;
TextureID AchievementUnknown, BaseBox, BarTex[3];
bool ShouldObscure;
bool bHidden;
int width;
DemolitionistMenuAchievementItem Init( DemolitionistMenu master, SWWMAchievementInfo a, int width = 0 )
@ -194,8 +222,72 @@ Class DemolitionistMenuAchievementItem : DemolitionistMenuListItem
Super.Init(master,"");
self.a = a;
self.width = width;
AchievementUnknown = TexMan.CheckForTexture("graphics/Achievements/HiddenAchievement.png",TexMan.Type_Any);
BaseBox = TexMan.CheckForTexture("graphics/Achievements/NoAchievement.png",TexMan.Type_Any);
BarTex[0] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementBase.png",TexMan.Type_Any);
BarTex[1] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementProgress.png",TexMan.Type_Any);
BarTex[2] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementDone.png",TexMan.Type_Any);
ShouldObscure = (swwm_filterachievements==1);
Update();
return self;
}
// cache state/progress to not call it every frame
void Update()
{
a.state = master.shnd.achievementstate.At(a.basename).ToInt();
if ( !a.maxval ) return;
a.val = master.shnd.achievementprogress.At(a.basename).ToInt();
if ( !a.bitfield ) return;
int val = 0;
for ( int i=0; i<a.maxval; i++ )
val += !!(a.val&(1<<i));
a.val = val;
}
override int GetWidth()
{
return width;
}
override int GetHeight()
{
return 50; // hardcoded
}
override void Drawer( Vector2 pos, bool selected, int cliptop, int clipbottom, int clipleft, int clipright )
{
if ( bHidden ) return;
bool completed = !!a.state;
bool hasprogress = (a.maxval && a.val);
double xx = master.origin.x+pos.x;
double yy = master.origin.y+pos.y;
Screen.DrawTexture(BaseBox,false,xx+1,yy+1,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright,DTA_FillColor,(!completed&&!hasprogress&&ShouldObscure)?Color(8,8,8):Color(16,16,16));
Screen.DrawTexture((!completed&&!hasprogress&&ShouldObscure)?AchievementUnknown:a.icon,false,xx,yy,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright,DTA_Desaturate,(!completed)*255,DTA_ColorOverlay,completed?Color(0,0,0,0):(hasprogress||!ShouldObscure)?Color(96,0,0,0):Color(192,0,0,0));
Screen.DrawTexture(BarTex[0],false,xx+1,yy+36,DTA_DestWidthF,width,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright,DTA_FillColor,Color(0,0,0));
String str;
if ( a.maxval && (!ShouldObscure || hasprogress) )
{
int val = clamp(a.val,0,a.maxval);
if ( val < a.maxval ) Screen.DrawTexture(BarTex[0],false,xx,yy+35,DTA_DestWidthF,width,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
Screen.DrawTexture(BarTex[completed?2:1],false,xx,yy+35,DTA_DestWidthF,width*(val/double(a.maxval)),DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
if ( completed ) str = String.Format("%s / %s",SWWMUtility.ThousandsNum(a.maxval),SWWMUtility.ThousandsNum(a.maxval));
else str = String.Format("%s / %s",SWWMUtility.ThousandsNum(val),SWWMUtility.ThousandsNum(a.maxval));
int ox = (width-smallfont2.StringWidth(str))/2;
Screen.DrawText(smallfont2,completed?Font.CR_GREEN:Font.CR_WHITE,xx+ox,yy+37,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
}
else Screen.DrawTexture(BarTex[completed?2:0],false,xx,yy+35,DTA_DestWidthF,width,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
str = a.hasformat?String.Format(StringTable.Localize("$SWWM_ACHIEVEMENT_"..a.basename.."_TXT"),a.maxval):StringTable.Localize("$SWWM_ACHIEVEMENT_"..a.basename.."_TXT");
if ( !completed && !hasprogress && ShouldObscure ) SWWMUtility.ObscureText(str,(gametic/3)+ypos*2);
BrokenLines l = smallfont2.BreakLines(str,width-40);
str = StringTable.Localize("$SWWM_ACHIEVEMENT_"..a.basename.."_TAG");
if ( !completed && !hasprogress && ShouldObscure ) SWWMUtility.ObscureText(str,(gametic/3)+ypos*2+1);
int oy = (32-(14+(9*l.Count())))/2;
Screen.DrawText(smallfont,completed?Font.CR_GREEN:Font.CR_DARKGRAY,xx+36,yy+oy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
for ( int i=0; i<l.Count(); i++ )
Screen.DrawText(smallfont2,completed?Font.CR_WHITE:Font.CR_BLACK,xx+40,yy+oy+14+i*9,l.StringAt(i),DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true,DTA_ClipTop,cliptop,DTA_ClipBottom,clipbottom,DTA_ClipLeft,clipleft,DTA_ClipRight,clipright);
l.Destroy();
}
}
// inventory item

View file

@ -22,6 +22,7 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
sname[i] = StringTable.Localize("$SWWM_STATTAB"..i);
lwidth = max(lwidth,smallfont.StringWidth(sname[i]));
lists[i] = new("DemolitionistMenuList");
lists[i].master = master;
lists[i].selected = -1;
}
lwidth += 16; // account for padding of 8px on each side
@ -61,6 +62,8 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
lists[2].selected = lists[2].items.Size()-1; // so it shows the "current level" indicator
for ( int i=0; i<lists[2].items.Size(); i++ )
lists[2].items[i].ypos = i*16;
for ( int i=0; i<master.shnd.achievementinfo.Size(); i++ )
lists[3].items.Push(new("DemolitionistMenuAchievementItem").Init(master,master.shnd.achievementinfo[i]));
return Super.Init(master);
}
override void OnDestroy()
@ -130,6 +133,75 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
qsort_mstats(a,p+1,h);
}
private bool CmpAchievement( SWWMAchievementInfo a, SWWMAchievementInfo b )
{
bool adone = !!(a.state), bdone = !!(b.state);
double afactor = adone?1.:0., bfactor = bdone?1.:0.;
if ( a.maxval )
{
int cur = 0;
if ( a.bitfield )
{
for ( int i=0; i<a.maxval; i++ )
cur += !!(a.val&(1<<i));
cur = min(cur,a.maxval);
}
else cur = clamp(a.val,0,a.maxval);
afactor = cur/double(a.maxval);
}
if ( b.maxval )
{
int cur = 0;
if ( b.bitfield )
{
for ( int i=0; i<b.maxval; i++ )
cur += !!(b.val&(1<<i));
cur = min(cur,b.maxval);
}
else cur = clamp(b.val,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<DemolitionistMenuListItem> a, int l, int h )
{
DemolitionistMenuListItem pv = a[h];
int i = (l-1);
for ( int j=l; j<=(h-1); j++ )
{
if ( CmpAchievement(DemolitionistMenuAchievementItem(pv).a,DemolitionistMenuAchievementItem(a[j]).a) )
{
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_achievements( Array<DemolitionistMenuListItem> 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()
{
if ( !stats ) return;
@ -314,6 +386,36 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
l.maxlen[j] = maxlen[j];
}
break;
case 3:
// update achievement progress
for ( int i=0; i<lists[3].items.Size(); i++ )
DemolitionistMenuAchievementItem(lists[3].items[i]).Update();
// sort achievements
qsort_achievements(lists[3].items,0,lists[3].items.Size()-1);
// set offsets (based on which ones should be hidden, too)
int ay = 0;
bool dohide = (swwm_filterachievements==2);
for ( int i=0; i<lists[3].items.Size(); i++ )
{
let ai = DemolitionistMenuAchievementItem(lists[3].items[i]);
let key = ai.a.basename;
ai.bHidden = true;
if ( (key == "everything") && !ai.a.state ) continue;
if ( dohide && !ai.a.state && (!ai.a.maxval || !ai.a.val) ) continue;
ai.bHidden = false;
ai.ypos = ay;
ay += 50;
}
// get max scroll
maxofs[3] = int(ay-(master.ws.y-52));
// update widths
w = int(master.ws.x-(24+lwidth));
if ( maxofs[3] > 0 ) w -= 8;
for ( int i=0; i<lists[3].items.Size(); i++ )
{
let ai = DemolitionistMenuAchievementItem(lists[3].items[i]);
ai.width = w;
}
}
// update smooth scroll
smofs[section] = (smofs[section]*.6)+(ofs[section]*.4);
@ -440,9 +542,16 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
yy += 16;
}
master.DrawVSeparator(lwidth,14,master.ws.y-28);
// achievement drawer has different margins
if ( section == 3 )
{
// TODO achievement drawer has different margins
xx = lwidth+12;
yy = 26;
int cliptop = int((master.origin.y+26)*master.hs);
int clipbottom = int((master.origin.y+master.ws.y-26)*master.hs);
int clipleft = int((master.origin.x+lwidth+12)*master.hs);
int clipright = int((master.origin.x+master.ws.x-12)*master.hs);
lists[section].Drawer((xx,yy-smofs[section]),cliptop,clipbottom,clipleft,clipright);
}
else
{
@ -453,14 +562,14 @@ Class DemolitionistStatsTab : DemolitionistMenuTab
int clipleft = int((master.origin.x+lwidth+9)*master.hs);
int clipright = int((master.origin.x+master.ws.x-9)*master.hs);
lists[section].Drawer((xx,yy-smofs[section]),cliptop,clipbottom,clipleft,clipright);
if ( maxofs[section] > 0 )
{
xx = master.ws.x-8;
master.DrawVSeparator(xx,14,master.ws.y-28);
xx += 2;
yy = floor(smofs[section]*((master.ws.y-39)/maxofs[section]))+14;
Screen.DrawText(smallfont,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 ( maxofs[section] > 0 )
{
xx = master.ws.x-8;
master.DrawVSeparator(xx,14,master.ws.y-28);
xx += 2;
yy = floor(smofs[section]*((master.ws.y-39)/maxofs[section]))+14;
Screen.DrawText(smallfont,Font.CR_FIRE,master.origin.x+xx,master.origin.y+yy,"▮",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true);
}
}
}

View file

@ -30,9 +30,7 @@ Class SWWMAchievementMenu : GenericMenu
// cache state/progress
mItems[i].state = hnd.achievementstate.At(key).ToInt();
if ( mItems[i].maxval )
{
mItems[i].val = hnd.achievementprogress.At(key).ToInt();
}
// check if we need to hide it
if ( (key == "everything") && !mItems[i].state )
{