As of this commit, do not consider the experience when playing that new expansion to be complete. I've only partially written some of the mission texts and rudimentarily enhanced some boss fights. Currently there is one major limitation in that the intermission texts cannot be replaced, as they're hardcoded inside the UMAPINFO. I don't know if I can work around that.
765 lines
25 KiB
Text
765 lines
25 KiB
Text
// player-specific thinkers
|
|
|
|
Class SWWMStaticThinker : Thinker abstract
|
|
{
|
|
// shell class to iterate easier on sanity checks
|
|
// all subclasses should be guaranteed to have a statnum of STAT_STATIC
|
|
// if they don't, something is very wrong
|
|
}
|
|
|
|
// Dedicated mission log (for custom maps)
|
|
Class SWWMMissionLog : SWWMStaticThinker
|
|
{
|
|
Array<String> entries;
|
|
bool clockset;
|
|
int year, month, day, hour, minute;
|
|
String tz;
|
|
|
|
static clearscope SWWMMissionLog Get()
|
|
{
|
|
let ml = SWWMMissionLog(ThinkerIterator.Create('SWWMMissionLog',STAT_STATIC).Next());
|
|
return ml;
|
|
}
|
|
|
|
static play void AddLog( String str )
|
|
{
|
|
let ml = SWWMMissionLog(ThinkerIterator.Create('SWWMMissionLog',STAT_STATIC).Next());
|
|
if ( !ml )
|
|
{
|
|
ml = new('SWWMMissionLog');
|
|
ml.ChangeStatNum(STAT_STATIC);
|
|
}
|
|
if ( ml.entries.Find(str) < ml.entries.Size() ) return;
|
|
ml.entries.Push(str);
|
|
Console.Printf(StringTable.Localize("$SWWM_NEWMISSION"));
|
|
}
|
|
|
|
static play void SetClock( int year, int month, int day, int hour, int minute, String tz = "JST" )
|
|
{
|
|
let ml = SWWMMissionLog(ThinkerIterator.Create('SWWMMissionLog',STAT_STATIC).Next());
|
|
if ( !ml )
|
|
{
|
|
ml = new('SWWMMissionLog');
|
|
ml.ChangeStatNum(STAT_STATIC);
|
|
}
|
|
ml.clockset = true;
|
|
ml.year = year;
|
|
ml.month = month;
|
|
ml.day = day;
|
|
ml.hour = hour;
|
|
ml.minute = minute;
|
|
ml.tz = tz;
|
|
}
|
|
}
|
|
|
|
// Stats
|
|
Class WeaponUsage
|
|
{
|
|
Class<Weapon> w;
|
|
int kills;
|
|
}
|
|
|
|
Class MonsterKill
|
|
{
|
|
Class<Actor> m;
|
|
int kills;
|
|
}
|
|
|
|
Class LevelStat
|
|
{
|
|
bool hub, visited;
|
|
int cluster;
|
|
String levelname, mapname;
|
|
int kcount, ktotal;
|
|
int icount, itotal;
|
|
int scount, stotal;
|
|
int time, par, suck;
|
|
}
|
|
|
|
Class SWWMStats : SWWMStaticThinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int lastspawn, dashcount, boostcount, stompcount, airtime, kills,
|
|
deaths, damagedealt, hdamagedealt, damagetaken, hdamagetaken,
|
|
mkill, hiscore, topdealt, toptaken, skill, wponch,
|
|
busts, buttslams, secrets, items, parries, pparries, pats,
|
|
befriend, smooch;
|
|
double grounddist, airdist, swimdist, fuelusage, topspeed, teledist;
|
|
Array<WeaponUsage> wstats;
|
|
Array<MonsterKill> mstats;
|
|
Array<LevelStat> lstats;
|
|
Array<Class<Weapon> > alreadygot;
|
|
int favweapon;
|
|
// these two are used for mission updates
|
|
Array<int> clustervisit;
|
|
Array<bool> secretdone;
|
|
// hackaround for stuff getting lost
|
|
Array<Class<SWWMCollectible> > ownedcollectibles;
|
|
int plushuses;
|
|
// for pistol start info (to avoid it within hubs)
|
|
int lastcluster;
|
|
// for trash removal achievement
|
|
int nazicleanup;
|
|
// stored for hexen
|
|
int puzzlecnt, realpuzzlecnt;
|
|
// easter eggs
|
|
int silveregg, quadegg, deepegg;
|
|
bool oldcheat;
|
|
bool gotyorick;
|
|
// caching
|
|
SWWMHandler hnd;
|
|
|
|
bool GotWeapon( Class<Weapon> which )
|
|
{
|
|
foreach ( a:alreadygot )
|
|
{
|
|
if ( a == which ) return true;
|
|
}
|
|
alreadygot.Push(which);
|
|
return false;
|
|
}
|
|
|
|
void CleanGotWeapons()
|
|
{
|
|
// clean up the list of weapons we don't have anymore
|
|
// so the obtain lines play again
|
|
for ( int i=0; i<alreadygot.Size(); i++ )
|
|
{
|
|
// special case for dual weapons
|
|
String cn = alreadygot[i].GetClassName();
|
|
if ( (cn.Left(4) ~== "Dual") )
|
|
{
|
|
int ss = myplayer.mo.CountInv(GetDefaultByType(alreadygot[i]).SisterWeaponType);
|
|
if ( ss == 2 ) continue;
|
|
alreadygot.Delete(i);
|
|
i--;
|
|
continue;
|
|
}
|
|
if ( myplayer.mo.FindInventory(alreadygot[i]) ) continue;
|
|
alreadygot.Delete(i);
|
|
i--;
|
|
}
|
|
}
|
|
|
|
private Class<Weapon> WeaponFromInflictor( Actor inflictor, Name damagetype )
|
|
{
|
|
Class<Weapon> which = myplayer.ReadyWeapon?myplayer.ReadyWeapon.GetClass():null;
|
|
if ( inflictor is 'SWWMPuff' ) inflictor = inflictor.master; // special puffs transfer real inflictor through master pointer
|
|
if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass();
|
|
if ( which is 'DualExplodiumGun' ) which = 'ExplodiumGun'; // don't credit sister weapon
|
|
if ( inflictor && inflictor.FindInventory('ParriedBuff') ) which = 'DoomWeapon'; // gross hack
|
|
// properly credit some projectiles to their respective gun
|
|
else if ( inflictor is 'AirBullet' ) which = 'DeepImpact';
|
|
else if ( (inflictor is 'HammerShockwave') || (inflictor is 'HammerRadiusShockwave') ) which = 'ItamexHammer';
|
|
else if ( (inflictor is 'ExplodiumMagArm') || (inflictor is 'ExplodiumMagProj') || (inflictor is 'ExplodiumBulletImpact') ) which = 'ExplodiumGun';
|
|
else if ( (inflictor is 'GoldenSubImpact') || (inflictor is 'GoldenSubSubImpact') ) which = 'Spreadgun';
|
|
else if ( (inflictor is 'EvisceratorChunk') || (inflictor is 'EvisceratorProj') ) which = 'Eviscerator';
|
|
else if ( inflictor is 'SheenTrail' ) which = 'HeavyMahSheenGun';
|
|
else if ( (inflictor is 'HellblazerMissile') || (inflictor is 'HellblazerArm') ) which = 'Hellblazer';
|
|
else if ( (inflictor is 'QuadProj') || (inflictor is 'QuadExplArm') || (inflictor is 'OnFire') ) which = 'Quadravol';
|
|
else if ( (inflictor is 'BigBiospark') || (inflictor is 'BiosparkBall') || (inflictor is 'BiosparkBeamImpact') || (inflictor is 'BiosparkComboImpact') || (inflictor is 'BiosparkComboImpactSub') || (inflictor is 'BiosparkBeam') || (inflictor is 'BiosparkArc') || (inflictor is 'BiosparkCore') ) which = 'Sparkster';
|
|
else if ( (inflictor is 'SilverAirRip') || (inflictor is 'SilverImpact') ) which = 'SilverBullet';
|
|
else if ( (inflictor is 'CandyBeam') || (inflictor is 'CandyPop') || (inflictor is 'CandyMagArm') || (inflictor is 'CandyGunProj') || (inflictor is 'CandyMagProj') || (inflictor is 'CandyBulletImpact') ) which = 'CandyGun';
|
|
else if ( (inflictor is 'MisterBulletImpact') || (inflictor is 'MisterPop') || (inflictor is 'MisterFuzzy') || (inflictor is 'MisterGrenade') || (inflictor is 'MisterRailBeam') ) which = 'MisterRifle';
|
|
else if ( (inflictor is 'YnykronBeam') || (inflictor is 'YnykronImpact') || (inflictor is 'YnykronSingularity') || (inflictor is 'YnykronCloud') || (inflictor is 'YnykronVoidBeam') || (inflictor is 'YnykronLightningArc') || (inflictor is 'YnykronLightningImpact') ) which = 'Ynykron';
|
|
else if ( (inflictor is 'Demolitionist') || (inflictor is 'DemolitionistShockwave') || (inflictor is 'DemolitionistRadiusShockwave') || (inflictor is 'SWWMGesture')
|
|
|| (inflictor is 'SWWMItemGesture') ) which = 'SWWMWeapon'; // hack to assume Demolitionist as weapon
|
|
else if ( inflictor is 'BigPunchSplash' )
|
|
{
|
|
// guess from damagetype
|
|
if ( (damagetype == 'Jump') || (damagetype == 'Dash') || (damagetype == 'Buttslam') || (damagetype == 'GroundPound') )
|
|
which = 'SWWMWeapon';
|
|
else if ( damagetype == 'Love' )
|
|
which = 'SWWMGesture';
|
|
// others are just weapon melee, so keep the readyweapon
|
|
}
|
|
else if ( inflictor is 'FroggyChair' )
|
|
which = 'SWWMItemGesture'; // more gross hacks
|
|
if ( damagetype == 'Falling' )
|
|
which = 'Weapon'; // the gross hacks continue
|
|
return which;
|
|
}
|
|
|
|
void AddDamageDealt( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt += lower;
|
|
if ( damagedealt > 999999999 )
|
|
{
|
|
upper = damagedealt/1000000000;
|
|
lower = damagedealt%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt = lower;
|
|
}
|
|
}
|
|
void AddDamageTaken( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken += lower;
|
|
if ( damagetaken > 999999999 )
|
|
{
|
|
upper = damagetaken/1000000000;
|
|
lower = damagetaken%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken = lower;
|
|
}
|
|
}
|
|
|
|
private LevelStat FindLevelStats( String mapname )
|
|
{
|
|
foreach ( ls:lstats )
|
|
{
|
|
if ( ls.mapname != mapname ) continue;
|
|
return ls;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// we doin' that thing again, yup
|
|
int ClusterRemap( int clus, int levelnum )
|
|
{
|
|
if ( SWWMUtility.IsEviternityTwo() )
|
|
{
|
|
// clusters in eviternity 2 have to be remapped
|
|
if ( clus == 5 ) return 1;
|
|
if ( (clus == 6) || (clus == 13) ) return 2;
|
|
if ( (clus == 7) || (clus == 14) ) return 3;
|
|
if ( (clus == 8) || (clus == 15) ) return 4;
|
|
if ( (clus == 9) || (clus == 16) ) return 5;
|
|
if ( (clus == 10) || (clus == 17) ) return 6;
|
|
if ( (clus == 11) || (clus == 12) || (clus == 18) || (clus == 19) ) return 7;
|
|
}
|
|
else if ( SWWMUtility.IsEviternity() )
|
|
{
|
|
// we have to do some heavy lifting here because episodes don't match clusters
|
|
if ( levelnum <= 0 ) return clus;
|
|
if ( levelnum <= 5 ) return 1;
|
|
if ( levelnum <= 10 ) return 2;
|
|
if ( levelnum <= 15 ) return 3;
|
|
if ( levelnum <= 20 ) return 4;
|
|
if ( levelnum <= 25 ) return 5;
|
|
if ( levelnum <= 30 ) return 6;
|
|
if ( levelnum <= 31 ) return 7;
|
|
if ( levelnum <= 32 ) return 8;
|
|
}
|
|
else if ( SWWMUtility.IsLegacyOfRust() )
|
|
{
|
|
// legacy of rust has its quirks, as with umapinfo there are
|
|
// technically no clusters
|
|
if ( levelnum <= 0 ) return clus;
|
|
if ( (levelnum <= 7) || (levelnum == 15) ) return 28;
|
|
else if ( (levelnum <= 14) || (levelnum == 16) ) return 29;
|
|
}
|
|
return clus;
|
|
}
|
|
|
|
void PreloadLevelStats()
|
|
{
|
|
// pre-adds all unvisited levels from the current cluster
|
|
int nlevels = LevelInfo.GetLevelInfoCount();
|
|
int ourcluster = ClusterRemap(level.cluster,level.levelnum);
|
|
for ( int i=0; i<nlevels; i++ )
|
|
{
|
|
let li = LevelInfo.GetLevelInfo(i);
|
|
if ( !li.isValid() || !LevelInfo.MapExists(li.mapname)
|
|
|| FindLevelStats(li.mapname) )
|
|
continue;
|
|
int theircluster = ClusterRemap(li.cluster,li.levelnum);
|
|
if ( theircluster != ourcluster )
|
|
continue;
|
|
let ls = new('LevelStat');
|
|
// we can automatically assume that all levels from the same cluster will have this flag as well
|
|
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
|
|
ls.visited = false;
|
|
ls.cluster = theircluster;
|
|
ls.levelname = li.LookupLevelName();
|
|
// level name may contain trailing whitespace due to DEHACKED nonsense, so strip it
|
|
ls.levelname.StripRight();
|
|
if ( li.authorname == "" ) // the author name might be part of the level name, try to strip it
|
|
{
|
|
int iof;
|
|
if ( ((iof = ls.levelname.RightIndexOf(" - by: ")) != -1) || ((iof = ls.levelname.RightIndexOf(" - by ")) != -1) || ((iof = ls.levelname.RightIndexOf(" - ")) != -1) )
|
|
ls.levelname.Truncate(iof);
|
|
}
|
|
ls.mapname = li.mapname;
|
|
// append after last entry whose cluster is lower or equal to our own
|
|
// or just push otherwise
|
|
int lastent = -1, i = 0;
|
|
foreach ( ls:lstats )
|
|
{
|
|
if ( ls.cluster <= li.cluster )
|
|
lastent = i;
|
|
i++;
|
|
}
|
|
if ( lastent != -1 ) lstats.Insert(lastent+1,ls);
|
|
else lstats.Push(ls);
|
|
}
|
|
}
|
|
|
|
void AddLevelStats()
|
|
{
|
|
// overwrite any existing entry
|
|
let ls = FindLevelStats(level.mapname);
|
|
if ( ls )
|
|
{
|
|
ls.visited = true;
|
|
ls.kcount = level.killed_monsters;
|
|
ls.ktotal = level.total_monsters;
|
|
ls.icount = level.found_items;
|
|
ls.itotal = level.total_items;
|
|
ls.scount = level.found_secrets;
|
|
ls.stotal = level.total_secrets;
|
|
ls.time = level.maptime;
|
|
ls.par = level.partime;
|
|
ls.suck = level.sucktime;
|
|
return;
|
|
}
|
|
ls = new('LevelStat');
|
|
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
|
|
ls.visited = true;
|
|
ls.cluster = level.cluster;
|
|
ls.levelname = level.levelname;
|
|
// level name may contain trailing whitespace due to DEHACKED nonsense, so strip it
|
|
ls.levelname.StripRight();
|
|
if ( level.authorname == "" ) // the author name might be part of the level name, try to strip it
|
|
{
|
|
int iof;
|
|
if ( ((iof = ls.levelname.RightIndexOf(" - by: ")) != -1) || ((iof = ls.levelname.RightIndexOf(" - by ")) != -1) || ((iof = ls.levelname.RightIndexOf(" - ")) != -1) )
|
|
ls.levelname.Truncate(iof);
|
|
}
|
|
ls.mapname = level.mapname;
|
|
ls.kcount = level.killed_monsters;
|
|
ls.ktotal = level.total_monsters;
|
|
ls.icount = level.found_items;
|
|
ls.itotal = level.total_items;
|
|
ls.scount = level.found_secrets;
|
|
ls.stotal = level.total_secrets;
|
|
ls.time = level.maptime;
|
|
ls.par = level.partime;
|
|
ls.suck = level.sucktime;
|
|
// append to last entry whose cluster is lower or equal to our own
|
|
// or just push otherwise
|
|
int lastent = -1, i = 0;
|
|
foreach ( ls:lstats )
|
|
{
|
|
if ( ls.cluster <= level.cluster )
|
|
lastent = i;
|
|
i++;
|
|
}
|
|
if ( lastent != -1 ) lstats.Insert(lastent,ls);
|
|
else lstats.Push(ls);
|
|
}
|
|
|
|
void AddWeaponKill( Actor inflictor, Actor victim, Name damagetype )
|
|
{
|
|
if ( victim )
|
|
{
|
|
if ( !hnd ) hnd = SWWMHandler(EventHandler.Find('SWWMHandler'));
|
|
Class<Actor> mvictim = SWWMUtility.MergeMonster(hnd,victim.GetClass());
|
|
bool found = false;
|
|
foreach ( ms:mstats )
|
|
{
|
|
if ( ms.m != mvictim ) continue;
|
|
found = true;
|
|
ms.kills++;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
let ms = new('MonsterKill');
|
|
ms.m = mvictim;
|
|
ms.kills = 1;
|
|
mstats.Push(ms);
|
|
}
|
|
}
|
|
Class<Weapon> which = WeaponFromInflictor(inflictor,damagetype);
|
|
if ( !which ) return;
|
|
for ( int i=0; i<wstats.Size(); i++ )
|
|
{
|
|
if ( wstats[i].w != which ) continue;
|
|
wstats[i].kills++;
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < wstats[i].kills) ) favweapon = i;
|
|
return;
|
|
}
|
|
let ws = new('WeaponUsage');
|
|
ws.w = which;
|
|
ws.kills = 1;
|
|
wstats.Push(ws);
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < ws.kills) )
|
|
favweapon = wstats.Size()-1;
|
|
}
|
|
|
|
static clearscope SWWMStats Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create('SWWMStats',STAT_STATIC);
|
|
SWWMStats t;
|
|
while ( t = SWWMStats(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Scoring
|
|
Class SWWMCredits : SWWMStaticThinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int credits;
|
|
|
|
static void Give( PlayerInfo p, int amount )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return;
|
|
// safeguard
|
|
if ( amount < 0 ) ThrowAbortException("SWWMCredits.Give() called with negative amount");
|
|
if ( c.credits+amount < c.credits ) c.credits = 999999999;
|
|
else c.credits = min(999999999,c.credits+amount);
|
|
let s = SWWMStats.Find(p);
|
|
if ( s && (c.credits > s.hiscore) ) s.hiscore = c.credits;
|
|
// append to hud
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( players[i] != p ) continue;
|
|
EventHandler.SendInterfaceEvent(i,"swwmhudgivescore",amount);
|
|
break;
|
|
}
|
|
SWWMLoreLibrary.Add(p,"ScoreSystem");
|
|
}
|
|
|
|
static clearscope bool CanTake( PlayerInfo p, int amount )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
// safeguard
|
|
if ( amount < 0 ) ThrowAbortException("SWWMCredits.CanTake() called with negative amount");
|
|
// too much!
|
|
if ( (amount > 999999999) || (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false;
|
|
return true;
|
|
}
|
|
|
|
static bool Take( PlayerInfo p, int amount )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
// safeguard
|
|
if ( amount < 0 ) ThrowAbortException("SWWMCredits.Take() called with negative amount");
|
|
// too much!
|
|
if ( (amount > 999999999) || (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false;
|
|
c.credits -= amount;
|
|
// append to hud
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( players[i] != p ) continue;
|
|
EventHandler.SendInterfaceEvent(i,"swwmhudtakescore",amount);
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static clearscope int Get( PlayerInfo p )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return 0;
|
|
return c.credits;
|
|
}
|
|
|
|
static clearscope SWWMCredits Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create('SWWMCredits',STAT_STATIC);
|
|
SWWMCredits t;
|
|
while ( t = SWWMCredits(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Lore holder
|
|
enum ELoreTab
|
|
{
|
|
LORE_ITEM,
|
|
LORE_ENEMY,
|
|
LORE_PEOPLE,
|
|
LORE_LORE // lol
|
|
};
|
|
|
|
Class SWWMLore
|
|
{
|
|
String tag, text, assoc;
|
|
TextureID img;
|
|
int tab;
|
|
bool read;
|
|
}
|
|
|
|
Class SWWMLoreLibrary : SWWMStaticThinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
Array<SWWMLore> ent;
|
|
int lastaddtic;
|
|
|
|
static bool PreVerify( String ref )
|
|
{
|
|
// restrictions
|
|
let mlog = SWWMMissionLog.Get();
|
|
if ( !(gameinfo.gametype&GAME_Raven) && (!mlog || ((mlog.year < 2171) && (mlog.month < 3))) )
|
|
{
|
|
if ( ref ~== "Parthoris" ) return true;
|
|
if ( ref ~== "Sidhe" ) return true;
|
|
if ( ref ~== "SerpentRiders" ) return true;
|
|
}
|
|
if ( !(gameinfo.gametype&GAME_Hexen) && (!mlog || ((mlog.year < 2171) && (mlog.month < 4))) )
|
|
{
|
|
if ( ref ~== "Cronos" ) return true;
|
|
if ( ref ~== "Kirin" ) return true; // not met
|
|
if ( ref ~== "Alakir" ) return true; // likewise
|
|
if ( ref ~== "Fabricator" ) return true; // not yet introduced
|
|
if ( ref ~== "Administrators" ) return true; // not met
|
|
}
|
|
// check if entry is for a collectible
|
|
foreach ( cls:AllActorClasses )
|
|
{
|
|
let c = (Class<SWWMCollectible>)(cls);
|
|
if ( !c || (c == 'SWWMCollectible') ) continue;
|
|
let def = GetDefaultByType(c);
|
|
// skip if we match and it's not for this game
|
|
if ( (c.GetClassName() == ref) && !def.ValidGame() )
|
|
return true;
|
|
}
|
|
ref = ref.MakeUpper();
|
|
String tag = String.Format("SWWM_LORETAG_%s",ref);
|
|
String tab = String.Format("SWWM_LORETAB_%s",ref);
|
|
String text = String.Format("SWWM_LORETXT_%s",ref);
|
|
// check that it's valid
|
|
if ( StringTable.Localize(tag,false) == tag ) return true;
|
|
if ( StringTable.Localize(tab,false) == tab )
|
|
{
|
|
if ( developer >= 2 ) Console.Printf("Entry \"%s\" defines no tab.",ref);
|
|
return true;
|
|
}
|
|
if ( StringTable.Localize(text,false) == text )
|
|
{
|
|
if ( developer >= 2 ) Console.Printf("Entry \"%s\" defines no text.",ref);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DirectAdd( String ref )
|
|
{
|
|
if ( PreVerify(ref) ) return true;
|
|
return InternalAdd(ref);
|
|
}
|
|
|
|
private bool InternalAdd( String ref )
|
|
{
|
|
ref = ref.MakeUpper();
|
|
String tag = String.Format("SWWM_LORETAG_%s",ref);
|
|
String tab = String.Format("SWWM_LORETAB_%s",ref);
|
|
String text = String.Format("SWWM_LORETXT_%s",ref);
|
|
String assoc = String.Format("SWWM_LOREREL_%s",ref);
|
|
// redirects
|
|
let mlog = SWWMMissionLog.Get();
|
|
if ( (gameinfo.gametype&GAME_Hexen) || (mlog && (mlog.year >= 2171) && (mlog.month >= 4)) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_AKARIPROJECT" )
|
|
text = "SWWM_LORETXT_AKARIPROJECT3"; // mentions kirin
|
|
else if ( text ~== "SWWM_LORETXT_ANARUKON" )
|
|
text = "SWWM_LORETXT_ANARUKON2"; // comments from miyamoto-xanai wedding
|
|
else if ( text ~== "SWWM_LORETXT_DEMOLITIONIST" )
|
|
text = "SWWM_LORETXT_DEMOLITIONIST3"; // married to kirin
|
|
else if ( text ~== "SWWM_LORETXT_DEMONINVASION" )
|
|
text = "SWWM_LORETXT_DEMONINVASION4"; // meeting with azkhan
|
|
else if ( text ~== "SWWM_LORETXT_GHOULHUNT" )
|
|
text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the wedding
|
|
else if ( text ~== "SWWM_LORETXT_GODS" )
|
|
text = "SWWM_LORETXT_GODS2"; // beyond gods
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL4"; // met father nostros during the wedding
|
|
else if ( text ~== "SWWM_LORETXT_MAIDBOT" )
|
|
text = "SWWM_LORETXT_MAIDBOT2"; // married to kirin
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA3"; // stuff that happened at the wedding
|
|
else if ( text ~== "SWWM_LORETXT_NOVOSKHANA" )
|
|
text = "SWWM_LORETXT_NOVOSKHANA2"; // met up with the empress
|
|
else if ( text ~== "SWWM_LORETXT_RAGEKIT" )
|
|
text = "SWWM_LORETXT_RAGEKIT2"; // kirin's reactions to demo using this item
|
|
else if ( text ~== "SWWM_LORETXT_SANKAIDERIHA" )
|
|
text = "SWWM_LORETXT_SANKAIDERIHA2"; // comments about kirin
|
|
else if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA3"; // married kirin
|
|
else if ( text ~== "SWWM_LORETXT_SAFETYTETHER" )
|
|
text = "SWWM_LORETXT_SAFETYTETHER2"; // we're in cronos now
|
|
else if ( text ~== "SWWM_LORETXT_SERPENTRIDERS" )
|
|
text = "SWWM_LORETXT_SERPENTRIDERS2"; // defeated d'sparil
|
|
else if ( text ~== "SWWM_LORETXT_XANIMEN" )
|
|
text = "SWWM_LORETXT_XANIMEN2"; // footnote about nuoma
|
|
else if ( text ~== "SWWM_LORETXT_YNYKRON" )
|
|
text = "SWWM_LORETXT_YNYKRON2"; // confirmed to harm (but not kill) gods
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH2" )
|
|
text = "SWWM_LORETXT_ZANAVETH22"; // met at wedding
|
|
else if ( text ~== "SWWM_LORETXT_MADCAT" )
|
|
text = "SWWM_LORETXT_MADCAT3"; // demolition quest released
|
|
else if ( text ~== "SWWM_LORETXT_MARISA" )
|
|
text = "SWWM_LORETXT_MARISA3"; // post-wedding update
|
|
}
|
|
if ( (gameinfo.gametype&GAME_Raven) || (mlog && (mlog.year >= 2171) && (mlog.month >= 3)) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_AKARIPROJECT" )
|
|
text = "SWWM_LORETXT_AKARIPROJECT2"; // fiction becomes reality
|
|
else if ( text ~== "SWWM_LORETXT_ARCHDEMONS" )
|
|
text = "SWWM_LORETXT_ARCHDEMONS3"; // archdemon generals assassinated
|
|
else if ( text ~== "SWWM_LORETXT_DECADEMECH" )
|
|
text = "SWWM_LORETXT_DECADEMECH2"; // extra info
|
|
else if ( text ~== "SWWM_LORETXT_DEMONINVASION" )
|
|
text = "SWWM_LORETXT_DEMONINVASION3"; // events of doom 64 and such
|
|
else if ( text ~== "SWWM_LORETXT_DOOMGUY" )
|
|
text = "SWWM_LORETXT_DOOMGUY3"; // he gone
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL3"; // invasion was a thing of the past
|
|
else if ( text ~== "SWWM_LORETXT_UAC" )
|
|
text = "SWWM_LORETXT_UAC3"; // events of doom 64 and more
|
|
else if ( text ~== "SWWM_LORETXT_MADCAT" )
|
|
text = "SWWM_LORETXT_MADCAT2"; // interstellar demolitionist released
|
|
else if ( text ~== "SWWM_LORETXT_MARISA" )
|
|
text = "SWWM_LORETXT_MARISA2"; // post-invasion update
|
|
}
|
|
if ( (gameinfo.gametype&GAME_Raven) || SWWMUtility.IsEviternity() || SWWMUtility.IsEviternityTwo() || SWWMUtility.IsLegacyOfRust() || (mlog && (mlog.year >= 2150) && (mlog.month >= 5)) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_AKARILABS" )
|
|
text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced
|
|
else if ( text ~== "SWWM_LORETXT_ARCHDEMONS" )
|
|
text = "SWWM_LORETXT_ARCHDEMONS2"; // demon invasion repelled
|
|
else if ( text ~== "SWWM_LORETXT_BIGSHOT" )
|
|
text = "SWWM_LORETXT_BIGSHOT2"; // predictions about crimes_m
|
|
else if ( text ~== "SWWM_LORETXT_DEMOLITIONIST" )
|
|
text = "SWWM_LORETXT_DEMOLITIONIST2"; // demo rewarded with maidbot frame
|
|
else if ( text ~== "SWWM_LORETXT_DEMONINVASION" )
|
|
text = "SWWM_LORETXT_DEMONINVASION2"; // victory against invasion
|
|
else if ( text ~== "SWWM_LORETXT_DOOMGUY" )
|
|
text = "SWWM_LORETXT_DOOMGUY2"; // decommissioned
|
|
else if ( text ~== "SWWM_LORETXT_GENERICCUBE" )
|
|
text = "SWWM_LORETXT_GENERICCUBE2"; // info from mykka
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL2"; // events of tnt/plutonia
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA2"; // demo met nana
|
|
else if ( text ~== "SWWM_LORETXT_RIKA" )
|
|
text = "SWWM_LORETXT_RIKA2"; // saw demo's maidbot frame
|
|
else if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA2"; // dating demo
|
|
else if ( text ~== "SWWM_LORETXT_UAC" )
|
|
text = "SWWM_LORETXT_UAC2"; // uac "reformed"
|
|
else if ( text ~== "SWWM_LORETXT_VOICEBOX" )
|
|
text = "SWWM_LORETXT_VOICEBOX2"; // remarks on demo's voice as a maidbot
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH3" )
|
|
text = "SWWM_LORETXT_ZANAVETH32"; // iagb happened
|
|
}
|
|
// check if existing
|
|
foreach ( e:ent )
|
|
{
|
|
if ( e.tag != "$"..tag ) continue;
|
|
return true;
|
|
}
|
|
SWWMLore e = new('SWWMLore');
|
|
e.tag = "$"..tag;
|
|
if ( StringTable.Localize(e.tag) == "" )
|
|
{
|
|
if ( developer >= 2 ) Console.Printf("Entry \"%s\" has an empty tag.",ref);
|
|
return true;
|
|
}
|
|
String ttab = StringTable.Localize(tab,false);
|
|
if ( ttab ~== "People" ) e.tab = LORE_PEOPLE;
|
|
else if ( ttab ~== "Lore" ) e.tab = LORE_LORE;
|
|
else if ( ttab ~== "Item" ) e.tab = LORE_ITEM;
|
|
else if ( ttab ~== "Enemy" ) e.tab = LORE_ENEMY;
|
|
else
|
|
{
|
|
if ( developer >= 2 ) Console.Printf("Entry \"%s\" has an incorrect tab setting of \"%s\".",ref,ttab);
|
|
return true;
|
|
}
|
|
e.text = "$"..text;
|
|
if ( StringTable.Localize(e.text) == "" )
|
|
{
|
|
if ( developer >= 2 ) Console.Printf("Entry \"%s\" has empty text.",ref);
|
|
return true;
|
|
}
|
|
e.assoc = "$"..assoc;
|
|
e.read = false;
|
|
// image for certain entries
|
|
e.img = TexMan.CheckForTexture("graphics/KBase/PFP_"..ref..".png");
|
|
// "new lore" message
|
|
if ( (level.maptime > 0) && (gametic > lastaddtic) && (myplayer == players[consoleplayer]) && (!menuactive || (menuactive == Menu.OnNoPause)) && (myplayer.mo is 'Demolitionist') )
|
|
Console.Printf(StringTable.Localize("$SWWM_NEWLORE"));
|
|
lastaddtic = gametic;
|
|
ent.Push(e);
|
|
return true;
|
|
}
|
|
|
|
static void Add( PlayerInfo p, String ref )
|
|
{
|
|
if ( deathmatch || PreVerify(ref) ) return;
|
|
SWWMLoreLibrary ll = Find(p);
|
|
if ( !ll )
|
|
{
|
|
ll = new('SWWMLoreLibrary');
|
|
ll.ChangeStatNum(STAT_STATIC);
|
|
ll.myplayer = p;
|
|
}
|
|
ll.InternalAdd(ref);
|
|
}
|
|
|
|
void MarkRead( int idx )
|
|
{
|
|
if ( (idx < 0) || (idx >= ent.Size()) ) return;
|
|
if ( !ent[idx].read )
|
|
{
|
|
ent[idx].read = true;
|
|
// add associated entries
|
|
Array<String> rel;
|
|
rel.Clear();
|
|
String assocstr = StringTable.Localize(ent[idx].assoc);
|
|
assocstr.Split(rel,";",0);
|
|
foreach ( r:rel )
|
|
{
|
|
if ( r == "" ) continue;
|
|
bool rslt = DirectAdd(r);
|
|
if ( (developer >= 2) && !rslt )
|
|
Console.Printf("Related entry \"%s\" not found.",r);
|
|
}
|
|
}
|
|
}
|
|
|
|
clearscope int FindEntry( String tag )
|
|
{
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag ~== tag )
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static clearscope SWWMLoreLibrary Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create('SWWMLoreLibrary',STAT_STATIC);
|
|
SWWMLoreLibrary ll;
|
|
while ( ll = SWWMLoreLibrary(ti.Next()) )
|
|
{
|
|
if ( ll.myplayer != p ) continue;
|
|
return ll;
|
|
}
|
|
return Null;
|
|
}
|
|
}
|