Implement minimap (like radar from SWWM Z but better).

Make item sense detect Chanceboxes too.
Cache LOCKDEFS parsing.
This commit is contained in:
Mari the Deer 2021-02-21 02:00:43 +01:00
commit 9b6c7b0d81
12 changed files with 656 additions and 55 deletions

View file

@ -1,7 +1,7 @@
## Some Weird Weapons Mod ~ GZDoom Edition
![](docimg/logo.png)
**SWWM GZ** brings to **GZDoom** a "best of" collection of custom weapons I've made for **Unreal Tournament**, plus many new things that didn't make the cut there.
**SWWM GZ** brings to **GZDoom** a "best of" collection of custom weapons I've made for **Unreal Tournament**, plus many new things that didn't make the cut there. It is also effectively a reboot of the entire **SWWM** series, which unfortunately ended with the cancelled **SWWM Z**, this mod's direct predecessor.
It contains weapons and items remastered and revived from old projects such as the previous **SWWM** entries, along with the **Zanaveth Ultra Suite** side project, and also notably, the main **UnSX** series that never truly saw the light of day, as all work done on it so far has been lost forever. There may also be some original things here and there just to spice things up.
@ -491,6 +491,8 @@ In **Doom** and **Heretic**, collected keys will be displayed below the score bo
When the **Automap** is open, the map name and stats will also be shown here.
Optionally, a minimap can be shown below the score box too. This works mostly like the radar did in **SWWM Z**, but it has the added benefit of also showing map geometry. Colors are taken from GZDoom's own custom automap settings.
### Bottom left corner
Your health and fuel, along with an inventory box, and all active armors and powerups (with their respective durability/duration).

View file

@ -100,6 +100,7 @@ user int swwm_numcolor_dmg = 6; // font color for damage numbers (default: red
user int swwm_numcolor_hp = 7; // font color for health numbers (default: blue)
user int swwm_numcolor_ap = 3; // font color for armor numbers (default: green)
server int swwm_drlaskill = 3; // [DRLA Monsters] skill setting for monster spawns
user bool swwm_showminimap = false; // show a minimap below the score counter
server noarchive bool swwm_iseriouslywanttoplaythiswithbd = false; // self-explanatory

BIN
graphics/HUD/MinimapBox.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

View file

@ -205,6 +205,7 @@ SWWM_DRLASKILL_NIGHTMARE = "Nightmare";
SWWM_DRLASKILL_TECHNOPHOBIA = "Technophobia";
SWWM_DRLASKILL_ARMAGEDDON = "Armageddon";
SWWM_DRLASKILL_ADAPTIVE = "Adaptive";
SWWM_SHOWMINIMAP = "Show Minimap";
TOOLTIP_SWWM_VOICETYPE = "Sets the voice pack for the player.";
TOOLTIP_SWWM_MUTEVOICE = "Control what gets muted, if you'd rather have a more silent protagonist.";
TOOLTIP_SWWM_FLASHSTRENGTH = "Screen flashes usually happen when firing some weapons, you can lower this if these effects are harmful for you.";
@ -295,6 +296,7 @@ TOOLTIP_SWWM_NUMCOLOR_DMG = "Select the color for damage numbers.";
TOOLTIP_SWWM_NUMCOLOR_HP = "Select the color for health numbers.";
TOOLTIP_SWWM_NUMCOLOR_AP = "Select the color for armor numbers.";
TOOLTIP_SWWM_DRLASKILL = "[DRLA Monsters] Sets the skill level for enemy spawns.";
TOOLTIP_SWWM_SHOWMINIMAP = "Displays a minimap under the score counter.";
// knowledge base
SWWM_COMINGSOON = "(coming soon)";
SWWM_MISSTAB = "Mission";

View file

@ -202,6 +202,7 @@ SWWM_DRLASKILL_NIGHTMARE = "Pesadilla";
SWWM_DRLASKILL_TECHNOPHOBIA = "Tecnofobia";
SWWM_DRLASKILL_ARMAGEDDON = "Armagedón";
SWWM_DRLASKILL_ADAPTIVE = "Adaptado";
SWWM_SHOWMINIMAP = "Mostrar Minimapa";
TOOLTIP_SWWM_VOICETYPE = "Selecciona el pack de voz para el jugador.";
TOOLTIP_SWWM_MUTEVOICE = "Controla lo que se mutea, si prefieres tener un protagonista más silencioso.";
TOOLTIP_SWWM_FLASHSTRENGTH = "Los destellos en pantalla suelen ocurrir al disparar algunas armas, puedes reducirlo si este tipo de efectos te causan malestar.";
@ -292,6 +293,7 @@ TOOLTIP_SWWM_NUMCOLOR_DMG = "Selecciona el color para los números de daño.";
TOOLTIP_SWWM_NUMCOLOR_HP = "Selecciona el color para los números de salud.";
TOOLTIP_SWWM_NUMCOLOR_AP = "Selecciona el color para los números de armadura.";
TOOLTIP_SWWM_DRLASKILL = "[DRLA Monsters] Elige el nivel de dificultad para spawns de enemigos.";
TOOLTIP_SWWM_SHOWMINIMAP = "Muestra un minimapa bajo el contador de puntuación.";
// knowledge base
SWWM_COMINGSOON = "(próximamente)";
SWWM_MISSTAB = "Misión";

