swwmgz_m/zscript/handler/swwm_handler.zsc
Marisa the Magician cf17e889d8 Use InterfaceEvents instead of PostUiTick for oneliners, more reliable.
Due to some arbitrary change in GZDoom, which was later merged into VKDoom as
well, PostUiTick no longer consistently gets called after WorldTick.
This is kind of funny and also disappointing and sad because I intentionally
designed that feature this way, on purpose, and now it no longer works as
intended. Who knows how many other mods are affected by this now.
2025-08-21 01:31:55 +02:00

363 lines
11 KiB
Text

// Handler responsible for item replacements and whatever else
// most of the code is split up to make it easier to navigate
Class SWWMHandler : EventHandler
{
Mixin SWWMUIRandom;
transient int lastlock;
transient int lastpickuptic[MAXPLAYERS]; // these two are mostly used
transient int lastnuggettic[MAXPLAYERS]; // to avoid deafening players
SWWMScoreObj scorenums;
SWWMDamNum damnums;
SWWMInterest intpoints;
Array<String> damtypes, damcolors;
transient int slotstrictwarn;
transient ui String sswstr;
transient ui BrokenLines sswl;
// stuff to reduce worldthingspawned overhead
int bossmap;
int iwantdie;
int indoomvacation;
int inultdoom2;
Array<Service> funtagsv, mergemonstersv;
// for checkreplacement
bool hasdrlamonsters, haslegionofbones;
int iskdizd;
Array<String> bludtypes;
// mod inter-compat stuff
bool ccloaded;
// session globals
SWWMGlobals gdat;
// profiling data
bool profiling;
int bprofiletics, profiletics; // how many tics to aggregate data for
double prof_ms[8], prof_avg[8];
int prof_calls[8];
double curms;
// to avoid some overlaps
ui DSMapTitle mapmsg;
// corruption cards stuff
ui bool incardmenu, cardmessaged;
// ring buffer for player path tracing in minimap
const MAX_TRACED_BUFSZ = 8192;
transient ui Vector2 traced_steps[MAX_TRACED_BUFSZ];
transient ui int traced_steppos, traced_stepnum;
transient ui Vector2 oldplayerpos;
transient ui bool do_trace_steps;
enum EProfileTimer
{
PT_WORLDTICK,
PT_WORLDTHINGSPAWNED,
PT_WORLDTHINGDESTROYED,
PT_WORLDTHINGDIED,
PT_WORLDTHINGDAMAGED,
PT_WORLDTHINGREVIVED,
PT_CHECKREPLACEMENT,
PT_CHECKREPLACEE
}
private void ProfileTick()
{
curms = MSTimeF();
}
private void ProfileTock( int idx )
{
double diff = (MSTimeF()-curms);
prof_ms[idx] += diff;
prof_avg[idx] = (prof_calls[idx]>0)?(prof_avg[idx]+diff)/2.:diff;
prof_calls[idx]++;
}
static play void ToggleStore( bool val )
{
let hnd = SWWMHandler(EventHandler.Find('SWWMHandler'));
if ( !hnd || !hnd.gdat ) return; // shouldn't happen, but doesn't hurt to check
hnd.gdat.disablestore = !val;
}
static play void ToggleRevive( bool val )
{
let hnd = SWWMHandler(EventHandler.Find('SWWMHandler'));
if ( !hnd || !hnd.gdat ) return; // shouldn't happen, but doesn't hurt to check
hnd.gdat.disablerevive = !val;
}
override void OnRegister()
{
// oneliner RNG must be relative to consoleplayer
SetRandomSeed[DemoLines](Random[DemoLines]()+consoleplayer+MSTime());
// "uninitialize" some vars
iwantdie = -1;
bossmap = -1;
indoomvacation = -1;
inultdoom2 = -1;
// class-checking ones can be initialized here easily
if ( FindClass('RLMonster','Actor') ) hasdrlamonsters = true;
if ( FindClass('LOBZombieman','Actor') ) haslegionofbones = true;
if ( FindClass('CCards_Global','Thinker') ) ccloaded = true;
if ( LevelInfo.MapExists("Z1M1") && (LevelInfo.MapChecksum("Z1M1") ~== "2B7744234ED2C162AD08A3255E979F65") )
iskdizd = true;
// read bludtype files if they can be found
for ( int lmp = Wads.FindLump("BLUDTYPE"); lmp != -1; lmp = Wads.FindLump("BLUDTYPE",lmp+1) )
{
String dat = Wads.ReadLump(lmp);
Array<String> list;
// Windows pls
dat.Replace("\r","");
list.Clear();
dat.Split(list,"\n");
foreach ( l:list )
{
if ( (l.Length() == 0) || (l.Left(2) == "//") || (l.Left(1) == "") )
continue;
bludtypes.Push(l);
}
}
// read damnum colors
damtypes.Clear();
damcolors.Clear();
for ( int lmp = Wads.FindLump("DAMTYPES"); lmp != -1; lmp = Wads.FindLump("DAMTYPES",lmp+1) )
{
String dat = Wads.ReadLump(lmp);
Array<String> list;
// 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;
int spc = l.IndexOf(" ");
damtypes.Push(l.Left(spc));
damcolors.Push(l.Mid(spc+1));
}
}
// cache various services into the handler on register
// this dramatically reduces overhead by not having to use an iterator every time they're needed
// especially noticeable for fun tags, as they're looked up for every monster on map load
let si = ServiceIterator.Find('FunTagService');
Service sv;
while ( sv = si.Next() ) funtagsv.Push(sv);
si = ServiceIterator.Find('MergeMonsterService');
while ( sv = si.Next() ) mergemonstersv.Push(sv);
// start profiling
if ( swwm_profstart <= 0 ) return;
bprofiletics = profiletics = swwm_profstart;
profiling = true;
for ( int i=0; i<8; i++ )
{
prof_ms[i] = 0;
prof_avg[i] = 0;
prof_calls[i] = 0;
}
Console.Printf("Gathering data for %d tic%s...",bprofiletics,(bprofiletics>1)?"s":"");
}
override void WorldTick()
{
if ( profiling ) ProfileTick();
LangRefresh();
QueueMaintenance();
if ( !mnotify && (level.maptime >= 5) )
{
mnotify = true;
let ti = ThinkerIterator.Create('SWWMStats',Thinker.STAT_STATIC);
SWWMStats s;
while ( s = SWWMStats(ti.Next()) )
{
if ( !SWWMUtility.IsKnownMap() ) break;
if ( s.myplayer != players[consoleplayer] ) continue;
int clust = level.cluster;
if ( SWWMUtility.IsEviternityTwo() )
{
// clusters have to be remapped here
if ( clust == 5 ) clust = 1;
else if ( (clust == 6) || (clust == 13) ) clust = 2;
else if ( (clust == 7) || (clust == 14) ) clust = 3;
else if ( (clust == 8) || (clust == 15) ) clust = 4;
else if ( (clust == 9) || (clust == 16) ) clust = 5;
else if ( (clust == 10) || (clust == 17) ) clust = 6;
else if ( (clust == 11) || (clust == 12) || (clust == 18) || (clust == 19) ) clust = 7;
}
else if ( SWWMUtility.IsEviternity() )
{
// we have to do some heavy lifting here because episodes don't match clusters
if ( level.levelnum <= 5 ) clust = 1;
else if ( level.levelnum <= 10 ) clust = 2;
else if ( level.levelnum <= 15 ) clust = 3;
else if ( level.levelnum <= 20 ) clust = 4;
else if ( level.levelnum <= 25 ) clust = 5;
else if ( level.levelnum <= 30 ) clust = 6;
else if ( level.levelnum <= 31 ) clust = 7;
else if ( level.levelnum <= 32 ) clust = 8;
}
int csiz = s.clustervisit.Size();
if ( (csiz > 0) && (s.clustervisit[csiz-1] != clust) )
Console.Printf(StringTable.Localize("$SWWM_NEWMISSION"));
}
}
for ( int i=0; i<legtrack.Size(); i++ )
{
// when a monster mutates, update its healthbar for all players
for ( int j=0; j<MAXPLAYERS; j++ )
{
if ( !playeringame[j] ) continue;
let t = SWWMQuickCombatTracker.Update(self,players[j],legtrack[i].Owner);
if ( t && (t.myplayer == players[consoleplayer]) ) Console.Printf(StringTable.Localize("$SWWM_LTFORM"),t.mytag);
}
legtrack.Delete(i--);
}
// healthbar on whatever the player is aiming at
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || (players[i].Health <= 0) || !players[i].mo ) continue;
let t = players[i].mo.GetPointer(AAPTR_PLAYER_GETTARGET);
if ( t && t.bSHOOTABLE && (t.Health > 0) ) SWWMQuickCombatTracker.Update(self,players[i],t);
// keep healthbars updated for all friends of this player
for ( int j=0; j<MAXPLAYERS; j++ )
{
if ( !playeringame[j] || (players[j].Health <= 0) || !players[j].mo || !players[j].mo.IsFriend(players[i].mo) ) continue;
SWWMQuickCombatTracker.Update(self,players[j],players[i].mo);
}
}
OnelinerTick();
ItemCountTrack();
CombatTrack();
OneHundredPercentCheck();
UpdateHUDObjects();
SimpleTracking();
VanillaBossTick();
if ( !profiling ) return;
ProfileTock(PT_WorldTick);
Console.Printf("%d...",profiletics);
profiletics--;
if ( profiletics > 0 ) return;
profiling = false;
static const String prof_name[] =
{
"WorldTick ",
"WorldThingSpawned ",
"WorldThingDestroyed",
"WorldThingDied ",
"WorldThingDamaged ",
"WorldThingRevived ",
"CheckReplacement ",
"CheckReplacee "
};
Console.Printf("Done!");
String str = String.Format(
"SWWMHandler profiling info for %d tic%s:\n"
" event name | calls | total ms | avg ms\n"
"---------------------|--------|-------------|-------------\n",
bprofiletics,(bprofiletics>1)?"s":"");
for ( int i=0; i<8; i++ )
str.AppendFormat(" %s | %6d | %11.6f | %11.6f\n",prof_name[i],prof_calls[i],prof_ms[i],prof_avg[i]);
Console.PrintfEx(PRINT_HIGH|PRINT_NONOTIFY,str);
}
override void PostUiTick()
{
VanillaBossUITick();
// corruption cards dialogue
if ( ccloaded && !gdat.ccstartonce && !cardmessaged && (gamestate == GS_LEVEL) )
{
let m = Menu.GetCurrentMenu();
if ( m && (m.GetClassName() == 'CorruptionCardsSelector') ) incardmenu = true;
else if ( incardmenu )
{
if ( !swwm_ccmessage ) SWWMDialogues.StartSeq("CC");
CVar.GetCVar('swwm_ccmessage').SetBool(true);
cardmessaged = true;
SendNetworkEvent("swwmccstart");
}
}
}
override void WorldLinePreActivated( WorldEvent e )
{
// oneliner on locked doors
if ( !e.Thing ) return;
int locknum = SWWMUtility.GetLineLock(e.ActivatedLine);
if ( (locknum < 1) || (locknum > 255) ) return;
if ( e.Thing.CheckLocalView() && !e.Thing.CheckKeys(locknum,false,true) )
{
if ( !lastlock || (gametic > lastlock+20) )
{
if ( Key.IsLockDefined(locknum) )
lastlock = AddOneliner("locked",2);
else lastlock = AddOneliner("jammed",2);
}
}
}
override void WorldLineActivated( WorldEvent e )
{
if ( !(e.ActivationType&SPAC_Use) ) return;
if ( !e.Thing || !e.Thing.player ) return;
if ( (e.Thing.player == players[consoleplayer]) && swwm_beepboop )
SWWMHandler.AddOneliner("buttonpush",2,0);
let w = SWWMWeapon(e.Thing.player.ReadyWeapon);
if ( (!w || !w.wallponch) && (!(e.Thing is 'Demolitionist') || !Demolitionist(e.Thing).hitactivate) ) return;
let s = SWWMStats.Find(e.Thing.player);
if ( s ) s.wponch++;
SWWMUtility.AchievementProgressInc("slemg",1,e.Thing.player);
}
// stuff for hud
override void RenderUnderlay( RenderEvent e )
{
// armor/health flashes
FlashRender(e);
if ( slotstrictwarn && (gametic < slotstrictwarn) )
{
String str = StringTable.Localize("$SWWM_SETSLOTSTRICT");
if ( sswstr != str )
{
sswstr = str;
if ( sswl ) sswl.Destroy();
}
double t = (slotstrictwarn-(gametic+e.FracTic))/20.;
double alph = clamp(t,0.,1.);
if ( !sswl ) sswl = newsmallfont.BreakLines(sswstr,300);
double yy = (200-sswl.Count()*newsmallfont.GetHeight())/2;
for ( int i=0; i<sswl.Count(); i++ )
{
double xx = (320-sswl.StringWidth(i))/2;
Screen.DrawText(newsmallfont,Font.CR_UNTRANSLATED,xx,yy,sswl.StringAt(i),DTA_Clean,true,DTA_Alpha,alph);
yy += newsmallfont.GetHeight();
}
sswl.Destroy();
}
// weapon underlays
let sw = SWWMWeapon(players[consoleplayer].ReadyWeapon);
if ( sw )
{
sw.RenderUnderlay(e);
if ( sw.bHASSCRTEX ) sw.RenderTexture(e);
}
TraceCrosshairs(e);
RenderCrosshairs(e);
if ( !statusbar || !(statusbar is 'SWWMStatusBar') ) return;
SWWMStatusBar(statusbar).viewpos = e.viewpos;
SWWMStatusBar(statusbar).viewrot = (e.viewangle,e.viewpitch,e.viewroll);
}
// various shaders
override void RenderOverlay( RenderEvent e )
{
CheatOverlay(e);
RenderShaders(e);
DrawDebug(e);
}
}