swwmgz_m/zscript/handler/swwm_statichandler_achievements.zsc

365 lines
10 KiB
Text

// achievement tracking
Class SWWMAchievementInfo
{
int baseindex; // sorting order in the list
String basename; // base name for identifying achievement
TextureID icon; // our icon
int maxval; // maximum value (0: no progress)
bool hasformat; // achievement description text must be formatted to add maxval
bool bitfield; // progress is tracked as a bitfield
int state, val; // set by other scripts, used to avoid excessive dictionary lookups
}
extend Class SWWMStaticHandler
{
ui int lastachievementnotify; // prevent overlap
Dictionary achievementstate, achievementprogress;
Array<SWWMAchievementInfo> achievementinfo; // specific info on achievements
private ui bool CheckAchievement( SWWMAchievementInfo a )
{
int val = achievementstate.At(a.basename).ToInt();
// manually check progress
if ( a.maxval )
{
int prog = achievementprogress.At(a.basename).ToInt();
// special case for bitfields
if ( a.bitfield )
{
int pb = 0;
for ( int i=0; i<a.maxval; i++ )
pb += !!(prog&(1<<i));
if ( val && (pb < a.maxval) )
{
achievementstate.Insert(a.basename,"0");
val = 0;
}
else if ( !val && (pb >= a.maxval) )
{
achievementstate.Insert(a.basename,"1");
val = 1;
}
}
else
{
if ( val && (prog < a.maxval) )
{
achievementstate.Insert(a.basename,"0");
val = 0;
}
else if ( !val && (prog >= a.maxval) )
{
achievementstate.Insert(a.basename,"1");
val = 1;
}
}
}
if ( (val == 1) && (gametic > lastachievementnotify) )
{
achievementstate.Insert(a.basename,"2");
EventHandler.SendNetworkEvent("swwmachievement."..a.basename,consoleplayer);
let notif = new("SWWMAchievementNotification").Init(a.basename,a.icon,a.hasformat?a.maxval:0);
StatusBar.AttachMessage(notif,-3478);
lastachievementnotify = gametic+200;
}
return (val > 0);
}
private ui void CheckAllAchievements()
{
// don't check constantly, and don't check during level transitions
if ( unloading || (maptime < 105) || (maptime%35) ) return;
bool alldone = true;
int ev = -1;
for ( int i=0; i<achievementinfo.Size(); i++ )
{
// this one is updated outside the loop
if ( achievementinfo[i].basename == "everything" )
{
ev = i;
continue;
}
if ( !CheckAchievement(achievementinfo[i]) )
alldone = false;
}
if ( ev == -1 ) return; // should not happen, though
int val = achievementstate.At("everything").ToInt();
// not done!
if ( !alldone )
{
if ( val != 0 ) achievementstate.Insert("everything","0");
return;
}
// all done!
if ( val == 0 ) achievementstate.Insert("everything","1");
else if ( (val == 1) && (gametic > lastachievementnotify) )
{
achievementstate.Insert("everything","2");
EventHandler.SendNetworkEvent("swwmachievement."..achievementinfo[ev].basename,consoleplayer);
let notif = new("SWWMAchievementNotification").Init(achievementinfo[ev].basename,achievementinfo[ev].icon);
StatusBar.AttachMessage(notif,-3478);
lastachievementnotify = gametic+200;
}
}
// parses achievements.lst file(s)
private void ParseAchievementList( out Array<SWWMAchievementInfo> achievements )
{
achievements.Clear();
let lmp = Wads.FindLumpFullName("achievements.lst");
if ( lmp == -1 ) ThrowAbortException("'achievements.lst' not found");
String dat;
Array<String> list, ln;
int bidx = 0;
while ( lmp != -1 )
{
dat = Wads.ReadLump(lmp);
// Windows pls
dat.Replace("\r","");
list.Clear();
dat.Split(list,"\n");
foreach ( l:list )
{
if ( (l.Length() == 0) || (l.Left(1) == "#") || (l.Left(1) == "") )
continue;
ln.Clear();
l.Split(ln,",",0);
// game filtering
if ( !(gameinfo.gametype&GAME_DOOM) && (ln[3] ~== "doom") ) continue;
else if ( !(gameinfo.gametype&GAME_HERETIC) && (ln[3] ~== "heretic") ) continue;
else if ( !(gameinfo.gametype&GAME_HEXEN) && (ln[3] ~== "hexen") ) continue;
else if ( !(gameinfo.gametype&GAME_RAVEN) && (ln[3] ~== "raven") ) continue;
else if ( !(gameinfo.gametype&(GAME_DOOM|GAME_HERETIC)) && (ln[3] ~== "nothexen") ) continue;
let ac = new("SWWMAchievementInfo");
ac.baseindex = bidx;
ac.basename = ln[0];
ac.icon = TexMan.CheckForTexture("graphics/Achievements/Achievement"..ac.basename..".png");
// fallback icon if one is not found
if ( !ac.icon.IsValid() ) ac.icon = TexMan.CheckForTexture("graphics/Achievements/DefaultAchievement.png");
ac.maxval = ln[1].ToInt();
// special cases for maxval == -1 (currently only one, so this is simplified)
if ( (ac.maxval == -1) && (ac.basename == "allcoll") )
{
int nc = 0;
foreach ( cls:AllActorClasses )
{
let c = (Class<SWWMCollectible>)(cls);
if ( !c || (c == 'SWWMCollectible') ) continue;
let def = GetDefaultByType(c);
// check that we can collect it in this IWAD
if ( !def.ValidGame() ) continue;
nc++;
}
ac.maxval = nc;
}
// bitfield
else if ( ac.maxval < -1 )
{
ac.bitfield = true;
ac.maxval = abs(ac.maxval);
}
ac.hasformat = (ln[2]~=="yes");
achievements.Push(ac);
bidx++;
}
lmp = Wads.FindLumpFullName("achievements.lst",lmp+1);
}
}
// load all achievement state and progress
private void LoadAchievements()
{
String statestr = swwm_achievementstate;
String progstr = swwm_achievementprogress;
// invalid length?
if ( (statestr.length()%2) || (progstr.length()%2) )
{
CreateAchievements();
return;
}
// decode
static const int cypher[] = {0xAD,0xEA,0xDB,0xED};
String nstr = "";
for ( uint i=0; i<statestr.length(); i+=2 )
{
int b, c, a;
b = statestr.ByteAt(i);
c = statestr.ByteAt(i+1);
a = ((b-0x41)|((c-0x41)<<4))^cypher[(i/2)%4];
nstr.AppendCharacter(a);
}
statestr = nstr;
nstr = "";
for ( uint i=0; i<progstr.length(); i+=2 )
{
int b, c, a;
b = progstr.ByteAt(i);
c = progstr.ByteAt(i+1);
a = ((b-0x41)|((c-0x41)<<4))^cypher[(i/2)%4];
nstr.AppendCharacter(a);
}
progstr = nstr;
// no key separator?
if ( (statestr.IndexOf(",") == -1) || (progstr.IndexOf(",") == -1) )
{
CreateAchievements();
return;
}
// create dictionaries
achievementstate = Dictionary.Create();
achievementprogress = Dictionary.Create();
Array<String> keys;
keys.Clear();
statestr.Split(keys,",");
foreach ( k:keys )
{
int colon = k.IndexOf(":");
// no value separator?
if ( colon == -1 )
{
CreateAchievements();
return;
}
achievementstate.Insert(k.Left(colon),k.Mid(colon+1));
}
keys.Clear();
progstr.Split(keys,",");
foreach ( k:keys )
{
int colon = k.IndexOf(":");
// no value separator?
if ( colon == -1 )
{
CreateAchievements();
return;
}
achievementprogress.Insert(k.Left(colon),k.Mid(colon+1));
}
// load achievement info and trim any bogus keys, as well as adding any new ones that are missing
ParseAchievementList(achievementinfo);
let di = DictionaryIterator.Create(achievementstate);
while ( di.Next() )
{
String key = di.Key();
bool deleteme = true;
foreach ( inf:achievementinfo )
{
if ( inf.basename != key ) continue;
deleteme = false;
break;
}
if ( deleteme )
{
if ( developer >= 2 ) Console.Printf("Deleting bogus achievement state %s = %s",key,di.Value());
achievementstate.Remove(key);
}
}
di = DictionaryIterator.Create(achievementprogress);
while ( di.Next() )
{
String key = di.Key();
bool deleteme = true;
foreach ( inf:achievementinfo )
{
if ( inf.basename != key ) continue;
if ( !inf.maxval ) continue;
deleteme = false;
break;
}
if ( deleteme )
{
if ( developer >= 2 ) Console.Printf("Deleting bogus achievement progress %s = %s",key,di.Value());
achievementprogress.Remove(key);
}
}
foreach ( inf:achievementinfo )
{
if ( achievementstate.At(inf.basename) == "" )
{
if ( developer >= 2 ) Console.Printf("Adding missing achievement state %s",inf.basename);
achievementstate.Insert(inf.basename,"0");
}
if ( inf.maxval && (achievementprogress.At(inf.basename) == "") )
{
if ( developer >= 2 ) Console.Printf("Adding missing achievement progress %s",inf.basename);
achievementprogress.Insert(inf.basename,"0");
}
}
}
// save all achievement state and progress
private void SaveAchievements()
{
String statestr = achievementstate.ToString();
String progstr = achievementprogress.ToString();
// trim unneeded json stuff
statestr.Replace("{","");
statestr.Replace("}","");
statestr.Replace("\"","");
progstr.Replace("{","");
progstr.Replace("}","");
progstr.Replace("\"","");
// cheap encode
static const int cypher[] = {0xAD,0xEA,0xDB,0xED};
String nstr = "";
for ( uint i=0; i<statestr.length(); i++ )
{
int a, b, c;
a = statestr.ByteAt(i)^cypher[i%4];
b = (a&0x0F)+0x41;
c = ((a&0xF0)>>4)+0x41;
nstr.AppendFormat("%c%c",b,c);
}
statestr = nstr;
nstr = "";
for ( uint i=0; i<progstr.length(); i++ )
{
int a, b, c;
a = progstr.ByteAt(i)^cypher[i%4];
b = (a&0x0F)+0x41;
c = ((a&0xF0)>>4)+0x41;
nstr.AppendFormat("%c%c",b,c);
}
progstr = nstr;
let cv = CVar.FindCVar('swwm_achievementstate');
cv.SetString(statestr);
cv = CVar.FindCVar('swwm_achievementprogress');
cv.SetString(progstr);
}
// blank achievement creation, used if loading fails
private void CreateAchievements()
{
achievementstate = Dictionary.Create();
achievementprogress = Dictionary.Create();
ParseAchievementList(achievementinfo);
foreach ( inf:achievementinfo )
{
achievementstate.Insert(inf.basename,"0");
if ( !inf.maxval ) continue;
achievementprogress.Insert(inf.basename,"0");
}
}
// migrate from old individual CVars
private void MigrateAchievements()
{
achievementstate = Dictionary.Create();
achievementprogress = Dictionary.Create();
ParseAchievementList(achievementinfo);
CVar cv;
foreach ( inf:achievementinfo )
{
String val = "0";
cv = CVar.FindCVar("swwm_achievement_"..inf.basename);
if ( cv ) val = cv.GetString();
achievementstate.Insert(inf.basename,val);
if ( !inf.maxval ) continue;
val = "0";
cv = CVar.FindCVar("swwm_progress_"..inf.basename);
if ( cv ) val = cv.GetString();
achievementprogress.Insert(inf.basename,val);
}
}
}