View file

@ -1,3 +1,3 @@
[default]
SWWM_MODVER="\chSWWM \czGZ\c- \cw0.9.11b-pre r290 \cu(Fri 19 Feb 14:20:14 CET 2021)\c-";
SWWM_SHORTVER="\cw0.9.11b-pre r290 \cu(2021-02-19 14:20:14)\c-";
SWWM_MODVER="\chSWWM \czGZ\c- \cw0.9.11b-pre r291 \cu(Sun 21 Feb 02:00:43 CET 2021)\c-";
SWWM_SHORTVER="\cw0.9.11b-pre r291 \cu(2021-02-21 02:00:43)\c-";

View file

@ -99,6 +99,7 @@ OptionMenu "SWWMOptionMenu"
Slider "$SWWM_CHATLEN", "swwm_chatduration", 1, 30, 1, 0
Slider "$SWWM_MSGLEN", "swwm_msgduration", 1, 30, 1, 0
Slider "$SWWM_PICKLEN", "swwm_pickduration", 1, 30, 1, 0
Option "$SWWM_SHOWMINIMAP", "swwm_showminimap", "YesNo"
Option "$SWWM_TARGET", "swwm_targeter", "YesNo"
Option "$SWWM_TARGETTAG", "swwm_targettags", "YesNo"
Option "$SWWM_DAMAGETARGET", "swwm_damagetarget", "SWWMDamageTarget"

View file

