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.
530 lines
14 KiB
Text
530 lines
14 KiB
Text
// identification and tagging
|
|
extend Class SWWMUtility
|
|
{
|
|
// because GetTag() returns the localized string, we need to do things the hard way
|
|
static play String GetFunTag( SWWMHandler hnd, Actor a, String defstr = "" )
|
|
{
|
|
// look up fun tag services if available
|
|
foreach ( sv:hnd.funtagsv )
|
|
{
|
|
if ( !sv ) continue;
|
|
String res = sv.GetString("GetFunTag",objectArg:a);
|
|
if ( res == "" ) continue;
|
|
return res;
|
|
}
|
|
int ntags = 1;
|
|
String basetag = "";
|
|
switch ( a.GetClassName() )
|
|
{
|
|
// Doom
|
|
case 'ZombieMan':
|
|
case 'StealthZombieMan':
|
|
basetag = "ZOMBIE";
|
|
break;
|
|
case 'ShotgunGuy':
|
|
case 'StealthShotgunGuy':
|
|
basetag = "SHOTGUN";
|
|
break;
|
|
case 'ChaingunGuy':
|
|
case 'StealthChaingunGuy':
|
|
basetag = "HEAVY";
|
|
break;
|
|
case 'DoomImp':
|
|
case 'StealthDoomImp':
|
|
basetag = "IMP";
|
|
break;
|
|
case 'Demon':
|
|
case 'StealthDemon':
|
|
basetag = "DEMON";
|
|
break;
|
|
case 'Spectre':
|
|
basetag = "SPECTRE";
|
|
break;
|
|
case 'LostSoul':
|
|
case 'LostSoulEvit2':
|
|
case 'LostSoulCount':
|
|
basetag = "LOST";
|
|
break;
|
|
case 'Cacodemon':
|
|
case 'StealthCacodemon':
|
|
basetag = "CACO";
|
|
break;
|
|
case 'HellKnight':
|
|
case 'StealthHellKnight':
|
|
basetag = "HELL";
|
|
break;
|
|
case 'BaronOfHell':
|
|
case 'StealthBaron':
|
|
basetag = "BARON";
|
|
break;
|
|
case 'Arachnotron':
|
|
case 'StealthArachnotron':
|
|
basetag = "ARACH";
|
|
break;
|
|
case 'PainElemental':
|
|
basetag = "PAIN";
|
|
break;
|
|
case 'Revenant':
|
|
case 'StealthRevenant':
|
|
basetag = "REVEN";
|
|
break;
|
|
case 'Fatso':
|
|
case 'StealthFatso':
|
|
basetag = "MANCU";
|
|
break;
|
|
case 'Archvile':
|
|
case 'StealthArchvile':
|
|
basetag = "ARCH";
|
|
break;
|
|
case 'SpiderMastermind':
|
|
basetag = "SPIDER";
|
|
break;
|
|
case 'Cyberdemon':
|
|
case 'CyberdemonEvit2':
|
|
case 'CyberdemonMAP24':
|
|
basetag = "CYBER";
|
|
break;
|
|
case 'ID24Banshee':
|
|
basetag = "ID24BANSHEE";
|
|
break;
|
|
case 'ID24Ghoul':
|
|
basetag = "ID24GHOUL";
|
|
break;
|
|
case 'ID24Mindweaver':
|
|
basetag = "ID24MINDWEAVER";
|
|
break;
|
|
case 'ID24PlasmaGuy':
|
|
basetag = "ID24SHOCKTROOPER";
|
|
break;
|
|
case 'ID24Vassago':
|
|
basetag = "ID24VASSAGO";
|
|
break;
|
|
case 'ID24Tyrant':
|
|
case 'ID24TyrantBoss1':
|
|
case 'ID24TyrantBoss2':
|
|
basetag = "ID24TYRANT";
|
|
break;
|
|
case 'SWWMBossBrain':
|
|
basetag = "BOSSBRAIN";
|
|
break;
|
|
case 'WolfensteinSS':
|
|
if ( IsUltDoom2() )
|
|
{
|
|
basetag = "ELITEZOMBIE";
|
|
break;
|
|
}
|
|
// ensure it's not being replaced
|
|
if ( CheckDehackery('WolfensteinSS') ) break;
|
|
case 'SWWMSS':
|
|
basetag = "WOLFSS";
|
|
break;
|
|
case 'SWWMHangingKeen':
|
|
basetag = "KEEN";
|
|
break;
|
|
case 'MBFHelperDog':
|
|
case 'SWWMDog':
|
|
basetag = "DOG";
|
|
break;
|
|
case 'SWWMGuard':
|
|
basetag = "WOLFGUARD";
|
|
break;
|
|
case 'SWWMHans':
|
|
basetag = "WOLFHANS";
|
|
break;
|
|
// Heretic
|
|
case 'Chicken':
|
|
basetag = "CHICKEN";
|
|
break;
|
|
case 'Beast':
|
|
basetag = "BEAST";
|
|
break;
|
|
case 'Clink':
|
|
basetag = "CLINK";
|
|
break;
|
|
case 'Sorcerer1':
|
|
case 'Sorcerer2':
|
|
basetag = "DSPARIL";
|
|
break;
|
|
case 'HereticImp':
|
|
case 'HereticImpLeader':
|
|
basetag = "HERETICIMP";
|
|
break;
|
|
case 'Ironlich':
|
|
basetag = "IRONLICH";
|
|
break;
|
|
case 'Knight':
|
|
case 'KnightGhost':
|
|
basetag = "BONEKNIGHT";
|
|
break;
|
|
case 'Minotaur':
|
|
case 'MinotaurFriend':
|
|
basetag = "MINOTAUR";
|
|
break;
|
|
case 'Mummy':
|
|
case 'MummyGhost':
|
|
basetag = "MUMMY";
|
|
break;
|
|
case 'MummyLeader':
|
|
case 'MummyLeaderGhost':
|
|
basetag = "MUMMYLEADER";
|
|
break;
|
|
case 'Snake':
|
|
basetag = "SNAKE";
|
|
break;
|
|
case 'Wizard':
|
|
basetag = "WIZARD";
|
|
break;
|
|
// Hexen
|
|
case 'FireDemon':
|
|
basetag = "FIREDEMON";
|
|
break;
|
|
case 'Demon1':
|
|
case 'Demon1Mash':
|
|
case 'Demon2':
|
|
case 'Demon2Mash':
|
|
basetag = "DEMON1";
|
|
break;
|
|
case 'Ettin':
|
|
case 'EttinMash':
|
|
basetag = "ETTIN";
|
|
break;
|
|
case 'Centaur':
|
|
case 'CentaurMash':
|
|
basetag = "CENTAUR";
|
|
break;
|
|
case 'CentaurLeader':
|
|
basetag = "SLAUGHTAUR";
|
|
break;
|
|
case 'Bishop':
|
|
basetag = "BISHOP";
|
|
break;
|
|
case 'IceGuy':
|
|
basetag = "ICEGUY";
|
|
break;
|
|
case 'Serpent':
|
|
case 'SerpentLeader':
|
|
basetag = "SERPENT";
|
|
break;
|
|
case 'Wraith':
|
|
case 'WraithBuried':
|
|
basetag = "WRAITH";
|
|
break;
|
|
case 'Dragon':
|
|
basetag = "DRAGON";
|
|
break;
|
|
case 'Korax':
|
|
basetag = "KORAX";
|
|
break;
|
|
case 'FighterBoss':
|
|
basetag = "FBOSS";
|
|
break;
|
|
case 'MageBoss':
|
|
basetag = "MBOSS";
|
|
break;
|
|
case 'ClericBoss':
|
|
basetag = "CBOSS";
|
|
break;
|
|
case 'Heresiarch':
|
|
basetag = "HERESIARCH";
|
|
break;
|
|
case 'Pig':
|
|
basetag = "PIG";
|
|
break;
|
|
// eviternity
|
|
case 'ArchangelusA':
|
|
case 'ArchangelusB':
|
|
basetag = "ANGEL";
|
|
break;
|
|
case 'AstralCaco':
|
|
basetag = "ASTRAL";
|
|
break;
|
|
case 'Annihilator':
|
|
basetag = "ANNIHIL";
|
|
break;
|
|
case 'FormerCaptain':
|
|
basetag = "FCAPTAIN";
|
|
break;
|
|
case 'NightmareDemon':
|
|
basetag = "NDEMON";
|
|
break;
|
|
// eviternity 2
|
|
case 'FormerCorporal':
|
|
basetag = "FCORPORAL";
|
|
break;
|
|
case 'AstralArachnotron':
|
|
basetag = "ASTRALARACH";
|
|
break;
|
|
case 'AstralCacodemon':
|
|
basetag = "ASTRAL";
|
|
break;
|
|
case 'Veilimp':
|
|
basetag = "VEILIMP";
|
|
break;
|
|
case 'GoldenAstralCaco':
|
|
case 'GoldenAstralCacoBoss':
|
|
basetag = "ASTRALGOLD";
|
|
break;
|
|
case 'DukeOfHell':
|
|
basetag = "DUKE";
|
|
break;
|
|
case 'AstralBabyCaco':
|
|
basetag = "ASTRALBABY";
|
|
break;
|
|
case 'NightmareCacodemon':
|
|
basetag = "NAC";
|
|
break;
|
|
case 'AstralMancubus':
|
|
basetag = "ASTRALFATSO";
|
|
break;
|
|
case 'NecromenaceA':
|
|
case 'NecromenaceB':
|
|
case 'NecromenaceC':
|
|
case 'NecromenaceD':
|
|
basetag = "NECROMENACE";
|
|
break;
|
|
case 'The_Origin_Phase_1':
|
|
case 'The_Origin_Phase_2':
|
|
case 'The_Origin_Phase_3':
|
|
case 'The_Absolute_Origin_Phase_1':
|
|
case 'The_Absolute_Origin_Phase_2':
|
|
case 'The_Absolute_Origin_Phase_3':
|
|
basetag = "ORIGIN";
|
|
break;
|
|
case 'SpectralAstralCacodemon':
|
|
basetag = "SAC";
|
|
break;
|
|
case 'GrandDukeofHell':
|
|
basetag = "GDUKE";
|
|
break;
|
|
}
|
|
if ( basetag == "" ) return a.GetTag(defstr);
|
|
String funtag = "FN_"..basetag.."_FUN";
|
|
String lfuntag = StringTable.Localize(funtag,false);
|
|
if ( lfuntag != funtag ) return lfuntag;
|
|
String nfuntag = "FN_"..basetag.."_FUNN";
|
|
String lnfuntag = StringTable.Localize(nfuntag,false);
|
|
if ( lnfuntag == nfuntag ) return a.GetTag(defstr);
|
|
ntags = lnfuntag.ToInt();
|
|
return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags)));
|
|
}
|
|
|
|
// used to "substitute" a monster class for another so killcount stats are merged
|
|
// e.g.: "stealth" monsters and their non-stealth counterparts,
|
|
// or "HereticImp" and "HereticImpLeader", which have the same exact tag,
|
|
// and would result in an odd "duplication" of monster names
|
|
static play Class<Actor> MergeMonster( SWWMHandler hnd, Class<Actor> a )
|
|
{
|
|
// see if any services can resolve this first
|
|
foreach ( sv:hnd.mergemonstersv )
|
|
{
|
|
if ( !sv ) continue;
|
|
String res = sv.GetString("MergeMonster",stringArg:a.GetClassName());
|
|
if ( res == "" ) continue;
|
|
Class<Actor> rescls = res;
|
|
return rescls;
|
|
}
|
|
// special boss tyrants in LoR final map
|
|
if ( (a == 'ID24TyrantBoss1') || (a == 'ID24TyrantBoss2') ) return 'ID24Tyrant';
|
|
// stealth monsters, the worst thing ever invented
|
|
if ( a == 'StealthArachnotron' ) return 'Arachnotron';
|
|
if ( a == 'StealthArchvile' ) return 'Archvile';
|
|
if ( a == 'StealthBaron' ) return 'BaronOfHell';
|
|
if ( a == 'StealthCacodemon' ) return 'Cacodemon';
|
|
if ( a == 'StealthChaingunGuy' ) return 'ChaingunGuy';
|
|
if ( a == 'StealthDemon' ) return 'Demon';
|
|
if ( a == 'StealthHellKnight' ) return 'HellKnight';
|
|
if ( a == 'StealthDoomImp' ) return 'DoomImp';
|
|
if ( a == 'StealthFatso' ) return 'Fatso';
|
|
if ( a == 'StealthRevenant' ) return 'Revenant';
|
|
if ( a == 'StealthShotgunGuy' ) return 'ShotgunGuy';
|
|
if ( a == 'StealthZombieMan' ) return 'ZombieMan';
|
|
// eviternity 2 hackery
|
|
if ( a.GetClassName() == 'LostSoulEvit2' ) return 'LostSoul';
|
|
if ( a.GetClassName() == 'LostSoulCount' ) return 'LostSoul';
|
|
if ( a.GetClassName() == 'CyberdemonEvit2' ) return 'Cyberdemon';
|
|
if ( a.GetClassName() == 'CyberdemonMAP24' ) return 'Cyberdemon';
|
|
// heretic monsters
|
|
if ( a == 'Sorcerer2' ) return 'Sorcerer1';
|
|
if ( a == 'HereticImpLeader' ) return 'HereticImp';
|
|
if ( a == 'KnightGhost' ) return 'Knight';
|
|
if ( a == 'MummyGhost' ) return 'Mummy';
|
|
if ( a == 'MummyLeaderGhost' ) return 'MummyLeader';
|
|
// hexen monsters
|
|
if ( a == 'CentaurMash' ) return 'Centaur';
|
|
if ( a == 'Demon1Mash' ) return 'Demon1';
|
|
if ( a == 'Demon2' ) return 'Demon1';
|
|
if ( a == 'Demon2Mash' ) return 'Demon1';
|
|
if ( a == 'EttinMash' ) return 'Ettin';
|
|
if ( a == 'SerpentLeader' ) return 'Serpent';
|
|
if ( a == 'WraithBuried' ) return 'Wraith';
|
|
return a;
|
|
}
|
|
|
|
// because gendered languages
|
|
static bool SellFemaleItem( Inventory i, String loc = "SWWM_SELLEXTRA_FEM" )
|
|
{
|
|
// no gendered string alt
|
|
if ( StringTable.Localize("$"..loc) == loc )
|
|
return false;
|
|
if ( i is 'DeepImpact' ) return true;
|
|
if ( i is 'ExplodiumGun' ) return true;
|
|
if ( i is 'Wallbuster' ) return true;
|
|
if ( i is 'HeavyMahSheenGun' ) return true;
|
|
if ( i is 'Quadravol' ) return true;
|
|
if ( i is 'Sparkster' ) return true;
|
|
if ( i is 'CandyGun' ) return true;
|
|
//if ( i is 'RayKhom' ) return true;
|
|
//if ( i is 'RafanKos' ) return true;
|
|
if ( i is 'HealthNuggetItem' ) return true;
|
|
if ( i is 'ArmorNuggetItem' ) return true;
|
|
if ( i is 'WarArmor' ) return true;
|
|
if ( i is 'FuckingInvinciball' ) return true;
|
|
if ( i is 'SWWMLamp' ) return true;
|
|
if ( i is 'AngerySigil' ) return true;
|
|
return false;
|
|
}
|
|
|
|
// returns the plural tag (if available)
|
|
static string GetAmmoTag( Inventory i )
|
|
{
|
|
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..MagAmmo(i).PickupTag.."S");
|
|
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..SWWMAmmo(i).PickupTag.."S");
|
|
return i.GetTag();
|
|
}
|
|
// because of zscript weirdness with GetDefaultByType
|
|
static string GetAmmoTagClass( Class<Inventory> i )
|
|
{
|
|
if ( i is 'MagAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<MagAmmo>)(i)).PickupTag.."S");
|
|
if ( i is 'SWWMAmmo' ) return StringTable.Localize("$T_"..GetDefaultByType((Class<SWWMAmmo>)(i)).PickupTag.."S");
|
|
return GetDefaultByType(i).GetTag();
|
|
}
|
|
|
|
// IsZeroDamage() can lead to some false negatives, we have to account for that
|
|
static play bool ValidProjectile( Actor a )
|
|
{
|
|
if ( !a.bMISSILE ) return false;
|
|
if ( a is 'AirBullet' ) return true;
|
|
if ( a is 'ExplodiumMagProj' ) return true;
|
|
if ( a is 'TheBall' ) return true;
|
|
if ( a is 'EvisceratorChunk' ) return true;
|
|
if ( a is 'EvisceratorProj' ) return true;
|
|
if ( a is 'HellblazerMissile' ) return true;
|
|
if ( a is 'QuadProj' ) return true;
|
|
if ( a is 'BigBiospark' ) return true;
|
|
if ( a is 'BiosparkBall' ) return true;
|
|
if ( a is 'BiosparkCore' ) return true;
|
|
if ( a is 'CandyGunProj' ) return true;
|
|
if ( a is 'CandyMagProj' ) return true;
|
|
if ( a is 'MisterGrenade' ) return true;
|
|
if ( a is 'LoveHeart' ) return true;
|
|
if ( !a.IsZeroDamage() ) return true;
|
|
return false;
|
|
}
|
|
|
|
// Is this a beam projectile? (speed == length)
|
|
static bool IsBeamProj( Actor a )
|
|
{
|
|
if ( a is 'SaltBeam' ) return true;
|
|
if ( a is 'BiosparkBeam' ) return true;
|
|
if ( a is 'BiosparkArc' ) return true;
|
|
if ( a is 'CandyBeam' ) return true;
|
|
if ( a is 'YnykronBeam' ) return true;
|
|
if ( a is 'YnykronLightningArc' ) return true;
|
|
if ( a is 'YnykronAltBeam' ) return true;
|
|
if ( a is 'MykradvoTendril' ) return true;
|
|
if ( a is 'MisterRailBeam' ) return true;
|
|
return false;
|
|
}
|
|
// is this a YBeam type? (real pitch is pitch-90)
|
|
static bool IsYBeam( Actor a )
|
|
{
|
|
if ( a is 'MisterRailBeam' ) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool IdentifyingDog( Actor a )
|
|
{
|
|
if ( a is 'MBFHelperDog' ) return true;
|
|
if ( a is 'SWWMDog' ) return true;
|
|
if ( a.GetClassName() == 'GermanDog' ) return true; // brote dote
|
|
if ( a.GetClassName() == '64HellHound' ) return true; // brote dote 64
|
|
if ( a.GetClassName() == 'AbyssDemon2' ) return true; // CH
|
|
if ( a.GetClassName() == 'WHOLETTHEDOGSOUT' ) return true; // CH
|
|
// more dogs will be added as found
|
|
// because all dogs must be pet
|
|
return false;
|
|
}
|
|
|
|
static bool IdentifyingCaco( Actor a )
|
|
{
|
|
if ( a is 'DeadCacodemon' ) return false;
|
|
if ( a is 'Cacodemon' ) return true;
|
|
if ( a.Species == 'RLCacodemon' ) return true; // DRLA
|
|
if ( a.Species == 'Caco' ) return true; // CH and others
|
|
if ( a.Species == 'Cacodemon' ) return true; // Beautiful Doom
|
|
if ( a.GetClassName() == 'AstralCaco' ) return true; // Eviternity
|
|
if ( a.GetParentClass().GetClassName() == 'LEG_BaseCaco' ) return true; // LEGION
|
|
return false;
|
|
}
|
|
|
|
// Друг
|
|
static bool IdentifyingDrug( Actor a )
|
|
{
|
|
if ( a is 'Beast' ) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool IdentifyingDoubleBoi( Actor a )
|
|
{
|
|
if ( a is 'Ettin' ) return true;
|
|
return false;
|
|
}
|
|
|
|
static bool IsVipItem( Actor target )
|
|
{
|
|
if ( (target is 'Chancebox') && (target.CurState==target.SpawnState) )
|
|
return true;
|
|
if ( target is 'SWWMCollectible' )
|
|
return true;
|
|
if ( (target is 'Ynykron')/* || (target is 'RafanKos')*/ )
|
|
return true;
|
|
if ( (target is 'GoldShell') || (target is 'YnykronAmmo')/* || (target is 'UltimatePod') || (target is 'UltimateAmmo')*/ )
|
|
return true;
|
|
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
|
|
return true;
|
|
if ( target is 'PuzzleItem' )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// used by the store
|
|
static bool IsVipItemClass( Class<Actor> target )
|
|
{
|
|
if ( (target is 'Ynykron')/* || (target is 'RafanKos')*/ )
|
|
return true;
|
|
if ( (target is 'GoldShell') || (target is 'YnykronAmmo')/* || (target is 'UltimatePod') || (target is 'UltimateAmmo')*/ )
|
|
return true;
|
|
if ( (target is 'Mykradvo') || (target is 'AngerySigil') || (target is 'DivineSprite') )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static bool IsScoreItem( Actor target )
|
|
{
|
|
if ( target is 'Key' )
|
|
return true;
|
|
if ( target is 'HammerspaceEmbiggener' )
|
|
return true;
|
|
return target.bCOUNTITEM;
|
|
}
|
|
|
|
// return the highest parent class in hierarchy before a specific "highest class"
|
|
// useful to check stuff such as monster subtypes and the like
|
|
static Class<Object> GetParentClassBefore( Class<Object> baseclass, Class<Object> highestclass )
|
|
{
|
|
Class<Object> step = baseclass;
|
|
while ( (step.GetParentClass() != highestclass) && step.GetParentClass() )
|
|
step = step.GetParentClass();
|
|
return step;
|
|
}
|
|
}
|