// stats stab Class DemolitionistStatsTab : DemolitionistMenuTab { DemolitionistMenuList lists[4]; int section; String sname[4]; int lwidth; SWWMStats stats; int ofs[4], maxofs[4]; double smofs[4]; bool drag; override DemolitionistMenuTab Init( DemolitionistMenu master ) { title = StringTable.Localize("$SWWM_STATTAB"); section = 0; lwidth = 0; for ( int i=0; i<4; i++ ) { sname[i] = StringTable.Localize("$SWWM_STATTAB"..i); lwidth = max(lwidth,master.mSmallFont.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 stats = Demolitionist(players[consoleplayer].mo)?Demolitionist(players[consoleplayer].mo).mystats:null; if ( !stats ) return Super.Init(master); // the general stats are always the same in the same order, so init them all here in one go lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATUPTIME",stats,stat_uptime)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATONFOOT",stats,stat_onfoot)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATFLIGHT",stats,stat_flight)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATSWIM",stats,stat_swim)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATTELE",stats,stat_tele)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATBOOST",stats,stat_boost)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATDASH",stats,stat_dash)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATSTOMP",stats,stat_stomp)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATFUEL",stats,stat_fuel)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATSPEED",stats,stat_speed)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATAIRTIME",stats,stat_airtime)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATPARRY",stats,stat_parry)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATPPARRY",stats,stat_pparry)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATWPONCH",stats,stat_wponch)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATBUSTS",stats,stat_busts)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATBUTTS",stats,stat_butts)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATPATS",stats,stat_pats)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATKISS",stats,stat_kiss)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATFRIENDS",stats,stat_friends)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATITEMS",stats,stat_items)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATSECRETS",stats,stat_secrets)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATKILLS",stats,stat_kills)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATDEATHS",stats,stat_deaths)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATDDEALT",stats,stat_ddealt)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATDTAKEN",stats,stat_dtaken)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATTDEALT",stats,stat_tdealt)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATTTAKEN",stats,stat_ttaken)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATMKILL",stats,stat_mkill)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATSKILL",stats,stat_skill)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATFAVWEAP",stats,stat_favweap)); lists[0].items.Push(new('DemolitionistMenuStatItem').Init(master,"$SWWM_STATHISCORE",stats,stat_hiscore)); for ( int i=0; i tagb; } return a.kills < b.kills; } private int partition_mstats( Array a, int l, int h ) { DemolitionistMenuListItem pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpMonsterKill(DemolitionistMenuKillItem(pv).s,DemolitionistMenuKillItem(a[j]).s) ) { 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_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 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 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 ) { 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 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); } // BEGIN stat functions static ui string stat_uptime( SWWMStats s ) { int thour = ((level.totaltime-s.lastspawn)/(3600*GameTicRate)); int tmin = ((level.totaltime-s.lastspawn)/(60*GameTicRate))%60; int tsec = ((level.totaltime-s.lastspawn)/GameTicRate)%60; return String.Format("%02d\cu:\c-%02d\cu:\c-%02d",thour,tmin,tsec); } static ui string stat_onfoot( SWWMStats s ) { if ( s.grounddist > 32000. ) return String.Format("%g\cu%s\c-",s.grounddist/32000.,StringTable.Localize("$SWWM_UNIT_KILOMETER")); return String.Format("%g\cu%s\c-",s.grounddist/32.,StringTable.Localize("$SWWM_UNIT_METER")); } static ui string stat_flight( SWWMStats s ) { if ( s.airdist > 32000. ) return String.Format("%g\cu%s\c-",s.airdist/32000.,StringTable.Localize("$SWWM_UNIT_KILOMETER")); return String.Format("%g\cu%s\c-",s.airdist/32.,StringTable.Localize("$SWWM_UNIT_METER")); } static ui string stat_swim( SWWMStats s ) { if ( s.swimdist > 32000. ) return String.Format("%g\cu%s\c-",s.swimdist/32000.,StringTable.Localize("$SWWM_UNIT_KILOMETER")); return String.Format("%g\cu%s\c-",s.swimdist/32.,StringTable.Localize("$SWWM_UNIT_METER")); } static ui string stat_tele( SWWMStats s ) { if ( s.teledist > 32000. ) return String.Format("%g\cu%s\c-",s.teledist/32000.,StringTable.Localize("$SWWM_UNIT_KILOMETER")); return String.Format("%g\cu%s\c-",s.teledist/32.,StringTable.Localize("$SWWM_UNIT_METER")); } static ui string stat_boost( SWWMStats s ) { return String.Format("%d",s.boostcount); } static ui string stat_dash( SWWMStats s ) { return String.Format("%d",s.dashcount); } static ui string stat_stomp( SWWMStats s ) { return String.Format("%d",s.stompcount); } static ui string stat_fuel( SWWMStats s ) { return String.Format("%g\cu%s\c-",s.fuelusage,StringTable.Localize("$SWWM_UNIT_LITER")); } static ui string stat_speed( SWWMStats s ) { return String.Format("%g\cu%s\c-",(s.topspeed*3600.*GameTicRate)/32000.,StringTable.Localize("$SWWM_UNIT_KPH")); } static ui string stat_airtime( SWWMStats s ) { int thour = (s.airtime/(3600*GameTicRate)); int tmin = (s.airtime/(60*GameTicRate))%60; int tsec = (s.airtime/GameTicRate)%60; return String.Format("%02d\cu:\c-%02d\cu:\c-%02d",thour,tmin,tsec); } static ui string stat_parry( SWWMStats s ) { return String.Format("%d",s.parries); } static ui string stat_pparry( SWWMStats s ) { return String.Format("%d",s.pparries); } static ui string stat_wponch( SWWMStats s ) { return String.Format("%d",s.wponch); } static ui string stat_busts( SWWMStats s ) { return String.Format("%d",s.busts); } static ui string stat_butts( SWWMStats s ) { return String.Format("%d",s.buttslams); } static ui string stat_pats( SWWMStats s ) { return String.Format("%d",s.pats); } static ui string stat_kiss( SWWMStats s ) { return String.Format("%d",s.smooch); } static ui string stat_friends( SWWMStats s ) { return String.Format("%d",s.befriend); } static ui string stat_items( SWWMStats s ) { return String.Format("%d",s.items); } static ui string stat_secrets( SWWMStats s ) { return String.Format("%d",s.secrets); } static ui string stat_kills( SWWMStats s ) { return String.Format("%d",s.kills); } static ui string stat_deaths( SWWMStats s ) { return String.Format("%d",s.deaths); } static ui string stat_ddealt( SWWMStats s ) { if ( s.hdamagedealt > 0 ) return String.Format("%d%09d",s.hdamagedealt,s.damagedealt); return String.Format("%d",s.damagedealt); } static ui string stat_dtaken( SWWMStats s ) { if ( s.hdamagetaken > 0 ) return String.Format("%d%09d",s.hdamagetaken,s.damagetaken); return String.Format("%d",s.damagetaken); } static ui string stat_tdealt( SWWMStats s ) { return String.Format("%d",s.topdealt); } static ui string stat_ttaken( SWWMStats s ) { return String.Format("%d",s.toptaken); } static ui string stat_mkill( SWWMStats s ) { return String.Format("%d",s.mkill); } static ui string stat_skill( SWWMStats s ) { return String.Format("%d",s.skill); } static ui string stat_favweap( SWWMStats s ) { if ( s.favweapon == -1 ) return "N/A"; if ( s.wstats[s.favweapon].w == 'SWWMWeapon' ) return StringTable.Localize("$SWWM_YOURSELF"); if ( s.wstats[s.favweapon].w == 'SWWMGesture' ) return StringTable.Localize("$SWWM_DOKIDOKI"); if ( s.wstats[s.favweapon].w == 'SWWMItemGesture' ) return StringTable.Localize("$T_FROGGY"); if ( s.wstats[s.favweapon].w == 'Weapon' ) return StringTable.Localize("$SWWM_GRAVKILL"); if ( s.wstats[s.favweapon].w == 'DoomWeapon' ) return StringTable.Localize("$SWWM_PARRYKILL"); let def = GetDefaultByType(s.wstats[s.favweapon].w); return def.GetTag(); } static ui string stat_hiscore( SWWMStats s ) { return String.Format("\cu¥\c-%09d",s.hiscore); } // END stat functions override void Ticker() { if ( !stats ) return; // update list contents String str; int w; switch ( section ) { case 0: // not much to do here, they update themselves and are left-aligned maxofs[0] = int(16*lists[0].items.Size()-(master.ws.y-46)); break; case 1: // theoretically we can assume that the monster stats list will never shrink in the middle of gameplay, only grow // also the array only ever has elements pushed, never inserted in-between // if this somehow breaks, I don't know what the hell can do that if ( lists[1].items.Size() < stats.mstats.Size() ) { int oldindex = lists[1].items.Size(); lists[1].items.Resize(stats.mstats.Size()); for ( int i=oldindex; i 0 ) w -= 8; foreach ( i:lists[1].items ) { let k = DemolitionistMenuKillItem(i); k.width = w; } // sort qsort_mstats(lists[1].items,0,lists[1].items.Size()-1); // update y positions after sorting for ( int i=0; i 0 ) w -= 8; int len[4], maxlen[4]; for ( int i=0; i<4; i++ ) maxlen[i] = 0; for ( int i=0; i maxlen[j] ) maxlen[j] = len[j]; } // second pass to propagate the "max lengths" foreach ( i:lists[2].items ) { let l = DemolitionistMenuMapStatItem(i); for ( int j=0; j<4; j++ ) l.maxlen[j] = maxlen[j]; } break; case 3: // update achievement progress foreach ( i:lists[3].items ) DemolitionistMenuAchievementItem(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); foreach ( i:lists[3].items ) { let ai = DemolitionistMenuAchievementItem(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; foreach ( i:lists[3].items ) { let ai = DemolitionistMenuAchievementItem(i); if ( ai.width != w ) ai.oldstr = ""; // force brokenlines to update to the new width ai.width = w; } } // 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(); } // 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); } override void MenuInput( int key ) { switch ( key ) { case MK_LEFT: master.MenuSound("menu/demoscroll"); smofs[section] = ofs[section]; section--; if ( section < 0 ) section = 3; smofs[section] = ofs[section]; break; case MK_RIGHT: master.MenuSound("menu/demoscroll"); smofs[section] = ofs[section]; section++; if ( section > 3 ) section = 0; smofs[section] = ofs[section]; break; case MK_DOWN: if ( Scroll(16) ) master.MenuSound("menu/demoscroll"); break; case MK_UP: if ( Scroll(-16) ) master.MenuSound("menu/demoscroll"); break; } } override void MouseInput( Vector2 pos, int btn ) { // global events switch ( btn ) { case MB_DRAG: if ( drag ) SetOffset(pos.y); break; case MB_RELEASE: drag = false; break; } // mouse on left side if ( pos.x < lwidth ) { if ( btn != MB_LEFT ) return; for ( int i=0; i<4; i++ ) { if ( pos.x < 9 ) continue; if ( pos.x > 9+master.mSmallFont.StringWidth(sname[i]) ) continue; if ( pos.y < 23+16*i ) continue; if ( pos.y > 36+16*i ) continue; if ( section != i ) { master.MenuSound("menu/demoscroll"); smofs[section] = ofs[section]; section = i; smofs[section] = ofs[section]; } break; } return; } switch ( btn ) { case MB_LEFT: // see if we're clicking the scrollbar (if it exists) if ( (maxofs[section] > 0) && (pos.x > (master.ws.x-8)) ) { SetOffset(pos.y); master.MenuSound("menu/demoscroll"); drag = true; break; } break; case MB_WHEELUP: if ( Scroll(-8) ) master.MenuSound("menu/demoscroll"); break; case MB_WHEELDOWN: if ( Scroll(8) ) master.MenuSound("menu/demoscroll"); break; } } override void Drawer( double fractic ) { if ( !stats ) return; double xx = 9; double yy = 23; for ( int i=0; i<4; i++ ) { Screen.DrawText(master.mSmallFont,(i==section)?Font.CR_FIRE:Font.CR_WHITE,master.origin.x+xx,master.origin.y+yy,sname[i],DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); yy += 16; } master.DrawVSeparator(lwidth,14,master.ws.y-28); if ( lists[section].items.Size() == 0 ) { String str = StringTable.Localize("$SWWM_NOSTAT"); xx = lwidth+int((master.ws.x-lwidth)-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); return; } double ssmofs = (smofs[section]~==ofs[section])?smofs[section]:(smofs[section]*(1.-fractic)+((smofs[section]*.6)+(ofs[section]*.4))*fractic); if ( section == 3 ) { // achievement drawer has different margins xx = lwidth+12; yy = 26; Screen.SetClipRect(int((master.origin.x+lwidth+12)*master.hs),int((master.origin.y+26)*master.hs),int((master.ws.x-(lwidth+24))*master.hs),int((master.ws.y-52)*master.hs)); lists[section].Drawer((xx,yy-ssmofs)); } else { xx = lwidth+9; yy = 23; Screen.SetClipRect(int((master.origin.x+lwidth+9)*master.hs),int((master.origin.y+23)*master.hs),int((master.ws.x-(lwidth+18))*master.hs),int((master.ws.y-46)*master.hs)); lists[section].Drawer((xx,yy-ssmofs)); } Screen.ClearClipRect(); if ( maxofs[section] > 0 ) { xx = master.ws.x-8; master.DrawVSeparator(xx,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); } } } // general stat item Class DemolitionistMenuStatItem : DemolitionistMenuListItem { SWWMStats s; Function fn; String txt; DemolitionistMenuStatItem Init( DemolitionistMenu master, String label, SWWMStats s, Function fn ) { Super.Init(master,label); self.s = s; self.fn = fn; return self; } override int GetWidth() { String str = String.Format("%s%s",label,txt); return master.mSmallFont.StringWidth(str); } override void Ticker() { // call update fn txt = fn.call(s); } override void Drawer( Vector2 pos, bool selected ) { String str = String.Format("\cx%s\c-%s",label,txt); Screen.DrawText(master.mSmallFont,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); } } // monster kill stat item Class DemolitionistMenuKillItem : DemolitionistMenuListItem { MonsterKill s; int width; DemolitionistMenuKillItem Init( DemolitionistMenu master, MonsterKill s, int width = 0 ) { Super.Init(master,""); self.s = s; self.width = width; let m = GetDefaultByType(s.m); self.label = m.GetTag(FallbackTag); if ( self.label == FallbackTag ) { self.label = m.GetClassName(); SWWMUtility.BeautifyClassName(self.label); } return self; } override int GetWidth() { return width; } override void Drawer( Vector2 pos, bool selected ) { Screen.DrawText(master.mSmallFont,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); String str = String.Format("%d",s.kills); Screen.DrawText(master.mSmallFont,Font.CR_WHITE,master.origin.x+pos.x+width-master.mSmallFont.StringWidth(str),master.origin.y+pos.y,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } } // map stat item Class DemolitionistMenuMapStatItem : DemolitionistMenuListItem { LevelStat s; int width; int maxlen[4]; DemolitionistMenuMapStatItem Init( DemolitionistMenu master, LevelStat s, int width = 0 ) { Super.Init(master,""); self.s = s; self.width = width; self.label = s.levelname; return self; } override int GetWidth() { return width; } void GetLengths( bool selected, int &tlen, int &slen, int &ilen, int &klen ) { int time, par, suck, stotal, scount, itotal, icount, ktotal, kcount; if ( selected ) { time = level.maptime; par = level.partime; suck = level.sucktime; stotal = level.total_secrets; scount = level.found_secrets; itotal = level.total_items; icount = level.found_items; ktotal = level.total_monsters; kcount = level.killed_monsters; } else { time = s.time; par = s.par; suck = s.suck; stotal = s.stotal; scount = s.scount; itotal = s.itotal; icount = s.icount; ktotal = s.ktotal; kcount = s.kcount; } int sec = Thinker.Tics2Seconds(time); String str = String.Format("T %02d:%02d:%02d",sec/3600,(sec%3600)/60,sec%60); tlen = master.mTinyFont.StringWidth(str); if ( stotal > 0 ) { str = String.Format("S %d/%d",scount,stotal); slen = master.mTinyFont.StringWidth(str); } else slen = 0; if ( itotal > 0 ) { str = String.Format("I %d/%d",icount,itotal); ilen = master.mTinyFont.StringWidth(str); } else ilen = 0; if ( ktotal > 0 ) { str = String.Format("K %d/%d",kcount,ktotal); klen = master.mTinyFont.StringWidth(str); } else klen = 0; } override void Drawer( Vector2 pos, bool selected ) { String str = s.mapname..": "; if ( selected ) str = "\cd> \c-"..str; int nlen = master.mTinyFont.StringWidth(str); double xx = pos.x; double yy = pos.y; Screen.DrawText(master.mTinyFont,(selected||s.visited)?Font.CR_FIRE:Font.CR_DARKGRAY,master.origin.x+xx,master.origin.y+yy+2,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); xx += nlen; str = s.levelname; if ( !s.visited && !selected ) SWWMUtility.ObscureText(str,(Menu.MenuTime()/3)+ypos*2); bool smallname = (nlen+master.mSmallFont.StringWidth(str))>(width-(maxlen[3]+maxlen[2]+maxlen[1]+maxlen[0]+24)); bool allclear = true; if ( selected ) { if ( (level.total_secrets > 0) && (level.found_secrets < level.total_secrets) ) allclear = false; if ( (level.total_items > 0) && (level.found_items < level.total_items) ) allclear = false; if ( (level.total_monsters > 0) && (level.killed_monsters < level.total_monsters) ) allclear = false; } else if ( s.visited ) { if ( (s.stotal > 0) && (s.scount < s.stotal) ) allclear = false; if ( (s.itotal > 0) && (s.icount < s.itotal) ) allclear = false; if ( (s.ktotal > 0) && (s.kcount < s.ktotal) ) allclear = false; } else allclear = false; Screen.DrawText(smallname?master.mTinyFont:master.mSmallFont,(selected||s.visited)?allclear?Font.CR_GOLD:Font.CR_WHITE:Font.CR_BLACK,master.origin.x+xx,master.origin.y+yy+smallname*2,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); if ( !selected && !s.visited ) return; xx = pos.x+width; yy = pos.y+2; int time, par, suck, stotal, scount, itotal, icount, ktotal, kcount; if ( selected ) { time = level.maptime; par = level.partime; suck = level.sucktime; stotal = level.total_secrets; scount = level.found_secrets; itotal = level.total_items; icount = level.found_items; ktotal = level.total_monsters; kcount = level.killed_monsters; } else { time = s.time; par = s.par; suck = s.suck; stotal = s.stotal; scount = s.scount; itotal = s.itotal; icount = s.icount; ktotal = s.ktotal; kcount = s.kcount; } int sec = Thinker.Tics2Seconds(time); str = String.Format("%02d\cu:\c-%02d\cu:\c-%02d",sec/3600,(sec%3600)/60,sec%60); Screen.DrawText(master.mTinyFont,((suck>0)&&(sec>=(suck*3600)))?Font.CR_RED:(sec<=par)?Font.CR_GOLD:Font.CR_WHITE,master.origin.x+xx-master.mTinyFont.StringWidth(str),master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); Screen.DrawText(master.mTinyFont,Font.CR_FIRE,master.origin.x+xx-maxlen[0],master.origin.y+yy,"T",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); if ( maxlen[0] > 0 ) xx -= maxlen[0]+8; if ( stotal > 0 ) { str = String.Format("%d\cu/\c-%d",scount,stotal); Screen.DrawText(master.mTinyFont,(scount>=stotal)?Font.CR_GOLD:Font.CR_WHITE,master.origin.x+xx-master.mTinyFont.StringWidth(str),master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); Screen.DrawText(master.mTinyFont,Font.CR_FIRE,master.origin.x+xx-maxlen[1],master.origin.y+yy,"S",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } if ( maxlen[1] > 0 ) xx -= maxlen[1]+8; if ( itotal > 0 ) { str = String.Format("%d\cu/\c-%d",icount,itotal); Screen.DrawText(master.mTinyFont,(icount>=itotal)?Font.CR_GOLD:Font.CR_WHITE,master.origin.x+xx-master.mTinyFont.StringWidth(str),master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); Screen.DrawText(master.mTinyFont,Font.CR_FIRE,master.origin.x+xx-maxlen[2],master.origin.y+yy,"I",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } if ( maxlen[2] > 0 ) xx -= maxlen[2]+8; if ( ktotal > 0 ) { str = String.Format("%d\cu/\c-%d",kcount,ktotal); Screen.DrawText(master.mTinyFont,(kcount>=ktotal)?Font.CR_GOLD:Font.CR_WHITE,master.origin.x+xx-master.mTinyFont.StringWidth(str),master.origin.y+yy,str,DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); Screen.DrawText(master.mTinyFont,Font.CR_FIRE,master.origin.x+xx-maxlen[3],master.origin.y+yy,"K",DTA_VirtualWidthF,master.ss.x,DTA_VirtualHeightF,master.ss.y,DTA_KeepRatio,true); } } } // achievement item Class DemolitionistMenuAchievementItem : DemolitionistMenuListItem { SWWMAchievementInfo a; TextureID AchievementUnknown, BaseBox, BarTex[3]; bool ShouldObscure; bool bHidden; int width; BrokenLines l; String oldstr; DemolitionistMenuAchievementItem Init( DemolitionistMenu master, SWWMAchievementInfo a, int width = 0 ) { Super.Init(master,""); self.a = a; self.width = width; AchievementUnknown = TexMan.CheckForTexture("graphics/Achievements/HiddenAchievement.png"); BaseBox = TexMan.CheckForTexture("graphics/Achievements/NoAchievement.png"); BarTex[0] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementBase.png"); BarTex[1] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementProgress.png"); BarTex[2] = TexMan.CheckForTexture("graphics/Achievements/BarAchievementDone.png"); 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