@ -226,7 +226,8 @@ Class SWWMHandler : EventHandler
SWWMCombatTracker trackers;
SWWMScoreObj scorenums, damnums;
SWWMInterest intpoints;
int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt;
SWWMSimpleTracker strackers;
int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt, strackers_cnt;
bool tookdamage[MAXPLAYERS];
int spreecount[MAXPLAYERS];
int lastkill[MAXPLAYERS];
@ -306,6 +307,10 @@ Class SWWMHandler : EventHandler
bool hasdrlamonsters;
// for minimap
Array<int> ffsectors;
transient CVar showmini;
enum EVanillaMap
{
MAP_NONE,
@ -828,6 +833,70 @@ Class SWWMHandler : EventHandler
}
}
private void SetupLockdefsCache( SWWMCachedLockInfo cli )
{
for ( int i=0; i<Wads.GetNumLumps(); i++ )
{
String lname = Wads.GetLumpName(i);
if ( !(lname ~== "LOCKDEFS") ) continue;
String data = Wads.ReadLump(i);
Array<String> lines;
lines.Clear();
data.Split(lines,"\n");
bool valid = false;
for ( int j=0; j<lines.Size(); j++ )
{
// strip leading whitespace
while ( (lines[j].Left(1) == " ") || (lines[j].Left(1) == "\t") )
lines[j] = lines[j].Mid(1);
if ( lines[j].Left(10) ~== "CLEARLOCKS" )
{
for ( int k=0; k<cli.ent.Size(); k++ )
cli.ent[k].Destroy();
cli.ent.Clear();
}
else if ( Lines[j].Left(5) ~== "LOCK " )
{
Array<String> spl;
spl.Clear();
lines[j].Split(spl," ",TOK_SKIPEMPTY);
// check game string (if any)
if ( spl.Size() > 2 )
{
if ( (spl[2] ~== "DOOM") && !(gameinfo.gametype&GAME_Doom) ) continue;
else if ( (spl[2] ~== "HERETIC") && !(gameinfo.gametype&GAME_Heretic) ) continue;
else if ( (spl[2] ~== "HEXEN") && !(gameinfo.gametype&GAME_Hexen) ) continue;
else if ( (spl[2] ~== "STRIFE") && !(gameinfo.gametype&GAME_Strife) ) continue;
else if ( (spl[2] ~== "CHEX") && !(gameinfo.gametype&GAME_Chex) ) continue;
}
// valid lock, prepare it
let li = new("LIEntry");
li.locknumber = spl[1].ToInt();
li.hascolor = false;
// see if there's a Mapcolor defined
int k = j+1;
for ( int k=j+2; k<lines.Size(); k++ )
{
// strip leading whitespace
while ( (lines[k].Left(1) == " ") || (lines[k].Left(1) == "\t") )
lines[k] = lines[k].Mid(1);
if ( lines[k].Left(5) ~== "LOCK " )
break; // we reached the next lock
if ( !(lines[k].Left(9) ~== "MAPCOLOR ") )
continue;
// here it is
spl.Clear();
lines[k].Split(spl," ",TOK_SKIPEMPTY);
if ( spl.Size() < 4 ) break;
li.hascolor = true;
li.mapcolor = Color(spl[1].ToInt(),spl[2].ToInt(),spl[3].ToInt());
}
cli.ent.Push(li);
}
}
}
}
override void WorldLoaded( WorldEvent e )
{
if ( level.levelname ~== "Modder Test Map" )
@ -846,6 +915,27 @@ Class SWWMHandler : EventHandler
if ( (level.GetChecksum() ~== "F286BABF0D152259CD6B996E8920CA70")
|| (level.GetChecksum() ~== "A52BD2038CF814101AAB7D9C78F9ACE2") )
level.ExecuteSpecial(ACS_Execute,null,null,false,-Int('DVACATION_UNFUCK'));
// setup cached lockdefs data
let cli = SWWMCachedLockInfo.GetInstance();
if ( cli.ent.Size() == 0 ) SetupLockdefsCache(cli);
// keep a list of sectors containing 3D floors, for use by the minimap
ffsectors.Clear();
for ( int i=0; i<level.sectors.Size(); i++ )
{
Sector s = level.sectors[i];
if ( !s.Get3DFloorCount() ) continue;
int realcount = 0;
for ( int j=0; j<s.Get3DFloorCount(); j++ )
{
F3DFloor rover = s.Get3DFloor(j);
if ( rover.flags&F3DFloor.FF_THISINSIDE ) continue;
if ( !(rover.flags&F3DFloor.FF_EXISTS) ) continue;
if ( rover.alpha == 0 ) continue;
realcount++;
}
if ( !realcount ) continue;
ffsectors.Push(s.Index());
}
// for skipping over merged exit lines (sharing vertices)
Array<Line> skipme;
skipme.Clear();
@ -1321,6 +1411,81 @@ Class SWWMHandler : EventHandler
mapclearagain++;
}
// "simple" tracking (used by the minimap)
private void SimpleTracking()
{
if ( !showmini ) showmini = CVar.GetCVar('swwm_showminimap',players[consoleplayer]);
if ( !showmini.GetBool() )
{
while ( strackers )
{
SWWMSimpleTracker next = strackers.next;
strackers.Destroy();
strackers = next;
}
strackers_cnt = 0;
return;
}
// update trackers for anything around the player
bool thesight = players[consoleplayer].mo.FindInventory("Omnisight");
BlockThingsIterator bt = BlockThingsIterator.Create(players[consoleplayer].Camera,SWWMStatusBar.MAPVIEWDIST);
while ( bt.Next() )
{
let a = bt.Thing;
if ( !a ) continue;
Vector2 rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > SWWMStatusBar.MAPVIEWDIST )
continue;
if ( a == players[consoleplayer].Camera )
continue;
if ( !a.player && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') )
continue;
if ( !thesight && !a.IsFriend(players[consoleplayer].mo) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
if ( a.bKILLED )
continue;
if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner) )
continue;
if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) )
{
// "last breath" update
for ( SWWMSimpleTracker t=strackers; t; t=t.next )
{
if ( t.target != a ) continue;
if ( !t.expired ) t.Update();
}
continue;
}
SWWMSimpleTracker.Track(a);
}
// prune expired trackers
SWWMSimpleTracker trk = strackers;
while ( trk )
{
SWWMSimpleTracker next = trk.next;
// minimize lifespan of destroyed targets
if ( !trk.target )
trk.lastupdate = min(trk.lastupdate,level.maptime);
else if ( !trk.expired )
{
// "last breath" update
if ( trk.target.bKILLED
|| ((trk.target is 'Inventory') && (!trk.target.bSPECIAL || Inventory(trk.target).Owner))
|| ((trk.target is 'Chancebox') && (trk.target.CurState != trk.target.SpawnState)) )
trk.Update();
}
if ( trk.lastupdate+140 < level.maptime )
{
if ( !trk.prev ) strackers = trk.next;
else trk.prev.next = trk.next;
if ( trk.next ) trk.next.prev = trk.prev;
trk.Destroy();
strackers_cnt--;
}
trk = next;
}
}
override void WorldTick()
{
LangRefresh();
@ -1373,6 +1538,7 @@ Class SWWMHandler : EventHandler
ItemCountTrack();
CombatTrack();
OneHundredPercentCheck();
SimpleTracking();
if ( initialized ) return;
// wait until bosses are active
for ( int i=0; i<bossactors.Size(); i++ )

View file

@ -9,7 +9,8 @@ Class MsgLine
Class SWWMStatusBar : BaseStatusBar
{
TextureID StatusTex, WeaponTex, ScoreTex[3], InventoryTex, ChatTex[6],
HealthTex[5], FuelTex[2], DashTex, EnemyBTex, EnemyHTex[5], GenericAmmoTex[3];
HealthTex[5], FuelTex[2], DashTex, EnemyBTex, EnemyHTex[5],
GenericAmmoTex[3], MiniBox;
HUDFont mTewiFont, mMiniwiFont, mMPlusFont, mk6x8Font;
// "Full History" contains all messages since session start, nothing is flushed
@ -26,7 +27,7 @@ Class SWWMStatusBar : BaseStatusBar
SWWMHandler hnd;
// client cvars
transient CVar safezone, maxchat[2], maxpick, chatduration, msgduration, pickduration, chatcol, teamcol, obitcol, critcol, pickcol, targetter, healthnums, scorenums, scorebonus, pois, targettag, lang, maxtarg, maxscore, maxdamns, hscale, bscale, nscale, sscale, iscale, dscale, midcol, midbcol, midduration, bigtags, showitems, showmaplabel, showmonsters, showsecrets, showtime, showtotaltime, legspoil, camhud, forcestats;
transient CVar safezone, maxchat[2], maxpick, chatduration, msgduration, pickduration, chatcol, teamcol, obitcol, critcol, pickcol, targetter, healthnums, scorenums, scorebonus, pois, targettag, lang, maxtarg, maxscore, maxdamns, hscale, bscale, nscale, sscale, iscale, dscale, midcol, midbcol, midduration, bigtags, showitems, showmaplabel, showmonsters, showsecrets, showtime, showtotaltime, legspoil, camhud, forcestats, showmini;
// shared stuff
Vector2 ss, hs;
@ -64,6 +65,11 @@ Class SWWMStatusBar : BaseStatusBar
bool koraxhack;
// minimap constants
const CLIPDIST = 800; // clip distance for minimap view, with rotation accounted
const MAPVIEWDIST = 1132; // maximum distance for something to be considered visible (rounded up CLIPDIST*sqrt(2))
const HALFMAPSIZE = 40; // half the size of the minimap draw region (unscaled)
// if playing in Japanese, returns an alternate font of the same height
// Tewi -> MPlus
// Miniwi -> k6x8
@ -542,9 +548,8 @@ Class SWWMStatusBar : BaseStatusBar
{
if ( senseitems.Size() != demo.itemsense_cnt )
senseitems.Resize(demo.itemsense_cnt);
total_sz = demo.itemsense_cnt;
i = 0;
for ( SWWMItemSense s=demo.itemsense; s && (i<total_sz); s=s.next )
for ( SWWMItemSense s=demo.itemsense; s; s=s.next )
{
// ignore points clearly outside of player view
Vector3 tdir = level.Vec3Diff(viewpos,s.pos);
@ -556,7 +561,7 @@ Class SWWMStatusBar : BaseStatusBar
senseitems[i++] = s;
}
// squeeze if some were discarded
if ( i != total_sz )
if ( i != demo.itemsense_cnt )
senseitems.Resize(i);
// sort by distance
qsort_itemsense(senseitems,0,senseitems.Size()-1);
@ -633,6 +638,7 @@ Class SWWMStatusBar : BaseStatusBar
GenericAmmoTex[0] = TexMan.CheckForTexture("graphics/HUD/GenericAmmoBoxL.png",TexMan.Type_Any);
GenericAmmoTex[1] = TexMan.CheckForTexture("graphics/HUD/GenericAmmoBoxM.png",TexMan.Type_Any);
GenericAmmoTex[2] = TexMan.CheckForTexture("graphics/HUD/GenericAmmoBoxR.png",TexMan.Type_Any);
MiniBox = TexMan.CheckForTexture("graphics/HUD/MinimapBox.png",TexMan.Type_Any);
mTewiFont = HUDFont.Create("TewiShaded");
mMiniwiFont = HUDFont.Create("MiniwiShaded");
mMPlusFont = HUDFont.Create("MPlusShaded");
@ -715,7 +721,7 @@ Class SWWMStatusBar : BaseStatusBar
double alph = clamp(((s.updated+mtime)-level.maptime)/35.,0.,1.);
alph *= clamp(1.5-1.5*(tdir.length()/(thesight?1200.:800.)),0.,1.);
tag = s.tag;
Screen.DrawText(fnt,s.scoreitem?Font.CR_GOLD:Font.CR_GREEN,(vpos.x-hsd.x*fnt.StringWidth(tag)/2.)/hsd.x,(vpos.y-hsd.y*fnt.GetHeight()/2.)/hsd.y,tag,DTA_VirtualWidthF,ssd.x,DTA_VirtualHeightF,ssd.y,DTA_KeepRatio,true,DTA_Alpha,alph);
Screen.DrawText(fnt,s.vipitem?Font.CR_PURPLE:s.scoreitem?Font.CR_GOLD:Font.CR_GREEN,(vpos.x-hsd.x*fnt.StringWidth(tag)/2.)/hsd.x,(vpos.y-hsd.y*fnt.GetHeight()/2.)/hsd.y,tag,DTA_VirtualWidthF,ssd.x,DTA_VirtualHeightF,ssd.y,DTA_KeepRatio,true,DTA_Alpha,alph);
tag = String.Format("\cu(%s\cu)\c-",FormatDist(tdir.length()));
Screen.DrawText(fnt,Font.CR_WHITE,(vpos.x-hsd.x*fnt.StringWidth(tag)/2.)/hsd.x,(vpos.y+hsd.y*fnt.GetHeight()/2.)/hsd.y,tag,DTA_VirtualWidthF,ssd.x,DTA_VirtualHeightF,ssd.y,DTA_KeepRatio,true,DTA_Alpha,alph);
}
@ -850,6 +856,238 @@ Class SWWMStatusBar : BaseStatusBar
// do nothing, DrawScore handles this
}
// minimap helper code
private bool ShowTriggerLine( Line l )
{
if ( am_showtriggerlines == 0 ) return false;
if ( am_showtriggerlines >= 2 ) return true;
if ( (l.special == Door_Open)
|| (l.special == Door_Close)
|| (l.special == Door_CloseWaitOpen)
|| (l.special == Door_Raise)
|| (l.special == Door_Animated)
|| (l.special == Generic_Door) )
return false;
return true;
}
private bool CmpFloorPlanes( Line l )
{
return (l.frontsector.floorplane.Normal == l.backsector.floorplane.Normal)
&& (l.frontsector.floorplane.D == l.backsector.floorplane.D);
}
private bool CmpCeilingPlanes( Line l )
{
return (l.frontsector.ceilingplane.Normal == l.backsector.ceilingplane.Normal)
&& (l.frontsector.ceilingplane.D == l.backsector.ceilingplane.D);
}
private int CheckSecret( Line l )
{
if ( l.frontsector && (l.frontsector.flags&Sector.SECF_WASSECRET) )
{
if ( am_map_secrets && !(l.frontsector.flags&Sector.SECF_SECRET) ) return 1;
if ( (am_map_secrets == 2) && !(l.flags&Line.ML_SECRET) ) return 2;
}
if ( l.backsector && (l.backsector.flags&Sector.SECF_WASSECRET) )
{
if ( am_map_secrets && !(l.backsector.flags&Sector.SECF_SECRET) ) return 1;
if ( (am_map_secrets == 2) && !(l.flags&Line.ML_SECRET) ) return 2;
}
return 0;
}
private bool CheckFFBoundary( Line l )
{
if ( !hnd || !hnd.ffsectors.Size() ) return false;
int frontidx = hnd.ffsectors.Find(l.frontsector.Index());
int backidx = hnd.ffsectors.Find(l.backsector.Index());
// no 3D floors, no boundary
if ( (frontidx == hnd.ffsectors.Size()) && (backidx == frontidx) )
return false;
return true;
}
private void DrawMapLines( Vector2 basepos )
{
Vector2 cpos = CPlayer.Camera.prev.xy*(1.-FracTic)+CPlayer.Camera.pos.xy*FracTic;
for ( int i=0; i<level.lines.Size(); i++ )
{
Line l = level.lines[i];
if ( !(l.flags&Line.ML_MAPPED) && !level.allmap && !am_cheat ) continue;
if ( (l.flags&Line.ML_DONTDRAW) && ((am_cheat == 0) || (am_cheat >= 4)) )
continue;
Vector2 rv1 = l.v1.p-cpos, rv2 = l.v2.p-cpos;
if ( min(min(abs(rv1.x),abs(rv2.x)),min(abs(rv1.y),abs(rv2.y))) > MAPVIEWDIST )
continue;
// flip Y
rv1.y *= -1;
rv2.y *= -1;
// rotate by view
rv1 = Actor.RotateVector(rv1,ViewRot.x-90);
rv2 = Actor.RotateVector(rv2,ViewRot.x-90);
// clip to frame
bool visible;
[visible, rv1, rv2] = SWWMUtility.LiangBarsky((-1,-1)*CLIPDIST,(1,1)*CLIPDIST,rv1,rv2);
if ( !visible ) continue;
// scale to minimap frame
rv1 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
rv2 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
// offset to minimap center
rv1 += basepos;
rv2 += basepos;
// get the line color
Color col = am_wallcolor;
if ( (l.flags&Line.ML_MAPPED) || am_cheat )
{
int secwit = CheckSecret(l);
int lock = SWWMUtility.GetLineLock(l);
if ( secwit == 1 ) col = am_secretsectorcolor;
else if ( secwit == 2 ) col = am_unexploredsecretcolor;
else if ( l.flags&Line.ML_SECRET )
{
if ( am_cheat && l.backsector )
col = am_secretwallcolor;
else col = am_wallcolor;
}
else if ( (l.special == Exit_Normal)
|| (l.special == Exit_Secret)
|| (l.special == Teleport_NewMap)
|| (l.special == Teleport_EndGame) )
col = am_interlevelcolor;
else if ( (l.activation&SPAC_PlayerActivate)
&& (l.special == Teleport)
|| (l.special == Teleport_NoFog)
|| (l.special == Teleport_ZombieChanger)
|| (l.special == Teleport_Line) )
col = am_intralevelcolor;
else if ( (lock > 1) && (lock < 256) && SWWMUtility.IsValidLockNum(lock) )
col = SWWMUtility.GetLockColor(lock);
else if ( (l.activation&SPAC_PlayerActivate) && l.special && ShowTriggerLine(l) )
col = am_specialwallcolor;
else if ( l.frontsector && l.backsector )
{
if ( !CmpFloorPlanes(l) ) col = am_fdwallcolor;
else if ( !CmpCeilingPlanes(l) ) col = am_cdwallcolor;
else if ( CheckFFBoundary(l) ) col = am_efwallcolor;
else
{
if ( (am_cheat == 0) || (am_cheat >= 4) )
continue;
col = am_tswallcolor;
}
}
}
else col = am_notseencolor;
// draw the line
Screen.DrawThickLine(int(rv1.x),int(rv1.y),int(rv2.x),int(rv2.y),max(1.,hs.x*.5),col);
}
}
private void DrawMapThings( Vector2 basepos )
{
bool thesight = !!CPlayer.mo.FindInventory("Omnisight");
Vector2 cpos = CPlayer.Camera.prev.xy*(1.-FracTic)+CPlayer.Camera.pos.xy*FracTic;
for ( SWWMSimpleTracker t=hnd.strackers; t; t=t.next )
{
Color col = am_thingcolor;
bool isitem = false;
Vector2 pos;
double angle;
double radius;
if ( t.target )
{
pos = t.target.prev.xy*(1.-FracTic)+t.target.pos.xy*FracTic;
angle = t.target.angle;
radius = t.target.radius;
if ( t.isitem )
{
if ( ((t.target is 'Chancebox') && (t.target.CurState == t.target.SpawnState)) || (t.target is 'SWWMCollectible') ) col = "Purple";
else if ( t.target.bCOUNTITEM || (t.target is 'Key') ) col = am_thingcolor_citem;
else col = am_thingcolor_item;
isitem = true;
}
else if ( t.target.player ) col = t.target.player.GetColor();
else if ( t.target.bFRIENDLY ) col = am_thingcolor_friend;
else if ( t.target.bCOUNTKILL ) col = am_thingcolor_monster;
else if ( t.target.bISMONSTER ) col = am_thingcolor_ncmonster;
}
else
{
pos = t.pos.xy;
angle = t.angle;
radius = t.radius;
if ( t.isitem )
{
if ( t.vipitem ) col = "Purple";
else if ( t.countitem ) col = am_thingcolor_citem;
else col = am_thingcolor_item;
isitem = true;
}
if ( t.isplayer ) col = t.playercol;
else if ( t.friendly ) col = am_thingcolor_friend;
else if ( t.countkill ) col = am_thingcolor_monster;
else if ( t.ismonster ) col = am_thingcolor_ncmonster;
}
int mtime = 35;
if ( thesight && !t.expired ) mtime += 105;
double alph = clamp(((t.lastupdate+mtime)-level.maptime)/35.,0.,1.);
if ( alph <= 0. ) continue;
Vector2 rv = pos-cpos;
if ( min(abs(rv.x)-radius,abs(rv.y)-radius) > MAPVIEWDIST ) continue;
// get actor triangle
Vector2 tv[4];
int nidx = isitem?4:3;
if ( isitem )
{
tv[0] = rv+(-10,-10);
tv[1] = rv+(10,10);
tv[2] = rv+(10,-10);
tv[3] = rv+(-10,10);
}
else
{
tv[0] = rv+Actor.RotateVector((radius,0),angle);
tv[1] = rv+Actor.RotateVector((-radius,radius),angle);
tv[2] = rv+Actor.RotateVector((-radius,-radius),angle);
}
// flip Y
for ( int j=0; j<nidx; j++ ) tv[j].y *= -1;
// rotate by view
for ( int j=0; j<nidx; j++ ) tv[j] = Actor.RotateVector(tv[j],ViewRot.x-90);
bool visible;
Vector2 x0, x1;
// clip to frame
if ( isitem ) for ( int j=0; j<4; j+=2 )
{
[visible, x0, x1] = SWWMUtility.LiangBarsky((-1,-1)*CLIPDIST,(1,1)*CLIPDIST,tv[j],tv[j+1]);
if ( visible )
{
// scale to minimap frame
x0 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
x1 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
// offset to minimap center
x0 += basepos;
x1 += basepos;
// draw the line
Screen.DrawThickLine(int(x0.x),int(x0.y),int(x1.x),int(x1.y),max(1.,hs.x*.5),col,int(alph*255));
}
}
else for ( int j=0; j<3; j++ )
{
[visible, x0, x1] = SWWMUtility.LiangBarsky((-1,-1)*CLIPDIST,(1,1)*CLIPDIST,tv[j],tv[(j+1)%3]);
if ( visible )
{
// scale to minimap frame
x0 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
x1 *= (HALFMAPSIZE/double(CLIPDIST))*hs.x;
// offset to minimap center
x0 += basepos;
x1 += basepos;
// draw the line
Screen.DrawThickLine(int(x0.x),int(x0.y),int(x1.x),int(x1.y),max(1.,hs.x*.5),col,int(alph*255));
}
}
}
}
private void DrawScore()
{
String sstr;
@ -866,10 +1104,31 @@ Class SWWMStatusBar : BaseStatusBar
}
Screen.DrawTexture(ScoreTex[2],false,ss.x-(margin+xx),margin,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
Screen.DrawText(mTewiFont.mFont,Font.CR_FIRE,ss.x-(margin+4+6*digits),margin+1,sstr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int yy = margin+19;
// obviously, don't draw the minimap if the automap is open
if ( !showmini ) showmini = CVar.GetCVar('swwm_showminimap',players[consoleplayer]);
if ( !automapactive && showmini.GetBool() )
{
xx = int(ss.x-(margin+84));
Screen.DrawTexture(MiniBox,false,xx,yy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
Vector2 basemappos = (xx+42,yy+42);
// draw dat stuff
DrawMapLines(basemappos*hs.x);
DrawMapThings(basemappos*hs.x);
// finally, draw the player arrow
int x0 = int(basemappos.x*hs.x);
int x1 = int((basemappos.x+2)*hs.x);
int x2 = int((basemappos.x-2)*hs.x);
int y0 = int((basemappos.y-2)*hs.x);
int y1 = int((basemappos.y+2)*hs.x);
Screen.DrawThickLine(x0,y0,x1,y1,max(1.,hs.x*.5),am_yourcolor);
Screen.DrawThickLine(x1,y1,x2,y1,max(1.,hs.x*.5),am_yourcolor);
Screen.DrawThickLine(x2,y1,x0,y0,max(1.,hs.x*.5),am_yourcolor);
yy += 87;
}
// draw stats and timer when automap is open
if ( !forcestats ) forcestats = CVar.GetCVar('swwm_forcestats',players[consoleplayer]);
int fstats = forcestats.GetInt();
int yy = margin+19;
if ( automapactive || (fstats > 0) )
{
if ( !showitems ) showitems = CVar.GetCVar('am_showitems',players[consoleplayer]);
@ -878,7 +1137,7 @@ Class SWWMStatusBar : BaseStatusBar
if ( !showsecrets ) showsecrets = CVar.GetCVar('am_showsecrets',players[consoleplayer]);
if ( !showtime ) showtime = CVar.GetCVar('am_showtime',players[consoleplayer]);
if ( !showtotaltime ) showtotaltime = CVar.GetCVar('am_showtotaltime',players[consoleplayer]);
int xx = int(ss.x-(margin+2));
xx = int(ss.x-(margin+2));
String str;
Font fnt;
if ( automapactive || (fstats > 1) )

View file

@ -462,8 +462,11 @@ Class Demolitionist : PlayerPawn
let bt = BlockThingsIterator.Create(self,800);
while ( bt.Next() )
{
let i = Inventory(bt.Thing);
if ( !i || i.bINVISIBLE || !i.bSPECIAL || i.Owner || !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
let i = bt.Thing;
if ( !i || (!(i is 'Inventory') && !(i is 'Chancebox')) ) continue;
if ( (i is 'Inventory') && (i.bINVISIBLE || !i.bSPECIAL || Inventory(i).Owner) ) continue;
if ( (i is 'Chancebox') && (i.CurState != i.SpawnState) ) continue;
if ( !SWWMUtility.SphereIntersect(i,pos,800) ) continue;
if ( !thesight && !CheckSight(i,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
SWWMItemSense.Spawn(self,i);
}

View file

@ -757,15 +757,15 @@ Class SWWMInterest : Thinker
Class SWWMItemSense : Thinker
{
Inventory item;
Actor item;
String tag;
int updated;
bool scoreitem;
bool scoreitem, vipitem;
Demolitionist parent;
SWWMItemSense prev, next;
Vector3 pos;
static SWWMItemSense Spawn( Demolitionist parent, Inventory item )
static SWWMItemSense Spawn( Demolitionist parent, Actor item )
{
if ( !parent || !item ) return null;
// only refresh the updated time if existing
@ -779,7 +779,8 @@ Class SWWMItemSense : Thinker
let i = new("SWWMItemSense");
i.ChangeStatNum(STAT_USER);
i.item = item;
i.scoreitem = item.bCOUNTITEM;
i.scoreitem = (item is 'Key')||item.bCOUNTITEM;
i.vipitem = (item is 'Chancebox')||(item is 'SWWMCollectible');
i.parent = parent;
i.updated = level.maptime+35;
i.UpdateTag();
@ -800,7 +801,7 @@ Class SWWMItemSense : Thinker
|| (item is 'BlackShell') || (item is 'PurpleShell')
|| (item is 'GoldShell') || (item is 'SMW05Ammo')
|| (item is 'SheenAmmo') )
tag = item.PickupMessage();
tag = Inventory(item).PickupMessage();
else tag = item.GetTag();
}
@ -1307,3 +1308,151 @@ Class SWWMCrusherBroken : Thinker
}
}
}
// cache data for manual lockdefs parsing nonsense
Class LIEntry
{
int locknumber;
bool hascolor;
Color mapcolor;
}
Class SWWMCachedLockInfo : Thinker
{
Array<LIEntry> ent;
static clearscope bool IsValidLock( int l )
{
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
if ( !cli ) return false;
for ( int i=0; i<cli.ent.Size(); i++ )
{
if ( cli.ent[i].locknumber == l )
return true;
}
return false;
}
static clearscope Color GetLockColor( int l )
{
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
if ( !cli ) return am_lockedcolor;
for ( int i=0; i<cli.ent.Size(); i++ )
{
if ( (cli.ent[i].locknumber == l) && cli.ent[i].hascolor )
return cli.ent[i].mapcolor;
}
return am_lockedcolor;
}
static SWWMCachedLockInfo GetInstance()
{
let ti = ThinkerIterator.Create("SWWMCachedLockInfo",STAT_STATIC);
SWWMCachedLockInfo cli = SWWMCachedLockInfo(ti.Next());
if ( cli ) return cli;
cli = new("SWWMCachedLockInfo");
cli.ChangeStatNum(STAT_STATIC);
return cli;
}
}
// ultralight trackers for certain things
Class SWWMSimpleTracker : Thinker
{
Actor target;
double radius;
double angle;
Vector3 pos;
bool isplayer;
Color playercol;
bool ismonster;
bool friendly;
bool countkill;
bool isitem;
bool countitem;
bool vipitem;
bool expired;
int lastupdate;
SWWMSimpleTracker prev, next;
void Update()
{
if ( !target ) return;
radius = target.radius;
angle = target.angle;
pos = target.pos;
isplayer = target.player;
if ( isplayer ) playercol = target.player.GetColor();
ismonster = target.bISMONSTER;
friendly = target.IsFriend(players[consoleplayer].mo);
countkill = target.bCOUNTKILL;
isitem = (target is 'Inventory');
countitem = (target is 'Key')||target.bCOUNTITEM;
vipitem = ((target is 'Chancebox')&&(target.CurState==target.SpawnState))||(target is 'SWWMCollectible');
lastupdate = level.maptime;
if ( isitem )
{
if ( !target.bSPECIAL || Inventory(target).Owner )
expired = true;
else
{
expired = false;
lastupdate += 70;
if ( countitem ) lastupdate += 70;
if ( vipitem ) lastupdate += 70;
}
}
else if ( vipitem )
{
if ( (target is 'Chancebox') && (target.CurState != target.SpawnState) )
expired = true;
else
{
expired = false;
lastupdate += 70;
}
}
else if ( friendly )
{
expired = target.bKILLED;
if ( expired ) lastupdate += 35;
else lastupdate += 140;
}
else if ( ismonster )
{
expired = target.bKILLED;
if ( !expired )
{
lastupdate += 35;
if ( target.target == players[consoleplayer].mo )
lastupdate += 70;
}
}
}
static SWWMSimpleTracker Track( Actor target )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !hnd ) return null;
SWWMSimpleTracker t;
for ( t=hnd.strackers; t; t=t.next )
{
if ( t.target != target ) continue;
t.Update();
return t;
}
t = new("SWWMSimpleTracker");
t.ChangeStatNum(STAT_INFO);
t.target = target;
t.Update();
t.next = hnd.strackers;
if ( hnd.strackers ) hnd.strackers.prev = t;
hnd.strackers = t;
hnd.strackers_cnt++;
return t;
}
// no OnDestroy pruning like the others, the cleanup is done manually by the handler
}

View file

@ -296,46 +296,62 @@ Class SWWMUtility
return (distsq <= (radius**2));
}
// THANKS FOR NOT GIVING US ANY OTHER WAY TO CHECK IF A LOCK NUMBER IS VALID
// Liang-Barsky line clipping
static clearscope bool, Vector2, Vector2 LiangBarsky( Vector2 minclip, Vector2 maxclip, Vector2 v0, Vector2 v1 )
{
double t0 = 0., t1 = 1.;
double xdelta = v1.x-v0.x;
double ydelta = v1.y-v0.y;
double p, q, r;
for ( int i=0;i<4; i++ )
{
switch ( i )
{
case 0:
p = -xdelta;
q = -(minclip.x-v0.x);
break;
case 1:
p = xdelta;
q = (maxclip.x-v0.x);
break;
case 2:
p = -ydelta;
q = -(minclip.y-v0.y);
break;
case 3:
p = ydelta;
q = (maxclip.y-v0.y);
break;
}
if ( (p == 0.) && (q<0.) ) return false;
if ( p < 0 )
{
r = q/p;
if ( r > t1 ) return false;
else if ( r > t0 ) t0 = r;
}
else if ( p > 0 )
{
r = q/p;
if ( r < t0 ) return false;
else if ( r < t1 ) t1 = r;
}
}
Vector2 ov0 = v0+(xdelta,ydelta)*t0;
Vector2 ov1 = v0+(xdelta,ydelta)*t1;
return true, ov0, ov1;
}
static clearscope bool IsValidLockNum( int l )
{
if ( (l < 1) || (l > 255) ) return true;
Array<Int> valid;
valid.Clear();
for ( int i=0; i<Wads.GetNumLumps(); i++ )
{
String lname = Wads.GetLumpName(i);
if ( !(lname ~== "LOCKDEFS") ) continue;
String data = Wads.ReadLump(i);
Array<String> lines;
lines.Clear();
data.Split(lines,"\n");
for ( int j=0; j<lines.Size(); j++ )
{
if ( lines[j].Left(10) ~== "CLEARLOCKS" ) valid.Clear();
else if ( Lines[j].Left(5) ~== "LOCK " )
{
Array<String> spl;
spl.Clear();
lines[j].Split(spl," ",TOK_SKIPEMPTY);
// check game string (if any)
if ( spl.Size() > 2 )
{
if ( (spl[2] ~== "DOOM") && !(gameinfo.gametype&GAME_Doom) ) continue;
else if ( (spl[2] ~== "HERETIC") && !(gameinfo.gametype&GAME_Heretic) ) continue;
else if ( (spl[2] ~== "HEXEN") && !(gameinfo.gametype&GAME_Hexen) ) continue;
else if ( (spl[2] ~== "STRIFE") && !(gameinfo.gametype&GAME_Strife) ) continue;
else if ( (spl[2] ~== "CHEX") && !(gameinfo.gametype&GAME_Chex) ) continue;
}
valid.Push(spl[1].ToInt());
}
}
}
for ( int i=0; i<valid.Size(); i++ )
{
if ( valid[i] == l ) return true;
}
return false;
return SWWMCachedLockInfo.IsValidLock(l);
}
static clearscope Color GetLockColor( int l )
{
return SWWMCachedLockInfo.GetLockColor(l);
}
// wheeeeeeee, let's play a game of "who is who"