633 lines
21 KiB
Text
633 lines
21 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
|
|
}
|
|
|
|
// "Full History" contains all messages since session start, nothing is flushed
|
|
// this can be accessed from a section of the knowledge base
|
|
Class SWWMFullHistory : SWWMStaticThinker
|
|
{
|
|
String lastmap;
|
|
Array<MsgLine> msg;
|
|
|
|
static clearscope SWWMFullHistory Get()
|
|
{
|
|
let fh = SWWMFullHistory(ThinkerIterator.Create("SWWMFullHistory",STAT_STATIC).Next());
|
|
return fh;
|
|
}
|
|
|
|
static play void PushMessage( String str, int tic, int type )
|
|
{
|
|
let fh = SWWMFullHistory(ThinkerIterator.Create("SWWMFullHistory",STAT_STATIC).Next());
|
|
if ( !fh )
|
|
{
|
|
fh = new("SWWMFullHistory");
|
|
fh.ChangeStatNum(STAT_STATIC);
|
|
}
|
|
MsgLine m;
|
|
if ( level.mapname != fh.lastmap )
|
|
{
|
|
// push a map change label
|
|
m = new("MsgLine");
|
|
m.str = level.levelname;
|
|
m.type = -1;
|
|
fh.lastmap = level.mapname;
|
|
fh.msg.Push(m);
|
|
}
|
|
m = new("MsgLine");
|
|
m.str = str;
|
|
m.tic = tic;
|
|
m.type = type;
|
|
fh.msg.Push(m);
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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;
|
|
bool oldcheat;
|
|
bool gotyorick;
|
|
|
|
bool GotWeapon( Class<Weapon> which )
|
|
{
|
|
for ( int i=0; i<alreadygot.Size(); i++ )
|
|
{
|
|
if ( alreadygot[i] == which ) return true;
|
|
}
|
|
alreadygot.Push(which);
|
|
return false;
|
|
}
|
|
|
|
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 'ExplodiumMagArm') || (inflictor is 'ExplodiumMagProj') || (inflictor is 'ExplodiumBulletImpact') ) which = 'ExplodiumGun';
|
|
else if ( ((inflictor is 'SaltImpact') && !inflictor.Args[0]) || ((inflictor is 'SaltBeam') && !inflictor.Args[1]) || (inflictor is 'CorrodeDebuff') || (inflictor is 'CorrosiveFlechette') || ((inflictor is 'TheBall') && !inflictor.special1) || (inflictor is 'GoldenImpact') || (inflictor is 'GoldenSubImpact') || (inflictor is 'GoldenSubSubImpact') ) which = 'Spreadgun';
|
|
else if ( ((inflictor is 'SaltImpact') && inflictor.Args[0]) || ((inflictor is 'SaltBeam') && inflictor.Args[1]) || ((inflictor is 'TheBall') && inflictor.special1) ) which = 'Wallbuster';
|
|
else if ( (inflictor is 'EvisceratorChunk') || (inflictor is 'EvisceratorProj') ) which = 'Eviscerator';
|
|
else if ( (inflictor is 'HellblazerMissile') || (inflictor is 'HellblazerRavagerArm') || (inflictor is 'HellblazerWarheadArm') ) which = 'Hellblazer';
|
|
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 'SilverAirRip2') || (inflictor is 'SilverImpact') || (inflictor is 'FatChodeImpact') || (inflictor is 'FatChodeExplosionArm') ) 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 '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;
|
|
}
|
|
}
|
|
|
|
void AddLevelStats()
|
|
{
|
|
let ls = new("LevelStat");
|
|
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
|
|
ls.levelname = level.levelname;
|
|
int iof = ls.levelname.IndexOf(" - by: ");
|
|
if ( iof != -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;
|
|
lstats.Push(ls);
|
|
}
|
|
|
|
void AddWeaponKill( Actor inflictor, Actor victim, Name damagetype )
|
|
{
|
|
if ( victim )
|
|
{
|
|
Class<Actor> mvictim = SWWMUtility.MergeMonster(victim.GetClass());
|
|
bool found = false;
|
|
for ( int i=0; i<mstats.Size(); i++ )
|
|
{
|
|
if ( mstats[i].m != mvictim ) continue;
|
|
found = true;
|
|
mstats[i].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;
|
|
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, int hamount = 0 )
|
|
{
|
|
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;
|
|
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 ~== "Fabricator" ) return true; // not yet introduced
|
|
if ( ref ~== "Administrators" ) return true; // not met
|
|
}
|
|
// check if entry is for a collectible
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let c = (Class<SWWMCollectible>)(AllActorClasses[i]);
|
|
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 )
|
|
{
|
|
Console.Printf("Entry \"%s\" defines no tab.",ref);
|
|
return true;
|
|
}
|
|
if ( StringTable.Localize(text,false) == text )
|
|
{
|
|
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_BRCALBUM" )
|
|
text = "SWWM_LORETXT_BRCALBUM2"; // comment about kirin's song
|
|
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_IBUKIMILK" )
|
|
text = "SWWM_LORETXT_IBUKIMILK3"; // tasted by kirin
|
|
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_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() || (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_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_IBUKIMILK" )
|
|
text = "SWWM_LORETXT_IBUKIMILK2"; // tasted by demo
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA2"; // demo met nana
|
|
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
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag != "$"..tag ) continue;
|
|
return true;
|
|
}
|
|
SWWMLore e = new("SWWMLore");
|
|
e.tag = "$"..tag;
|
|
if ( StringTable.Localize(e.tag) == "" )
|
|
{
|
|
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
|
|
{
|
|
Console.Printf("Entry \"%s\" has an incorrect tab setting of \"%s\".",ref,ttab);
|
|
return true;
|
|
}
|
|
e.text = "$"..text;
|
|
if ( StringTable.Localize(e.text) == "" )
|
|
{
|
|
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",TexMan.Type_Any);
|
|
// "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);
|
|
for ( int i=0; i<rel.Size(); i++ )
|
|
{
|
|
if ( (rel[i] != "") && !DirectAdd(rel[i]) )
|
|
Console.Printf("Related entry \"%s\" not found, please update LANGUAGE.txt",rel[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|