mirror of
https://github.com/ZDoom/gzdoom.git
synced 2024-12-02 00:43:29 +00:00
ebd4ebf298
cleaned up function interface to avoid referencing the file system in the worker functions. replaced StringTable's operators with functions.. The main reason is that these are far easier to look up when browsing the source. This also fixes a premature translation in SBARINFO that was done in the parsing stage, not the printing stage.
3499 lines
91 KiB
C++
3499 lines
91 KiB
C++
//-----------------------------------------------------------------------------
|
|
//
|
|
// Copyright 1993-1996 id Software
|
|
// Copyright 1994-1996 Raven Software
|
|
// Copyright 1999-2016 Randy Heit
|
|
// Copyright 2002-2016 Christoph Oelckers
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see http://www.gnu.org/licenses/
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
//
|
|
//
|
|
// DESCRIPTION: the automap code
|
|
//
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include <stdio.h>
|
|
#include <array>
|
|
|
|
#include "doomdef.h"
|
|
|
|
#include "g_level.h"
|
|
#include "st_stuff.h"
|
|
#include "p_local.h"
|
|
#include "p_lnspec.h"
|
|
#include "filesystem.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "d_event.h"
|
|
#include "gi.h"
|
|
#include "p_setup.h"
|
|
#include "c_bind.h"
|
|
#include "serializer_doom.h"
|
|
#include "r_sky.h"
|
|
#include "sbar.h"
|
|
#include "d_player.h"
|
|
#include "p_blockmap.h"
|
|
#include "g_game.h"
|
|
#include "v_video.h"
|
|
#include "d_main.h"
|
|
#include "v_draw.h"
|
|
|
|
#include "m_cheat.h"
|
|
#include "c_dispatch.h"
|
|
#include "d_netinf.h"
|
|
|
|
// State.
|
|
#include "r_state.h"
|
|
#include "r_utility.h"
|
|
|
|
// Data.
|
|
#include "gstrings.h"
|
|
|
|
#include "am_map.h"
|
|
#include "po_man.h"
|
|
#include "a_keys.h"
|
|
#include "g_levellocals.h"
|
|
#include "actorinlines.h"
|
|
#include "earcut.hpp"
|
|
#include "c_buttons.h"
|
|
#include "d_buttons.h"
|
|
#include "texturemanager.h"
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Global state
|
|
//
|
|
//=============================================================================
|
|
|
|
enum
|
|
{
|
|
AM_NUMMARKPOINTS = 10,
|
|
};
|
|
|
|
// C++ cannot do static const floats in a class, so these need to be global...
|
|
static const double PLAYERRADIUS = 16.; // player radius for automap checking
|
|
static const double M_ZOOMIN = 2; // how much zoom-in per second
|
|
static const double M_ZOOMOUT = 0.2; // how much zoom-out per second
|
|
static const double M_OLDZOOMIN = (1.02); // for am_zoom
|
|
static const double M_OLDZOOMOUT = (1 / 1.02);
|
|
|
|
static FTextureID marknums[AM_NUMMARKPOINTS]; // numbers used for marking by the automap
|
|
bool automapactive = false;
|
|
|
|
//=============================================================================
|
|
//
|
|
// Types
|
|
//
|
|
//=============================================================================
|
|
|
|
struct fpoint_t
|
|
{
|
|
int x, y;
|
|
};
|
|
|
|
struct fline_t
|
|
{
|
|
fpoint_t a, b;
|
|
};
|
|
|
|
struct mpoint_t
|
|
{
|
|
double x, y;
|
|
};
|
|
|
|
struct mline_t
|
|
{
|
|
mpoint_t a, b;
|
|
};
|
|
|
|
struct islope_t
|
|
{
|
|
double slp, islp;
|
|
};
|
|
|
|
//=============================================================================
|
|
//
|
|
// CVARs
|
|
//
|
|
//=============================================================================
|
|
|
|
CVAR(Bool, am_textured, false, CVAR_ARCHIVE)
|
|
CVAR(Float, am_linealpha, 1.0f, CVAR_ARCHIVE)
|
|
CVAR(Int, am_linethickness, 1, CVAR_ARCHIVE)
|
|
CVAR(Int, am_lineantialiasing, 0, CVAR_ARCHIVE)
|
|
CVAR(Bool, am_thingrenderstyles, true, CVAR_ARCHIVE)
|
|
CVAR(Int, am_showsubsector, -1, 0);
|
|
|
|
|
|
CUSTOM_CVAR(Int, am_showalllines, -1, CVAR_NOINITCALL) // This is a cheat so don't save it.
|
|
{
|
|
if (primaryLevel && primaryLevel->automap)
|
|
primaryLevel->automap->UpdateShowAllLines();
|
|
}
|
|
|
|
EXTERN_CVAR(Bool, sv_cheats)
|
|
CUSTOM_CVAR(Int, am_cheat, 0, 0)
|
|
{
|
|
// No automap cheat in net games when cheats are disabled!
|
|
if (netgame && !sv_cheats && self != 0)
|
|
{
|
|
self = 0;
|
|
}
|
|
}
|
|
|
|
|
|
CVAR(Int, am_rotate, 0, CVAR_ARCHIVE);
|
|
CUSTOM_CVAR(Int, am_overlay, 0, CVAR_ARCHIVE)
|
|
{
|
|
// stop overlay if we're told not to use it anymore.
|
|
if (automapactive && viewactive && (self == 0))
|
|
{
|
|
automapactive = false;
|
|
viewactive = true;
|
|
}
|
|
}
|
|
CVAR(Bool, am_showsecrets, true, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_showmonsters, true, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_showitems, false, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_showtime, true, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_showtotaltime, false, CVAR_ARCHIVE);
|
|
CVAR(Int, am_colorset, 0, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_customcolors, true, CVAR_ARCHIVE);
|
|
CVAR(Int, am_map_secrets, 1, CVAR_ARCHIVE);
|
|
CVAR(Int, am_drawmapback, 1, CVAR_ARCHIVE);
|
|
CVAR(Bool, am_showkeys, true, CVAR_ARCHIVE);
|
|
CVAR(Int, am_showtriggerlines, 0, CVAR_ARCHIVE);
|
|
CVAR(Int, am_showthingsprites, 0, CVAR_ARCHIVE);
|
|
CVAR (Bool, am_showkeys_always, false, CVAR_ARCHIVE);
|
|
|
|
CUSTOM_CVAR(Int, am_emptyspacemargin, 0, CVAR_ARCHIVE)
|
|
{
|
|
if (self < 0)
|
|
{
|
|
self = 0;
|
|
}
|
|
else if (self > 90)
|
|
{
|
|
self = 90;
|
|
}
|
|
|
|
if (nullptr != StatusBar && primaryLevel && primaryLevel->automap)
|
|
{
|
|
primaryLevel->automap->NewResolution();
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// map functions
|
|
//
|
|
//=============================================================================
|
|
|
|
CVAR(Bool, am_followplayer, true, CVAR_ARCHIVE)
|
|
CVAR(Bool, am_portaloverlay, true, CVAR_ARCHIVE)
|
|
CVAR(Bool, am_showgrid, false, CVAR_ARCHIVE)
|
|
CVAR(Float, am_zoomdir, 0.f, CVAR_ARCHIVE)
|
|
|
|
static const char *const DEFAULT_FONT_NAME = "AMMNUMx";
|
|
CVAR(String, am_markfont, DEFAULT_FONT_NAME, CVAR_ARCHIVE)
|
|
CVAR(Int, am_markcolor, CR_GREY, CVAR_ARCHIVE)
|
|
|
|
CCMD(am_togglefollow)
|
|
{
|
|
am_followplayer = !am_followplayer;
|
|
if (primaryLevel && primaryLevel->automap)
|
|
primaryLevel->automap->ResetFollowLocation();
|
|
Printf("%s\n", GStrings.GetString(am_followplayer ? "AMSTR_FOLLOWON" : "AMSTR_FOLLOWOFF"));
|
|
}
|
|
|
|
CCMD(am_togglegrid)
|
|
{
|
|
am_showgrid = !am_showgrid;
|
|
Printf("%s\n", GStrings.GetString(am_showgrid ? "AMSTR_GRIDON" : "AMSTR_GRIDOFF"));
|
|
}
|
|
|
|
CCMD(am_toggletexture)
|
|
{
|
|
am_textured = !am_textured;
|
|
Printf("%s\n", GStrings.GetString(am_textured ? "AMSTR_TEXON" : "AMSTR_TEXOFF"));
|
|
}
|
|
|
|
CCMD(am_setmark)
|
|
{
|
|
if (primaryLevel && primaryLevel->automap)
|
|
{
|
|
int m = primaryLevel->automap->addMark();
|
|
if (m >= 0)
|
|
{
|
|
Printf("%s %d\n", GStrings.GetString("AMSTR_MARKEDSPOT"), m);
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD(am_clearmarks)
|
|
{
|
|
if (primaryLevel && primaryLevel->automap && primaryLevel->automap->clearMarks())
|
|
{
|
|
Printf("%s\n", GStrings.GetString("AMSTR_MARKSCLEARED"));
|
|
}
|
|
}
|
|
|
|
CCMD(am_gobig)
|
|
{
|
|
if (primaryLevel && primaryLevel->automap)
|
|
primaryLevel->automap->GoBig();
|
|
}
|
|
|
|
CCMD(togglemap)
|
|
{
|
|
if (gameaction == ga_nothing)
|
|
{
|
|
gameaction = ga_togglemap;
|
|
}
|
|
}
|
|
|
|
CCMD(am_zoom)
|
|
{
|
|
if (argv.argc() >= 2)
|
|
{
|
|
am_zoomdir = (float)atof(argv[1]);
|
|
}
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Automap colors
|
|
//
|
|
//=============================================================================
|
|
|
|
CVAR (Color, am_backcolor, 0x6c5440, CVAR_ARCHIVE);
|
|
CVAR (Color, am_yourcolor, 0xfce8d8, CVAR_ARCHIVE);
|
|
CVAR (Color, am_wallcolor, 0x2c1808, CVAR_ARCHIVE);
|
|
CVAR (Color, am_secretwallcolor, 0x000000, CVAR_ARCHIVE);
|
|
CVAR (Color, am_specialwallcolor, 0xffffff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_tswallcolor, 0x888888, CVAR_ARCHIVE);
|
|
CVAR (Color, am_fdwallcolor, 0x887058, CVAR_ARCHIVE);
|
|
CVAR (Color, am_cdwallcolor, 0x4c3820, CVAR_ARCHIVE);
|
|
CVAR (Color, am_efwallcolor, 0x665555, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_gridcolor, 0x8b5a2b, CVAR_ARCHIVE);
|
|
CVAR (Color, am_xhaircolor, 0x808080, CVAR_ARCHIVE);
|
|
CVAR (Color, am_notseencolor, 0x6c6c6c, CVAR_ARCHIVE);
|
|
CVAR (Color, am_lockedcolor, 0x007800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_intralevelcolor, 0x0000ff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_interlevelcolor, 0xff0000, CVAR_ARCHIVE);
|
|
CVAR (Color, am_secretsectorcolor, 0xff00ff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_unexploredsecretcolor, 0xff00ff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor_friend, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor_monster, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor_ncmonster, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor_item, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_thingcolor_citem, 0xfcfcfc, CVAR_ARCHIVE);
|
|
CVAR (Color, am_portalcolor, 0x404040, CVAR_ARCHIVE);
|
|
|
|
CVAR (Color, am_ovyourcolor, 0xfce8d8, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovwallcolor, 0x00ff00, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovsecretwallcolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovspecialwallcolor, 0xffffff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovotherwallscolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovlockedcolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovefwallcolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovfdwallcolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovcdwallcolor, 0x008844, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovunseencolor, 0x00226e, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovtelecolor, 0xffff00, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovinterlevelcolor, 0xffff00, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovsecretsectorcolor,0x00ffff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovunexploredsecretcolor,0x00ffff, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor_friend, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor_monster, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor_ncmonster, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor_item, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovthingcolor_citem, 0xe88800, CVAR_ARCHIVE);
|
|
CVAR (Color, am_ovportalcolor, 0x004022, CVAR_ARCHIVE);
|
|
|
|
//=============================================================================
|
|
//
|
|
// internal representation of a single color
|
|
//
|
|
//=============================================================================
|
|
|
|
struct AMColor
|
|
{
|
|
uint32_t RGB;
|
|
|
|
void FromCVar(FColorCVar & cv)
|
|
{
|
|
RGB = uint32_t(cv) | MAKEARGB(255, 0, 0, 0);
|
|
}
|
|
|
|
void FromRGB(int r,int g, int b)
|
|
{
|
|
RGB = MAKEARGB(255, r, g, b);
|
|
}
|
|
|
|
void setInvalid()
|
|
{
|
|
RGB = 0;
|
|
}
|
|
|
|
bool isValid() const
|
|
{
|
|
return RGB != 0;
|
|
}
|
|
};
|
|
|
|
//=============================================================================
|
|
//
|
|
// a complete color set
|
|
//
|
|
//=============================================================================
|
|
|
|
static const char *ColorNames[] = {
|
|
"Background",
|
|
"YourColor",
|
|
"WallColor",
|
|
"TwoSidedWallColor",
|
|
"FloorDiffWallColor",
|
|
"CeilingDiffWallColor",
|
|
"ExtraFloorWallColor",
|
|
"ThingColor",
|
|
"ThingColor_Item",
|
|
"ThingColor_CountItem",
|
|
"ThingColor_Monster",
|
|
"ThingColor_NocountMonster",
|
|
"ThingColor_Friend",
|
|
"SpecialWallColor",
|
|
"SecretWallColor",
|
|
"GridColor",
|
|
"XHairColor",
|
|
"NotSeenColor",
|
|
"LockedColor",
|
|
"IntraTeleportColor",
|
|
"InterTeleportColor",
|
|
"SecretSectorColor",
|
|
"UnexploredSecretColor",
|
|
"PortalColor",
|
|
"AlmostBackgroundColor",
|
|
nullptr
|
|
};
|
|
|
|
struct AMColorset
|
|
{
|
|
enum
|
|
{
|
|
Background,
|
|
YourColor,
|
|
WallColor,
|
|
TSWallColor,
|
|
FDWallColor,
|
|
CDWallColor,
|
|
EFWallColor,
|
|
ThingColor,
|
|
ThingColor_Item,
|
|
ThingColor_CountItem,
|
|
ThingColor_Monster,
|
|
ThingColor_NocountMonster,
|
|
ThingColor_Friend,
|
|
SpecialWallColor,
|
|
SecretWallColor,
|
|
GridColor,
|
|
XHairColor,
|
|
NotSeenColor,
|
|
LockedColor,
|
|
IntraTeleportColor,
|
|
InterTeleportColor,
|
|
SecretSectorColor,
|
|
UnexploredSecretColor,
|
|
PortalColor,
|
|
AlmostBackgroundColor,
|
|
AM_NUM_COLORS
|
|
};
|
|
|
|
AMColor c[AM_NUM_COLORS];
|
|
bool displayLocks;
|
|
bool forcebackground;
|
|
bool defined; // only for mod specific colorsets: must be true to be usable
|
|
|
|
void initFromCVars(FColorCVarRef **values)
|
|
{
|
|
for(int i=0;i<AlmostBackgroundColor; i++)
|
|
{
|
|
c[i].FromCVar(*(values[i]->get()));
|
|
}
|
|
|
|
uint32_t ba = *(values[0]);
|
|
|
|
int r = RPART(ba) - 16;
|
|
int g = GPART(ba) - 16;
|
|
int b = BPART(ba) - 16;
|
|
|
|
if (r < 0)
|
|
r += 32;
|
|
if (g < 0)
|
|
g += 32;
|
|
if (b < 0)
|
|
b += 32;
|
|
|
|
c[AlmostBackgroundColor].FromRGB(r, g, b);
|
|
displayLocks = true;
|
|
forcebackground = false;
|
|
}
|
|
|
|
void initFromColors(const unsigned char *colors, bool showlocks)
|
|
{
|
|
for(int i=0, j=0; i<AM_NUM_COLORS; i++, j+=3)
|
|
{
|
|
if (colors[j] == 1 && colors[j+1] == 0 && colors[j+2] == 0)
|
|
{
|
|
c[i].setInvalid();
|
|
}
|
|
else
|
|
{
|
|
c[i].FromRGB(colors[j], colors[j+1], colors[j+2]);
|
|
}
|
|
}
|
|
displayLocks = showlocks;
|
|
forcebackground = false;
|
|
}
|
|
|
|
void setWhite()
|
|
{
|
|
c[0].FromRGB(0,0,0);
|
|
for(int i=1; i<AM_NUM_COLORS; i++)
|
|
{
|
|
c[i].FromRGB(255,255,255);
|
|
}
|
|
}
|
|
|
|
const AMColor &operator[](int index) const
|
|
{
|
|
return c[index];
|
|
}
|
|
|
|
bool isValid(int index) const
|
|
{
|
|
return c[index].isValid();
|
|
}
|
|
};
|
|
|
|
//=============================================================================
|
|
//
|
|
// automap colors forced by linedef
|
|
//
|
|
//=============================================================================
|
|
|
|
static const int AUTOMAP_LINE_COLORS[AMLS_COUNT] =
|
|
{
|
|
-1, // AMLS_Default (unused)
|
|
AMColorset::WallColor, // AMLS_OneSided,
|
|
AMColorset::TSWallColor, // AMLS_TwoSided
|
|
AMColorset::FDWallColor, // AMLS_FloorDiff
|
|
AMColorset::CDWallColor, // AMLS_CeilingDiff
|
|
AMColorset::EFWallColor, // AMLS_ExtraFloor
|
|
AMColorset::SpecialWallColor, // AMLS_Special
|
|
AMColorset::SecretWallColor, // AMLS_Secret
|
|
AMColorset::NotSeenColor, // AMLS_NotSeen
|
|
AMColorset::LockedColor, // AMLS_Locked
|
|
AMColorset::IntraTeleportColor, // AMLS_IntraTeleport
|
|
AMColorset::InterTeleportColor, // AMLS_InterTeleport
|
|
AMColorset::UnexploredSecretColor, // AMLS_UnexploredSecret
|
|
AMColorset::PortalColor, // AMLS_Portal
|
|
};
|
|
|
|
//=============================================================================
|
|
//
|
|
// predefined colorsets
|
|
//
|
|
//=============================================================================
|
|
|
|
static FColorCVarRef *cv_standard[] = {
|
|
&am_backcolor,
|
|
&am_yourcolor,
|
|
&am_wallcolor,
|
|
&am_tswallcolor,
|
|
&am_fdwallcolor,
|
|
&am_cdwallcolor,
|
|
&am_efwallcolor,
|
|
&am_thingcolor,
|
|
&am_thingcolor_item,
|
|
&am_thingcolor_citem,
|
|
&am_thingcolor_monster,
|
|
&am_thingcolor_ncmonster,
|
|
&am_thingcolor_friend,
|
|
&am_specialwallcolor,
|
|
&am_secretwallcolor,
|
|
&am_gridcolor,
|
|
&am_xhaircolor,
|
|
&am_notseencolor,
|
|
&am_lockedcolor,
|
|
&am_intralevelcolor,
|
|
&am_interlevelcolor,
|
|
&am_secretsectorcolor,
|
|
&am_unexploredsecretcolor,
|
|
&am_portalcolor
|
|
};
|
|
|
|
static FColorCVarRef *cv_overlay[] = {
|
|
&am_backcolor, // this will not be used in overlay mode
|
|
&am_ovyourcolor,
|
|
&am_ovwallcolor,
|
|
&am_ovotherwallscolor,
|
|
&am_ovfdwallcolor,
|
|
&am_ovcdwallcolor,
|
|
&am_ovefwallcolor,
|
|
&am_ovthingcolor,
|
|
&am_ovthingcolor_item,
|
|
&am_ovthingcolor_citem,
|
|
&am_ovthingcolor_monster,
|
|
&am_ovthingcolor_ncmonster,
|
|
&am_ovthingcolor_friend,
|
|
&am_ovspecialwallcolor,
|
|
&am_ovsecretwallcolor,
|
|
&am_gridcolor, // this will not be used in overlay mode
|
|
&am_xhaircolor, // this will not be used in overlay mode
|
|
&am_ovunseencolor,
|
|
&am_ovlockedcolor,
|
|
&am_ovtelecolor,
|
|
&am_ovinterlevelcolor,
|
|
&am_ovsecretsectorcolor,
|
|
&am_ovunexploredsecretcolor,
|
|
&am_ovportalcolor
|
|
};
|
|
|
|
CCMD(am_restorecolors)
|
|
{
|
|
for (unsigned i = 0; i < countof(cv_standard); i++)
|
|
{
|
|
cv_standard[i]->get()->ResetToDefault();
|
|
}
|
|
for (unsigned i = 0; i < countof(cv_overlay); i++)
|
|
{
|
|
cv_overlay[i]->get()->ResetToDefault();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#define NOT_USED 1,0,0 // use almost black as indicator for an unused color
|
|
|
|
static unsigned char DoomColors[]= {
|
|
0x00,0x00,0x00, // background
|
|
0xff,0xff,0xff, // yourcolor
|
|
0xfc,0x00,0x00, // wallcolor
|
|
0x80,0x80,0x80, // tswallcolor
|
|
0xbc,0x78,0x48, // fdwallcolor
|
|
0xfc,0xfc,0x00, // cdwallcolor
|
|
0xbc,0x78,0x48, // efwallcolor
|
|
0x74,0xfc,0x6c, // thingcolor
|
|
0x74,0xfc,0x6c, // thingcolor_item
|
|
0x74,0xfc,0x6c, // thingcolor_citem
|
|
0x74,0xfc,0x6c, // thingcolor_monster
|
|
0x74,0xfc,0x6c, // thingcolor_ncmonster
|
|
0x74,0xfc,0x6c, // thingcolor_friend
|
|
NOT_USED, // specialwallcolor
|
|
NOT_USED, // secretwallcolor
|
|
0x4c,0x4c,0x4c, // gridcolor
|
|
0x80,0x80,0x80, // xhaircolor
|
|
0x6c,0x6c,0x6c, // notseencolor
|
|
0xfc,0xfc,0x00, // lockedcolor
|
|
NOT_USED, // intrateleport
|
|
NOT_USED, // interteleport
|
|
NOT_USED, // secretsector
|
|
NOT_USED, // unexploredsecretsector
|
|
0x10,0x10,0x10, // almostbackground
|
|
0x40,0x40,0x40 // portal
|
|
};
|
|
|
|
static unsigned char StrifeColors[]= {
|
|
0x00,0x00,0x00, // background
|
|
239, 239, 0, // yourcolor
|
|
199, 195, 195, // wallcolor
|
|
119, 115, 115, // tswallcolor
|
|
55, 59, 91, // fdwallcolor
|
|
119, 115, 115, // cdwallcolor
|
|
55, 59, 91, // efwallcolor
|
|
187, 59, 0, // thingcolor
|
|
219, 171, 0, // thingcolor_item
|
|
219, 171, 0, // thingcolor_citem
|
|
0xfc,0x00,0x00, // thingcolor_monster
|
|
0xfc,0x00,0x00, // thingcolor_ncmonster
|
|
0xfc,0x00,0x00, // thingcolor_friend
|
|
NOT_USED, // specialwallcolor
|
|
NOT_USED, // secretwallcolor
|
|
0x4c,0x4c,0x4c, // gridcolor
|
|
0x80,0x80,0x80, // xhaircolor
|
|
0x6c,0x6c,0x6c, // notseencolor
|
|
119, 115, 115, // lockedcolor
|
|
NOT_USED, // intrateleport
|
|
NOT_USED, // interteleport
|
|
NOT_USED, // secretsector
|
|
NOT_USED, // unexploredsecretsector
|
|
0x10,0x10,0x10, // almostbackground
|
|
0x40,0x40,0x40 // portal
|
|
};
|
|
|
|
static unsigned char RavenColors[]= {
|
|
0x6c,0x54,0x40, // background
|
|
0xff,0xff,0xff, // yourcolor
|
|
75, 50, 16, // wallcolor
|
|
88, 93, 86, // tswallcolor
|
|
208, 176, 133, // fdwallcolor
|
|
103, 59, 31, // cdwallcolor
|
|
208, 176, 133, // efwallcolor
|
|
236, 236, 236, // thingcolor
|
|
236, 236, 236, // thingcolor_item
|
|
236, 236, 236, // thingcolor_citem
|
|
236, 236, 236, // thingcolor_monster
|
|
236, 236, 236, // thingcolor_ncmonster
|
|
236, 236, 236, // thingcolor_friend
|
|
NOT_USED, // specialwallcolor
|
|
NOT_USED, // secretwallcolor
|
|
75, 50, 16, // gridcolor
|
|
0x00,0x00,0x00, // xhaircolor
|
|
0x00,0x00,0x00, // notseencolor
|
|
103, 59, 31, // lockedcolor
|
|
NOT_USED, // intrateleport
|
|
NOT_USED, // interteleport
|
|
NOT_USED, // secretsector
|
|
NOT_USED, // unexploredsecretsector
|
|
0x10,0x10,0x10, // almostbackground
|
|
0x50,0x50,0x50 // portal
|
|
};
|
|
|
|
#undef NOT_USED
|
|
|
|
static AMColorset AMColors;
|
|
static AMColorset AMMod;
|
|
static AMColorset AMModOverlay;
|
|
|
|
|
|
void AM_ClearColorsets()
|
|
{
|
|
AMModOverlay.defined = false;
|
|
AMMod.defined = false;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
static void AM_initColors(bool overlayed)
|
|
{
|
|
if (overlayed)
|
|
{
|
|
if (am_customcolors && AMModOverlay.defined)
|
|
{
|
|
AMColors = AMModOverlay;
|
|
}
|
|
else
|
|
{
|
|
AMColors.initFromCVars(cv_overlay);
|
|
}
|
|
}
|
|
else if (am_customcolors && AMMod.defined)
|
|
{
|
|
AMColors = AMMod;
|
|
}
|
|
else switch (am_colorset)
|
|
{
|
|
default:
|
|
/* Use the custom colors in the am_* cvars */
|
|
AMColors.initFromCVars(cv_standard);
|
|
break;
|
|
|
|
case 1: // Doom
|
|
// Use colors corresponding to the original Doom's
|
|
AMColors.initFromColors(DoomColors, false);
|
|
break;
|
|
|
|
case 2: // Strife
|
|
// Use colors corresponding to the original Strife's
|
|
AMColors.initFromColors(StrifeColors, false);
|
|
break;
|
|
|
|
case 3: // Raven
|
|
// Use colors corresponding to the original Raven's
|
|
AMColors.initFromColors(RavenColors, true);
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// custom color parser
|
|
//
|
|
//=============================================================================
|
|
|
|
void FMapInfoParser::ParseAMColors(bool overlay)
|
|
{
|
|
bool colorset = false;
|
|
|
|
AMColorset &cset = overlay? AMModOverlay : AMMod;
|
|
|
|
cset.setWhite();
|
|
cset.defined = true;
|
|
sc.MustGetToken('{');
|
|
while(sc.GetToken())
|
|
{
|
|
if (sc.TokenType == '}') return;
|
|
|
|
sc.TokenMustBe(TK_Identifier);
|
|
FString nextKey = sc.String;
|
|
sc.MustGetToken('=');
|
|
|
|
if (nextKey.CompareNoCase("base") == 0)
|
|
{
|
|
if (colorset) sc.ScriptError("'base' must be specified before the first color");
|
|
sc.MustGetToken(TK_StringConst);
|
|
if (sc.Compare("doom"))
|
|
{
|
|
cset.initFromColors(DoomColors, false);
|
|
}
|
|
else if (sc.Compare("raven"))
|
|
{
|
|
cset.initFromColors(RavenColors, true);
|
|
}
|
|
else if (sc.Compare("strife"))
|
|
{
|
|
cset.initFromColors(StrifeColors, false);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError("Unknown value for 'base'. Must be 'Doom', 'Strife' or 'Raven'.");
|
|
}
|
|
}
|
|
else if (nextKey.CompareNoCase("showlocks") == 0)
|
|
{
|
|
if(sc.CheckToken(TK_False))
|
|
cset.displayLocks = false;
|
|
else
|
|
{
|
|
sc.MustGetToken(TK_True);
|
|
cset.displayLocks = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
for (i = 0; ColorNames[i] != nullptr; i++)
|
|
{
|
|
if (nextKey.CompareNoCase(ColorNames[i]) == 0)
|
|
{
|
|
sc.MustGetToken(TK_StringConst);
|
|
FString color = sc.String;
|
|
FString colorName = V_GetColorStringByName(color.GetChars());
|
|
if(!colorName.IsEmpty()) color = colorName;
|
|
int colorval = V_GetColorFromString(color.GetChars());
|
|
cset.c[i].FromRGB(RPART(colorval), GPART(colorval), BPART(colorval));
|
|
colorset = true;
|
|
break;
|
|
}
|
|
}
|
|
if (ColorNames[i]== nullptr)
|
|
{
|
|
sc.ScriptError("Unknown key '%s'", nextKey.GetChars());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// The vector graphics for the automap.
|
|
// A line drawing of the player pointing right,
|
|
// starting from the middle.
|
|
//
|
|
//=============================================================================
|
|
|
|
static TArray<mline_t> MapArrow;
|
|
static TArray<mline_t> CheatMapArrow;
|
|
static TArray<mline_t> CheatKey;
|
|
static TArray<mline_t> EasyKey;
|
|
|
|
static std::array<mline_t, 3> thintriangle_guy = { {
|
|
{{-.5,-.7}, {1,0}},
|
|
{{1,0}, {-.5,.7}},
|
|
{{-.5,.7}, {-.5,-.7}}
|
|
} };
|
|
|
|
//=============================================================================
|
|
//
|
|
// vector graphics
|
|
//
|
|
//=============================================================================
|
|
|
|
static void AM_ParseArrow(TArray<mline_t> &Arrow, const char *lumpname)
|
|
{
|
|
const int R = int((8 * PLAYERRADIUS) / 7);
|
|
FScanner sc;
|
|
int lump = fileSystem.CheckNumForFullName(lumpname, true);
|
|
if (lump >= 0)
|
|
{
|
|
sc.OpenLumpNum(lump);
|
|
sc.SetCMode(true);
|
|
while (sc.GetToken())
|
|
{
|
|
mline_t line;
|
|
sc.TokenMustBe('(');
|
|
sc.MustGetFloat();
|
|
line.a.x = sc.Float*R;
|
|
sc.MustGetToken(',');
|
|
sc.MustGetFloat();
|
|
line.a.y = sc.Float*R;
|
|
sc.MustGetToken(')');
|
|
sc.MustGetToken(',');
|
|
sc.MustGetToken('(');
|
|
sc.MustGetFloat();
|
|
line.b.x = sc.Float*R;
|
|
sc.MustGetToken(',');
|
|
sc.MustGetFloat();
|
|
line.b.y = sc.Float*R;
|
|
sc.MustGetToken(')');
|
|
Arrow.Push(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AM_StaticInit()
|
|
{
|
|
MapArrow.Clear();
|
|
CheatMapArrow.Clear();
|
|
CheatKey.Clear();
|
|
EasyKey.Clear();
|
|
|
|
if (gameinfo.mMapArrow.IsNotEmpty()) AM_ParseArrow(MapArrow, gameinfo.mMapArrow.GetChars());
|
|
if (gameinfo.mCheatMapArrow.IsNotEmpty()) AM_ParseArrow(CheatMapArrow, gameinfo.mCheatMapArrow.GetChars());
|
|
AM_ParseArrow(CheatKey, gameinfo.mCheatKey.GetChars());
|
|
AM_ParseArrow(EasyKey, gameinfo.mEasyKey.GetChars());
|
|
if (MapArrow.Size() == 0) I_FatalError("No automap arrow defined");
|
|
|
|
char namebuf[9];
|
|
|
|
for (int i = 0; i < AM_NUMMARKPOINTS; i++)
|
|
{
|
|
mysnprintf(namebuf, countof(namebuf), "AMMNUM%d", i);
|
|
marknums[i] = TexMan.CheckForTexture(namebuf, ETextureType::MiscPatch);
|
|
}
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// the actual automap class definition
|
|
//
|
|
//=============================================================================
|
|
|
|
IMPLEMENT_CLASS(DAutomapBase, true, false);
|
|
|
|
class DAutomap :public DAutomapBase
|
|
{
|
|
DECLARE_CLASS(DAutomap, DAutomapBase)
|
|
|
|
enum
|
|
{
|
|
F_PANINC = 140 / TICRATE, // how much the automap moves window per tic in frame-buffer coordinates moves 140 pixels at 320x200 in 1 second
|
|
};
|
|
|
|
//FLevelLocals *Level;
|
|
// scale on entry
|
|
// used by MTOF to scale from map-to-frame-buffer coords
|
|
double scale_mtof = .2;
|
|
// used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof)
|
|
double scale_ftom;
|
|
|
|
int bigstate;
|
|
int MapPortalGroup;
|
|
|
|
// Disable the ML_DONTDRAW line flag if x% of all lines in a map are flagged with it
|
|
// (To counter annoying mappers who think they are smart by making the automap unusable)
|
|
bool am_showallenabled;
|
|
|
|
// location of window on screen
|
|
int f_x;
|
|
int f_y;
|
|
|
|
// size of window on screen
|
|
int f_w;
|
|
int f_h;
|
|
|
|
int amclock;
|
|
|
|
mpoint_t m_paninc; // how far the window pans each tic (map coords)
|
|
double mtof_zoommul; // how far the window zooms in each tic (map coords)
|
|
|
|
double m_x, m_y; // LL x,y where the window is on the map (map coords)
|
|
double m_x2, m_y2; // UR x,y where the window is on the map (map coords)
|
|
|
|
//
|
|
// width/height of window on map (map coords)
|
|
//
|
|
double m_w;
|
|
double m_h;
|
|
|
|
// based on level size
|
|
double min_x, min_y, max_x, max_y;
|
|
|
|
double max_w; // max_x-min_x,
|
|
double max_h; // max_y-min_y
|
|
|
|
// based on player size
|
|
double min_w;
|
|
double min_h;
|
|
|
|
|
|
double min_scale_mtof; // used to tell when to stop zooming out
|
|
double max_scale_mtof; // used to tell when to stop zooming in
|
|
|
|
// old stuff for recovery later
|
|
double old_m_w, old_m_h;
|
|
double old_m_x, old_m_y;
|
|
|
|
// old location used by the Follower routine
|
|
mpoint_t f_oldloc;
|
|
|
|
mpoint_t markpoints[AM_NUMMARKPOINTS]; // where the points are
|
|
int markpointnum = 0; // next point to be assigned
|
|
|
|
FTextureID mapback; // the automap background
|
|
double mapystart = 0; // y-value for the start of the map bitmap...used in the parallax stuff.
|
|
double mapxstart = 0; //x-value for the bitmap.
|
|
|
|
TArray<FVector2> points;
|
|
|
|
// translates between frame-buffer and map distances
|
|
double FTOM(double x)
|
|
{
|
|
return x * scale_ftom;
|
|
}
|
|
|
|
double MTOF(double x)
|
|
{
|
|
return x * scale_mtof;
|
|
}
|
|
|
|
// translates between frame-buffer and map coordinates
|
|
int CXMTOF(double x)
|
|
{
|
|
return int(MTOF((x)-m_x)/* - f_x*/);
|
|
}
|
|
|
|
int CYMTOF(double y)
|
|
{
|
|
return int(f_h - MTOF((y)-m_y)/* + f_y*/);
|
|
}
|
|
|
|
void calcMinMaxMtoF();
|
|
|
|
void DrawMarker(FGameTexture *tex, double x, double y, int yadjust,
|
|
INTBOOL flip, double xscale, double yscale, FTranslationID translation, double alpha, uint32_t fillcolor, FRenderStyle renderstyle);
|
|
|
|
void rotatePoint(double *x, double *y);
|
|
void rotate(double *x, double *y, DAngle an);
|
|
void doFollowPlayer();
|
|
void saveScaleAndLoc();
|
|
void restoreScaleAndLoc();
|
|
void minOutWindowScale();
|
|
void activateNewScale();
|
|
void findMinMaxBoundaries();
|
|
void ClipRotatedExtents(double pivotx, double pivoty);
|
|
void ScrollParchment(double dmapx, double dmapy);
|
|
void changeWindowLoc();
|
|
void maxOutWindowScale();
|
|
void changeWindowScale(double delta);
|
|
void clearFB(const AMColor &color);
|
|
bool clipMline(mline_t *ml, fline_t *fl);
|
|
void drawMline(mline_t *ml, const AMColor &color);
|
|
void drawMline(mline_t *ml, int colorindex);
|
|
void drawGrid(int color);
|
|
void drawSubsectors();
|
|
void drawSeg(seg_t *seg, const AMColor &color);
|
|
void drawPolySeg(FPolySeg *seg, const AMColor &color);
|
|
void showSS();
|
|
void drawWalls(bool allmap);
|
|
void drawLineCharacter(const mline_t *lineguy, size_t lineguylines, double scale, DAngle angle, const AMColor &color, double x, double y);
|
|
void drawPlayers();
|
|
void drawKeys();
|
|
void drawThings();
|
|
void drawMarks();
|
|
void drawAuthorMarkers();
|
|
void drawCrosshair(const AMColor &color);
|
|
|
|
|
|
public:
|
|
bool Responder(event_t* ev, bool last) override;
|
|
void Ticker(void) override;
|
|
void Drawer(int bottom) override;
|
|
void NewResolution() override;
|
|
void LevelInit() override;
|
|
void UpdateShowAllLines() override;
|
|
void Serialize(FSerializer &arc) override;
|
|
void GoBig() override;
|
|
void ResetFollowLocation() override;
|
|
int addMark() override;
|
|
bool clearMarks() override;
|
|
DVector2 GetPosition() override;
|
|
void startDisplay() override;
|
|
|
|
};
|
|
|
|
IMPLEMENT_CLASS(DAutomap, false, false)
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// called by the coordinate drawer
|
|
//
|
|
//=============================================================================
|
|
|
|
DVector2 DAutomap::GetPosition()
|
|
{
|
|
return DVector2((m_x + m_w / 2), (m_y + m_h / 2));
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::activateNewScale ()
|
|
{
|
|
m_x += m_w/2;
|
|
m_y += m_h/2;
|
|
m_w = FTOM(f_w);
|
|
m_h = FTOM(f_h);
|
|
m_x -= m_w/2;
|
|
m_y -= m_h/2;
|
|
m_x2 = m_x + m_w;
|
|
m_y2 = m_y + m_h;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::saveScaleAndLoc ()
|
|
{
|
|
old_m_x = m_x;
|
|
old_m_y = m_y;
|
|
old_m_w = m_w;
|
|
old_m_h = m_h;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::restoreScaleAndLoc ()
|
|
{
|
|
m_w = old_m_w;
|
|
m_h = old_m_h;
|
|
if (!am_followplayer)
|
|
{
|
|
m_x = old_m_x;
|
|
m_y = old_m_y;
|
|
}
|
|
else
|
|
{
|
|
m_x = players[consoleplayer].camera->X() - m_w/2;
|
|
m_y = players[consoleplayer].camera->Y() - m_h/2;
|
|
}
|
|
m_x2 = m_x + m_w;
|
|
m_y2 = m_y + m_h;
|
|
|
|
// Change the scaling multipliers
|
|
scale_mtof = f_w / m_w;
|
|
scale_ftom = 1. / scale_mtof;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// adds a marker at the current location
|
|
//
|
|
//=============================================================================
|
|
|
|
int DAutomap::addMark ()
|
|
{
|
|
// Add a mark when default font is selected and its textures (AMMNUM?)
|
|
// are loaded. Mark is always added when custom font is selected
|
|
if (stricmp(*am_markfont, DEFAULT_FONT_NAME) != 0 || marknums[0].isValid())
|
|
{
|
|
auto m = markpointnum;
|
|
markpoints[markpointnum].x = m_x + m_w/2;
|
|
markpoints[markpointnum].y = m_y + m_h/2;
|
|
markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;
|
|
return m;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Determines bounding box of all vertices,
|
|
// sets global variables controlling zoom range.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::findMinMaxBoundaries ()
|
|
{
|
|
min_x = min_y = FLT_MAX;
|
|
max_x = max_y = FIXED_MIN;
|
|
|
|
for (auto &vert : Level->vertexes)
|
|
{
|
|
if (vert.fX() < min_x)
|
|
min_x = vert.fX();
|
|
else if (vert.fX() > max_x)
|
|
max_x = vert.fX();
|
|
|
|
if (vert.fY() < min_y)
|
|
min_y = vert.fY();
|
|
else if (vert.fY() > max_y)
|
|
max_y = vert.fY();
|
|
}
|
|
|
|
max_w = max_x - min_x;
|
|
max_h = max_y - min_y;
|
|
|
|
min_w = 2*PLAYERRADIUS; // const? never changed?
|
|
min_h = 2*PLAYERRADIUS;
|
|
|
|
calcMinMaxMtoF();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::calcMinMaxMtoF()
|
|
{
|
|
const double safe_frame = 1.0 - am_emptyspacemargin / 100.0;
|
|
double a = safe_frame * (twod->GetWidth() / max_w);
|
|
double b = safe_frame * (StatusBar->GetTopOfStatusbar() / max_h);
|
|
|
|
min_scale_mtof = a < b ? a : b;
|
|
max_scale_mtof = twod->GetHeight() / (2*PLAYERRADIUS);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::ClipRotatedExtents (double pivotx, double pivoty)
|
|
{
|
|
if (am_rotate == 0 || (am_rotate == 2 && !viewactive))
|
|
{
|
|
if (m_x + m_w/2 > max_x)
|
|
m_x = max_x - m_w/2;
|
|
else if (m_x + m_w/2 < min_x)
|
|
m_x = min_x - m_w/2;
|
|
|
|
if (m_y + m_h/2 > max_y)
|
|
m_y = max_y - m_h/2;
|
|
else if (m_y + m_h/2 < min_y)
|
|
m_y = min_y - m_h/2;
|
|
}
|
|
|
|
m_x2 = m_x + m_w;
|
|
m_y2 = m_y + m_h;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::ScrollParchment (double dmapx, double dmapy)
|
|
{
|
|
mapxstart = mapxstart - dmapx * scale_mtof;
|
|
mapystart = mapystart - dmapy * scale_mtof;
|
|
|
|
mapxstart = clamp(mapxstart, -40000., 40000.);
|
|
mapystart = clamp(mapystart, -40000., 40000.);
|
|
|
|
if (mapback.isValid())
|
|
{
|
|
auto backtex = TexMan.GetGameTexture(mapback);
|
|
|
|
if (backtex != nullptr)
|
|
{
|
|
int pwidth = int(backtex->GetDisplayWidth() * CleanXfac);
|
|
int pheight = int(backtex->GetDisplayHeight() * CleanYfac);
|
|
|
|
while(mapxstart > 0)
|
|
mapxstart -= pwidth;
|
|
while(mapxstart <= -pwidth)
|
|
mapxstart += pwidth;
|
|
while(mapystart > 0)
|
|
mapystart -= pheight;
|
|
while(mapystart <= -pheight)
|
|
mapystart += pheight;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::changeWindowLoc ()
|
|
{
|
|
if (m_paninc.x || m_paninc.y)
|
|
{
|
|
am_followplayer = false;
|
|
f_oldloc.x = FLT_MAX;
|
|
}
|
|
|
|
double oldmx = m_x, oldmy = m_y;
|
|
double incx, incy, oincx, oincy;
|
|
|
|
incx = m_paninc.x;
|
|
incy = m_paninc.y;
|
|
|
|
oincx = incx = m_paninc.x * twod->GetWidth() / 320;
|
|
oincy = incy = m_paninc.y * twod->GetHeight() / 200;
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotate(&incx, &incy, players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - DAngle::fromDeg(90.));
|
|
}
|
|
|
|
m_x += incx;
|
|
m_y += incy;
|
|
|
|
ClipRotatedExtents (oldmx + m_w/2, oldmy + m_h/2);
|
|
ScrollParchment (m_x != oldmx ? oincx : 0, m_y != oldmy ? -oincy : 0);
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::startDisplay()
|
|
{
|
|
int pnum;
|
|
|
|
f_oldloc.x = FLT_MAX;
|
|
amclock = 0;
|
|
|
|
m_paninc.x = m_paninc.y = 0;
|
|
mtof_zoommul = 1.;
|
|
|
|
m_w = FTOM(twod->GetWidth());
|
|
m_h = FTOM(twod->GetHeight());
|
|
|
|
// find player to center on initially
|
|
if (!playeringame[pnum = consoleplayer])
|
|
for (pnum=0;pnum<MAXPLAYERS;pnum++)
|
|
if (playeringame[pnum])
|
|
break;
|
|
assert(pnum >= 0 && pnum < MAXPLAYERS);
|
|
m_x = players[pnum].camera->X() - m_w/2;
|
|
m_y = players[pnum].camera->Y() - m_h/2;
|
|
changeWindowLoc();
|
|
|
|
// for saving & restoring
|
|
old_m_x = m_x;
|
|
old_m_y = m_y;
|
|
old_m_w = m_w;
|
|
old_m_h = m_h;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
bool DAutomap::clearMarks ()
|
|
{
|
|
for (int i = AM_NUMMARKPOINTS-1; i >= 0; i--)
|
|
markpoints[i].x = -1; // means empty
|
|
markpointnum = 0;
|
|
return marknums[0].isValid();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// called right after the level has been loaded
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::LevelInit ()
|
|
{
|
|
if (Level->info->MapBackground.Len() == 0)
|
|
{
|
|
mapback = TexMan.CheckForTexture("AUTOPAGE", ETextureType::MiscPatch);
|
|
}
|
|
else
|
|
{
|
|
mapback = TexMan.CheckForTexture(Level->info->MapBackground.GetChars(), ETextureType::MiscPatch);
|
|
}
|
|
|
|
clearMarks();
|
|
|
|
findMinMaxBoundaries();
|
|
scale_mtof = min_scale_mtof / 0.7;
|
|
if (scale_mtof > max_scale_mtof)
|
|
scale_mtof = min_scale_mtof;
|
|
scale_ftom = 1 / scale_mtof;
|
|
|
|
UpdateShowAllLines();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// set the window scale to the maximum size
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::minOutWindowScale ()
|
|
{
|
|
scale_mtof = min_scale_mtof;
|
|
scale_ftom = 1/ scale_mtof;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// set the window scale to the minimum size
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::maxOutWindowScale ()
|
|
{
|
|
scale_mtof = max_scale_mtof;
|
|
scale_ftom = 1 / scale_mtof;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Called right after the resolution has changed
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::NewResolution()
|
|
{
|
|
double oldmin = min_scale_mtof;
|
|
|
|
if ( oldmin == 0 )
|
|
{
|
|
return; // [SP] Not in a game, exit!
|
|
}
|
|
calcMinMaxMtoF();
|
|
scale_mtof = scale_mtof * min_scale_mtof / oldmin;
|
|
scale_ftom = 1 / scale_mtof;
|
|
if (scale_mtof < min_scale_mtof)
|
|
minOutWindowScale();
|
|
else if (scale_mtof > max_scale_mtof)
|
|
maxOutWindowScale();
|
|
f_w = twod->GetWidth();
|
|
f_h = StatusBar->GetTopOfStatusbar();
|
|
activateNewScale();
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Handle events (user inputs) in automap mode
|
|
//
|
|
//=============================================================================
|
|
|
|
bool DAutomap::Responder (event_t *ev, bool last)
|
|
{
|
|
if (automapactive && (ev->type == EV_KeyDown || ev->type == EV_KeyUp))
|
|
{
|
|
if (am_followplayer)
|
|
{
|
|
// check for am_pan* and ignore in follow mode
|
|
const char *defbind = AutomapBindings.GetBind(ev->data1);
|
|
if (defbind && !strnicmp(defbind, "+am_pan", 7)) return false;
|
|
}
|
|
|
|
bool res = C_DoKey(ev, &AutomapBindings, nullptr);
|
|
if (res && ev->type == EV_KeyUp && !last)
|
|
{
|
|
// If this is a release event we also need to check if it released a button in the main Bindings
|
|
// so that that button does not get stuck.
|
|
const char *defbind = Bindings.GetBind(ev->data1);
|
|
return (!defbind || defbind[0] != '+'); // Let G_Responder handle button releases
|
|
}
|
|
return res;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Zooming
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::changeWindowScale (double delta)
|
|
{
|
|
|
|
double mtof_zoommul;
|
|
|
|
if (am_zoomdir > 0)
|
|
{
|
|
mtof_zoommul = M_OLDZOOMIN * am_zoomdir;
|
|
}
|
|
else if (am_zoomdir < 0)
|
|
{
|
|
mtof_zoommul = M_OLDZOOMOUT / -am_zoomdir;
|
|
}
|
|
else if (buttonMap.ButtonDown(Button_AM_ZoomIn))
|
|
{
|
|
mtof_zoommul = (1 + (M_ZOOMIN - 1) * delta);
|
|
}
|
|
else if (buttonMap.ButtonDown(Button_AM_ZoomOut))
|
|
{
|
|
mtof_zoommul = (1 + (M_ZOOMOUT - 1) * delta);
|
|
}
|
|
else
|
|
{
|
|
mtof_zoommul = 1;
|
|
}
|
|
am_zoomdir = 0;
|
|
|
|
// Change the scaling multipliers
|
|
scale_mtof = scale_mtof * mtof_zoommul;
|
|
scale_ftom = 1 / scale_mtof;
|
|
|
|
if (scale_mtof < min_scale_mtof)
|
|
minOutWindowScale();
|
|
else if (scale_mtof > max_scale_mtof)
|
|
maxOutWindowScale();
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::doFollowPlayer ()
|
|
{
|
|
double sx, sy;
|
|
auto cam = players[consoleplayer].camera;
|
|
if (cam != nullptr)
|
|
{
|
|
double delta = cam->player ? cam->player->viewz - cam->Z() : cam->GetCameraHeight();
|
|
DVector3 ampos = cam->InterpolatedPosition(r_viewpoint.TicFrac);
|
|
|
|
if (f_oldloc.x != ampos.X || f_oldloc.y != ampos.Y)
|
|
{
|
|
m_x = ampos.X - m_w / 2;
|
|
m_y = ampos.Y - m_h / 2;
|
|
m_x2 = m_x + m_w;
|
|
m_y2 = m_y + m_h;
|
|
|
|
// do the parallax parchment scrolling.
|
|
sx = (ampos.X - f_oldloc.x);
|
|
sy = (f_oldloc.y - ampos.Y);
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotate(&sx, &sy, cam->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - DAngle::fromDeg(90));
|
|
}
|
|
ScrollParchment(sx, sy);
|
|
|
|
f_oldloc.x = ampos.X;
|
|
f_oldloc.y = ampos.Y;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Updates on Game Tick
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::Ticker ()
|
|
{
|
|
if (!automapactive)
|
|
return;
|
|
|
|
amclock++;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Clear automap frame buffer.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::clearFB (const AMColor &color)
|
|
{
|
|
bool drawback = mapback.isValid() && am_drawmapback != 0;
|
|
if (am_drawmapback == 2)
|
|
{
|
|
// only draw background when using a mod defined custom color set or Raven colors, if am_drawmapback is 2.
|
|
if (!am_customcolors || !AMMod.defined)
|
|
{
|
|
drawback &= (am_colorset == 3);
|
|
}
|
|
}
|
|
|
|
if (!drawback)
|
|
{
|
|
ClearRect(twod, 0, 0, f_w, f_h, -1, color.RGB);
|
|
}
|
|
else
|
|
{
|
|
auto backtex = TexMan.GetGameTexture(mapback);
|
|
if (backtex != nullptr)
|
|
{
|
|
int pwidth = int(backtex->GetDisplayWidth() * CleanXfac);
|
|
int pheight = int(backtex->GetDisplayHeight() * CleanYfac);
|
|
int x, y;
|
|
|
|
//blit the automap background to the screen.
|
|
for (y = int(mapystart); y < f_h; y += pheight)
|
|
{
|
|
for (x = int(mapxstart); x < f_w; x += pwidth)
|
|
{
|
|
DrawTexture(twod, backtex, x, y, DTA_ClipBottom, f_h, DTA_TopOffset, 0, DTA_LeftOffset, 0, DTA_DestWidth, pwidth, DTA_DestHeight, pheight, TAG_DONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Automap clipping of lines.
|
|
//
|
|
// Based on Cohen-Sutherland clipping algorithm but with a slightly
|
|
// faster reject and precalculated slopes. If the speed is needed,
|
|
// use a hash algorithm to handle the common cases.
|
|
//
|
|
//=============================================================================
|
|
|
|
bool DAutomap::clipMline (mline_t *ml, fline_t *fl)
|
|
{
|
|
enum {
|
|
LEFT =1,
|
|
RIGHT =2,
|
|
BOTTOM =4,
|
|
TOP =8
|
|
};
|
|
|
|
int outcode1 = 0;
|
|
int outcode2 = 0;
|
|
int outside;
|
|
|
|
fpoint_t tmp = { 0, 0 };
|
|
int dx;
|
|
int dy;
|
|
|
|
auto DOOUTCODE = [this](int &oc, double mx, double my)
|
|
{
|
|
oc = 0;
|
|
if (my < 0) oc |= TOP;
|
|
else if (my >= f_h) oc |= BOTTOM;
|
|
if (mx < 0) oc |= LEFT;
|
|
else if (mx >= f_w) oc |= RIGHT;
|
|
};
|
|
|
|
// do trivial rejects and outcodes
|
|
if (ml->a.y > m_y2)
|
|
outcode1 = TOP;
|
|
else if (ml->a.y < m_y)
|
|
outcode1 = BOTTOM;
|
|
|
|
if (ml->b.y > m_y2)
|
|
outcode2 = TOP;
|
|
else if (ml->b.y < m_y)
|
|
outcode2 = BOTTOM;
|
|
|
|
if (outcode1 & outcode2)
|
|
return false; // trivially outside
|
|
|
|
if (ml->a.x < m_x)
|
|
outcode1 |= LEFT;
|
|
else if (ml->a.x > m_x2)
|
|
outcode1 |= RIGHT;
|
|
|
|
if (ml->b.x < m_x)
|
|
outcode2 |= LEFT;
|
|
else if (ml->b.x > m_x2)
|
|
outcode2 |= RIGHT;
|
|
|
|
if (outcode1 & outcode2)
|
|
return false; // trivially outside
|
|
|
|
// transform to frame-buffer coordinates.
|
|
fl->a.x = CXMTOF(ml->a.x);
|
|
fl->a.y = CYMTOF(ml->a.y);
|
|
fl->b.x = CXMTOF(ml->b.x);
|
|
fl->b.y = CYMTOF(ml->b.y);
|
|
|
|
DOOUTCODE(outcode1, fl->a.x, fl->a.y);
|
|
DOOUTCODE(outcode2, fl->b.x, fl->b.y);
|
|
|
|
if (outcode1 & outcode2)
|
|
return false;
|
|
|
|
while (outcode1 | outcode2) {
|
|
// may be partially inside box
|
|
// find an outside point
|
|
if (outcode1)
|
|
outside = outcode1;
|
|
else
|
|
outside = outcode2;
|
|
|
|
// clip to each side
|
|
if (outside & TOP)
|
|
{
|
|
dy = fl->a.y - fl->b.y;
|
|
dx = fl->b.x - fl->a.x;
|
|
tmp.x = fl->a.x + Scale(dx, fl->a.y, dy);
|
|
tmp.y = 0;
|
|
}
|
|
else if (outside & BOTTOM)
|
|
{
|
|
dy = fl->a.y - fl->b.y;
|
|
dx = fl->b.x - fl->a.x;
|
|
tmp.x = fl->a.x + Scale(dx, fl->a.y - f_h, dy);
|
|
tmp.y = f_h-1;
|
|
}
|
|
else if (outside & RIGHT)
|
|
{
|
|
dy = fl->b.y - fl->a.y;
|
|
dx = fl->b.x - fl->a.x;
|
|
tmp.y = fl->a.y + Scale(dy, f_w-1 - fl->a.x, dx);
|
|
tmp.x = f_w-1;
|
|
}
|
|
else if (outside & LEFT)
|
|
{
|
|
dy = fl->b.y - fl->a.y;
|
|
dx = fl->b.x - fl->a.x;
|
|
tmp.y = fl->a.y + Scale(dy, -fl->a.x, dx);
|
|
tmp.x = 0;
|
|
}
|
|
|
|
if (outside == outcode1)
|
|
{
|
|
fl->a = tmp;
|
|
DOOUTCODE(outcode1, fl->a.x, fl->a.y);
|
|
}
|
|
else
|
|
{
|
|
fl->b = tmp;
|
|
DOOUTCODE(outcode2, fl->b.x, fl->b.y);
|
|
}
|
|
|
|
if (outcode1 & outcode2)
|
|
return false; // trivially outside
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Clip lines, draw visible parts of lines.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawMline (mline_t *ml, const AMColor &color)
|
|
{
|
|
fline_t fl;
|
|
|
|
if (clipMline (ml, &fl))
|
|
{
|
|
const int x1 = f_x + fl.a.x;
|
|
const int y1 = f_y + fl.a.y;
|
|
const int x2 = f_x + fl.b.x;
|
|
const int y2 = f_y + fl.b.y;
|
|
|
|
if (am_lineantialiasing) {
|
|
// Draw 5 lines (am_linethickness 2) or 9 lines (am_linethickness >= 3)
|
|
// slightly offset from each other, but with lower opacity
|
|
// as a bruteforce way to achieve antialiased line drawing.
|
|
const int aa_alpha_divide = am_linethickness >= 3 ? 3 : 2;
|
|
|
|
// Subtract to line thickness to compensate for the antialiasing making lines thicker.
|
|
const int aa_linethickness = max(1, am_linethickness - 2);
|
|
|
|
if (aa_linethickness >= 2) {
|
|
// Top row.
|
|
twod->AddThickLine(DVector2(x1 - 1, y1 - 1), DVector2(x2 - 1, y2 - 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1 + 1, y1 - 1), DVector2(x2 + 1, y2 - 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1, y1 - 1), DVector2(x2, y2 - 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
|
|
// Middle row.
|
|
twod->AddThickLine(DVector2(x1 - 1, y1), DVector2(x2 - 1, y2), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1 + 1, y1), DVector2(x2 + 1, y2), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1, y1), DVector2(x2, y2), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
|
|
// Bottom row.
|
|
twod->AddThickLine(DVector2(x1 - 1, y1 + 1), DVector2(x2 - 1, y2 + 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1 + 1, y1 + 1), DVector2(x2 + 1, y2 + 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddThickLine(DVector2(x1, y1 - 1), DVector2(x2, y2 - 1), aa_linethickness, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
} else {
|
|
// Use more efficient thin line drawing routine.
|
|
// Top row.
|
|
if (am_linethickness >= 3) {
|
|
// If original line thickness is 2, do not add diagonal lines to allow thin lines to be represented.
|
|
// This part is not needed for thick antialiased drawing, as original line thickness is always greater than 3.
|
|
twod->AddLine(DVector2(x1 - 1, y1 - 1), DVector2(x2 - 1, y2 - 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddLine(DVector2(x1 + 1, y1 - 1), DVector2(x2 + 1, y2 - 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
}
|
|
twod->AddLine(DVector2(x1, y1 - 1), DVector2(x2, y2 - 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
|
|
// Middle row.
|
|
twod->AddLine(DVector2(x1 - 1, y1), DVector2(x2 - 1, y2), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddLine(DVector2(x1 + 1, y1), DVector2(x2 + 1, y2), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddLine(DVector2(x1, y1), DVector2(x2, y2), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
|
|
// Bottom row.
|
|
if (am_linethickness >= 3) {
|
|
// If original line thickness is 2, do not add diagonal lines to allow thin lines to be represented.
|
|
// This part is not needed for thick antialiased drawing, as original line thickness is always greater than 3.
|
|
twod->AddLine(DVector2(x1 - 1, y1 + 1), DVector2(x2 - 1, y2 + 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
twod->AddLine(DVector2(x1 + 1, y1 + 1), DVector2(x2 + 1, y2 + 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
}
|
|
twod->AddLine(DVector2(x1, y1 - 1), DVector2(x2, y2 - 1), nullptr, color.RGB, uint8_t(am_linealpha * 255 / aa_alpha_divide));
|
|
}
|
|
} else {
|
|
if (am_linethickness >= 2) {
|
|
twod->AddThickLine(DVector2(x1, y1), DVector2(x2, y2), am_linethickness, color.RGB, uint8_t(am_linealpha * 255));
|
|
} else {
|
|
// Use more efficient thin line drawing routine.
|
|
twod->AddLine(DVector2(x1, y1), DVector2(x2, y2), nullptr, color.RGB, uint8_t(am_linealpha * 255));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DAutomap::drawMline (mline_t *ml, int colorindex)
|
|
{
|
|
drawMline(ml, AMColors[colorindex]);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Draws flat (floor/ceiling tile) aligned grid lines.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawGrid (int color)
|
|
{
|
|
double x, y;
|
|
double start, end;
|
|
mline_t ml;
|
|
double minlen, extx, exty;
|
|
double minx, miny;
|
|
auto bmaporgx = Level->blockmap.bmaporgx;
|
|
auto bmaporgy = Level->blockmap.bmaporgy;
|
|
|
|
// [RH] Calculate a minimum for how long the grid lines should be so that
|
|
// they cover the screen at any rotation.
|
|
minlen = sqrt (m_w*m_w + m_h*m_h);
|
|
extx = (minlen - m_w) / 2;
|
|
exty = (minlen - m_h) / 2;
|
|
|
|
minx = m_x;
|
|
miny = m_y;
|
|
|
|
// Figure out start of vertical gridlines
|
|
start = minx - extx;
|
|
start = ceil((start - bmaporgx) / FBlockmap::MAPBLOCKUNITS) * FBlockmap::MAPBLOCKUNITS + bmaporgx;
|
|
|
|
end = minx + minlen - extx;
|
|
|
|
// draw vertical gridlines
|
|
for (x = start; x < end; x += FBlockmap::MAPBLOCKUNITS)
|
|
{
|
|
ml.a.x = x;
|
|
ml.b.x = x;
|
|
ml.a.y = miny - exty;
|
|
ml.b.y = ml.a.y + minlen;
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&ml.a.x, &ml.a.y);
|
|
rotatePoint (&ml.b.x, &ml.b.y);
|
|
}
|
|
drawMline(&ml, color);
|
|
}
|
|
|
|
// Figure out start of horizontal gridlines
|
|
start = miny - exty;
|
|
start = ceil((start - bmaporgy) / FBlockmap::MAPBLOCKUNITS) * FBlockmap::MAPBLOCKUNITS + bmaporgy;
|
|
end = miny + minlen - exty;
|
|
|
|
// draw horizontal gridlines
|
|
for (y=start; y<end; y+=FBlockmap::MAPBLOCKUNITS)
|
|
{
|
|
ml.a.x = minx - extx;
|
|
ml.b.x = ml.a.x + minlen;
|
|
ml.a.y = y;
|
|
ml.b.y = y;
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&ml.a.x, &ml.a.y);
|
|
rotatePoint (&ml.b.x, &ml.b.y);
|
|
}
|
|
drawMline (&ml, color);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// This was previously using the variants from the renderers but with
|
|
// all globals being factored out this will become dangerouns and unpredictable
|
|
// as the original R_FakeFlat heavily depended on global variables from
|
|
// the last rendered scene.
|
|
//
|
|
//==========================================================================
|
|
|
|
sector_t * AM_FakeFlat(AActor *viewer, sector_t * sec, sector_t * dest)
|
|
{
|
|
if (sec->GetHeightSec() == nullptr) return sec;
|
|
|
|
DVector3 pos = viewer->InterpolatedPosition(r_viewpoint.TicFrac);
|
|
|
|
if (viewer->player)
|
|
{
|
|
pos.Z = viewer->player->viewz;
|
|
}
|
|
else
|
|
{
|
|
pos.Z += viewer->GetCameraHeight();
|
|
}
|
|
|
|
int in_area;
|
|
if (viewer->Sector->GetHeightSec() == nullptr)
|
|
{
|
|
in_area = 0;
|
|
}
|
|
else
|
|
{
|
|
in_area = pos.Z <= viewer->Sector->heightsec->floorplane.ZatPoint(pos) ? -1 :
|
|
(pos.Z > viewer->Sector->heightsec->ceilingplane.ZatPoint(pos) && !(viewer->Sector->heightsec->MoreFlags&SECMF_FAKEFLOORONLY)) ? 1 : 0;
|
|
}
|
|
|
|
int diffTex = (sec->heightsec->MoreFlags & SECMF_CLIPFAKEPLANES);
|
|
sector_t * s = sec->heightsec;
|
|
|
|
memcpy(dest, sec, sizeof(sector_t));
|
|
|
|
// Replace floor height with control sector's heights.
|
|
// The automap is only interested in the floor so let's skip the ceiling.
|
|
if (diffTex)
|
|
{
|
|
if (s->floorplane.CopyPlaneIfValid(&dest->floorplane, &sec->ceilingplane))
|
|
{
|
|
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
|
|
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::floor));
|
|
}
|
|
else if (s->MoreFlags & SECMF_FAKEFLOORONLY)
|
|
{
|
|
if (in_area == -1)
|
|
{
|
|
dest->Colormap = s->Colormap;
|
|
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
|
|
{
|
|
dest->lightlevel = s->lightlevel;
|
|
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
|
|
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
|
|
}
|
|
return dest;
|
|
}
|
|
return sec;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::floor));
|
|
dest->floorplane = s->floorplane;
|
|
}
|
|
|
|
if (in_area == -1)
|
|
{
|
|
dest->Colormap = s->Colormap;
|
|
dest->SetPlaneTexZQuick(sector_t::floor, sec->GetPlaneTexZ(sector_t::floor));
|
|
dest->floorplane = sec->floorplane;
|
|
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
|
|
{
|
|
dest->lightlevel = s->lightlevel;
|
|
}
|
|
|
|
dest->SetTexture(sector_t::floor, diffTex ? sec->GetTexture(sector_t::floor) : s->GetTexture(sector_t::floor), false);
|
|
dest->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
|
|
|
|
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
|
|
{
|
|
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
|
|
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
|
|
}
|
|
}
|
|
else if (in_area == 1)
|
|
{
|
|
dest->Colormap = s->Colormap;
|
|
dest->SetPlaneTexZQuick(sector_t::floor, s->GetPlaneTexZ(sector_t::ceiling));
|
|
dest->floorplane = s->ceilingplane;
|
|
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
|
|
{
|
|
dest->lightlevel = s->lightlevel;
|
|
}
|
|
|
|
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::ceiling), false);
|
|
|
|
if (s->GetTexture(sector_t::floor) != skyflatnum)
|
|
{
|
|
dest->SetTexture(sector_t::floor, s->GetTexture(sector_t::floor), false);
|
|
dest->planes[sector_t::floor].xform = s->planes[sector_t::floor].xform;
|
|
}
|
|
|
|
if (!(s->MoreFlags & SECMF_NOFAKELIGHT))
|
|
{
|
|
dest->lightlevel = s->lightlevel;
|
|
dest->SetPlaneLight(sector_t::floor, s->GetPlaneLight(sector_t::floor));
|
|
dest->ChangeFlags(sector_t::floor, -1, s->GetFlags(sector_t::floor));
|
|
}
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// AM_drawSubsectors
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawSubsectors()
|
|
{
|
|
std::vector<uint32_t> indices;
|
|
double scale = scale_mtof;
|
|
DAngle rotation;
|
|
sector_t tempsec;
|
|
int floorlight;
|
|
double scalex, scaley;
|
|
double originx, originy;
|
|
FColormap colormap;
|
|
PalEntry flatcolor;
|
|
mpoint_t originpt;
|
|
|
|
auto lm = getRealLightmode(Level, false);
|
|
bool softlightramp = !V_IsHardwareRenderer() || lm == ELightMode::Doom || lm == ELightMode::DoomDark;
|
|
|
|
auto &subsectors = Level->subsectors;
|
|
for (unsigned i = 0; i < subsectors.Size(); ++i)
|
|
{
|
|
auto sub = &subsectors[i];
|
|
if (sub->flags & SSECF_POLYORG)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ((!(sub->flags & SSECMF_DRAWN) || (sub->flags & SSECF_HOLE) || (sub->render_sector->MoreFlags & SECMF_HIDDEN)) && am_cheat == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (am_portaloverlay && sub->render_sector->PortalGroup != MapPortalGroup && sub->render_sector->PortalGroup != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Fill the points array from the subsector.
|
|
points.Resize(sub->numlines);
|
|
for (uint32_t j = 0; j < sub->numlines; ++j)
|
|
{
|
|
mpoint_t pt = { sub->firstline[j].v1->fX(),
|
|
sub->firstline[j].v1->fY() };
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint(&pt.x, &pt.y);
|
|
}
|
|
points[j].X = float(f_x + ((pt.x - m_x) * scale));
|
|
points[j].Y = float(f_y + (f_h - (pt.y - m_y) * scale));
|
|
}
|
|
// For lighting and texture determination
|
|
sector_t *sec = AM_FakeFlat(players[consoleplayer].camera, sub->render_sector, &tempsec);
|
|
floorlight = sec->GetFloorLight();
|
|
// Find texture origin.
|
|
originpt.x = -sec->GetXOffset(sector_t::floor);
|
|
originpt.y = sec->GetYOffset(sector_t::floor);
|
|
rotation = -sec->GetAngle(sector_t::floor);
|
|
// Coloring for the polygon
|
|
colormap = sec->Colormap;
|
|
|
|
FTextureID maptex = sec->GetTexture(sector_t::floor);
|
|
flatcolor = sec->SpecialColors[sector_t::floor];
|
|
|
|
scalex = sec->GetXScale(sector_t::floor);
|
|
scaley = sec->GetYScale(sector_t::floor);
|
|
|
|
if (sec->e->XFloor.ffloors.Size())
|
|
{
|
|
secplane_t *floorplane = &sec->floorplane;
|
|
|
|
// Look for the highest floor below the camera viewpoint.
|
|
// Check the center of the subsector's sector. Do not check each
|
|
// subsector separately because that might result in different planes for
|
|
// different subsectors of the same sector which is not wanted here.
|
|
// (Make the comparison in floating point to avoid overflows and improve performance.)
|
|
double secx;
|
|
double secy;
|
|
double seczb, seczt;
|
|
auto &vp = r_viewpoint;
|
|
double cmpz = vp.Pos.Z;
|
|
|
|
if (players[consoleplayer].camera && sec == players[consoleplayer].camera->Sector)
|
|
{
|
|
// For the actual camera sector use the current viewpoint as reference.
|
|
secx = vp.Pos.X;
|
|
secy = vp.Pos.Y;
|
|
}
|
|
else
|
|
{
|
|
secx = sec->centerspot.X;
|
|
secy = sec->centerspot.Y;
|
|
}
|
|
seczb = floorplane->ZatPoint(secx, secy);
|
|
seczt = sec->ceilingplane.ZatPoint(secx, secy);
|
|
|
|
for (unsigned int i = 0; i < sec->e->XFloor.ffloors.Size(); ++i)
|
|
{
|
|
F3DFloor *rover = sec->e->XFloor.ffloors[i];
|
|
if (!(rover->flags & FF_EXISTS)) continue;
|
|
if (rover->flags & (FF_FOG | FF_THISINSIDE)) continue;
|
|
if (!(rover->flags & FF_RENDERPLANES)) continue;
|
|
if (rover->alpha == 0) continue;
|
|
double roverz = rover->top.plane->ZatPoint(secx, secy);
|
|
// Ignore 3D floors that are above or below the sector itself:
|
|
// they are hidden. Since 3D floors are sorted top to bottom,
|
|
// if we get below the sector floor, we can stop.
|
|
if (roverz > seczt) continue;
|
|
if (roverz < seczb) break;
|
|
if (roverz < cmpz)
|
|
{
|
|
maptex = *(rover->top.texture);
|
|
floorplane = rover->top.plane;
|
|
sector_t *model = rover->top.model;
|
|
int selector = (rover->flags & FF_INVERTPLANES) ? sector_t::floor : sector_t::ceiling;
|
|
flatcolor = model->SpecialColors[selector];
|
|
rotation = -model->GetAngle(selector);
|
|
scalex = model->GetXScale(selector);
|
|
scaley = model->GetYScale(selector);
|
|
originpt.x = -model->GetXOffset(selector);
|
|
originpt.y = model->GetYOffset(selector);
|
|
break;
|
|
}
|
|
}
|
|
|
|
lightlist_t *light = P_GetPlaneLight(sec, floorplane, false);
|
|
floorlight = *light->p_lightlevel;
|
|
colormap = light->extra_colormap;
|
|
}
|
|
if (maptex == skyflatnum)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Apply the floor's rotation to the texture origin.
|
|
if (rotation != nullAngle)
|
|
{
|
|
rotate(&originpt.x, &originpt.y, rotation);
|
|
}
|
|
// Apply the automap's rotation to the texture origin.
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotation = rotation + DAngle::fromDeg(90.) - players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw;
|
|
rotatePoint(&originpt.x, &originpt.y);
|
|
}
|
|
originx = f_x + ((originpt.x - m_x) * scale);
|
|
originy = f_y + (f_h - (originpt.y - m_y) * scale);
|
|
|
|
// If this subsector has not actually been seen yet (because you are cheating
|
|
// to see it on the map), tint and desaturate it.
|
|
if (!(sub->flags & SSECMF_DRAWN))
|
|
{
|
|
colormap.LightColor = PalEntry(
|
|
(colormap.LightColor.r + 255) / 2,
|
|
(colormap.LightColor.g + 200) / 2,
|
|
(colormap.LightColor.b + 160) / 2);
|
|
colormap.Desaturation = 255 - (255 - colormap.Desaturation) / 4;
|
|
}
|
|
// make table based fog visible on the automap as well.
|
|
if (Level->flags & LEVEL_HASFADETABLE)
|
|
{
|
|
colormap.FadeColor = PalEntry(0, 128, 128, 128);
|
|
}
|
|
|
|
// Draw the polygon.
|
|
if (maptex.isValid())
|
|
{
|
|
// Hole filling "subsectors" are not necessarily convex so they require real triangulation.
|
|
// These things are extremely rare so performance is secondary here.
|
|
if (sub->flags & SSECF_HOLE && sub->numlines > 3)
|
|
{
|
|
using Point = std::pair<double, double>;
|
|
std::vector<std::vector<Point>> polygon;
|
|
std::vector<Point> *curPoly;
|
|
|
|
polygon.resize(1);
|
|
curPoly = &polygon.back();
|
|
curPoly->resize(points.Size());
|
|
|
|
for (unsigned i = 0; i < points.Size(); i++)
|
|
{
|
|
(*curPoly)[i] = { points[i].X, points[i].Y };
|
|
}
|
|
indices = mapbox::earcut(polygon);
|
|
}
|
|
else indices.clear();
|
|
|
|
// Use an equation similar to player sprites to determine shade
|
|
|
|
// Convert a light level into an unbounded colormap index (shade).
|
|
// Why the +12? I wish I knew, but experimentation indicates it
|
|
// is necessary in order to best reproduce Doom's original lighting.
|
|
double fadelevel;
|
|
if (softlightramp)
|
|
{
|
|
double map = (NUMCOLORMAPS * 2.) - ((floorlight + 12) * (NUMCOLORMAPS / 128.));
|
|
fadelevel = clamp((map - 12) / NUMCOLORMAPS, 0.0, 1.0);
|
|
}
|
|
else
|
|
{
|
|
// for the hardware renderer's light modes that use a linear light scale this must do the same. Otherwise the automap gets too dark.
|
|
fadelevel = 1. - clamp(floorlight, 0, 255) / 255.f;
|
|
}
|
|
|
|
twod->AddPoly(TexMan.GetGameTexture(maptex, true),
|
|
&points[0], points.Size(),
|
|
originx, originy,
|
|
scale / scalex,
|
|
scale / scaley,
|
|
rotation,
|
|
colormap,
|
|
flatcolor,
|
|
fadelevel,
|
|
indices.data(), indices.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
static int AM_CheckSecret(line_t *line)
|
|
{
|
|
if (AMColors.isValid(AMColors.SecretSectorColor))
|
|
{
|
|
if (line->frontsector != nullptr)
|
|
{
|
|
if (line->frontsector->wasSecret())
|
|
{
|
|
if (am_map_secrets!=0 && !line->frontsector->isSecret()) return 1;
|
|
if (am_map_secrets==2 && !(line->flags & ML_SECRET)) return 2;
|
|
}
|
|
}
|
|
if (line->backsector != nullptr)
|
|
{
|
|
if (line->backsector->wasSecret())
|
|
{
|
|
if (am_map_secrets!=0 && !line->backsector->isSecret()) return 1;
|
|
if (am_map_secrets==2 && !(line->flags & ML_SECRET)) return 2;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Polyobject debug stuff
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawSeg(seg_t *seg, const AMColor &color)
|
|
{
|
|
mline_t l;
|
|
l.a.x = seg->v1->fX();
|
|
l.a.y = seg->v1->fY();
|
|
l.b.x = seg->v2->fX();
|
|
l.b.y = seg->v2->fY();
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&l.a.x, &l.a.y);
|
|
rotatePoint (&l.b.x, &l.b.y);
|
|
}
|
|
drawMline(&l, color);
|
|
}
|
|
|
|
void DAutomap::drawPolySeg(FPolySeg *seg, const AMColor &color)
|
|
{
|
|
mline_t l;
|
|
l.a.x = seg->v1.pos.X;
|
|
l.a.y = seg->v1.pos.Y;
|
|
l.b.x = seg->v2.pos.X;
|
|
l.b.y = seg->v2.pos.Y;
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&l.a.x, &l.a.y);
|
|
rotatePoint (&l.b.x, &l.b.y);
|
|
}
|
|
drawMline(&l, color);
|
|
}
|
|
|
|
void DAutomap::showSS()
|
|
{
|
|
if (am_showsubsector >= 0 && (unsigned)am_showsubsector < Level->subsectors.Size())
|
|
{
|
|
AMColor yellow;
|
|
yellow.FromRGB(255,255,0);
|
|
AMColor red;
|
|
red.FromRGB(255,0,0);
|
|
|
|
subsector_t *sub = &Level->subsectors[am_showsubsector];
|
|
for (unsigned int i = 0; i < sub->numlines; i++)
|
|
{
|
|
drawSeg(sub->firstline + i, yellow);
|
|
}
|
|
|
|
for (auto &poly : Level->Polyobjects)
|
|
{
|
|
FPolyNode *pnode = poly.subsectorlinks;
|
|
|
|
while (pnode != nullptr)
|
|
{
|
|
if (pnode->subsector == sub)
|
|
{
|
|
for (unsigned j = 0; j < pnode->segs.Size(); j++)
|
|
{
|
|
drawPolySeg(&pnode->segs[j], red);
|
|
}
|
|
}
|
|
pnode = pnode->snext;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Determines if a 3D floor boundary should be drawn
|
|
//
|
|
//=============================================================================
|
|
|
|
bool AM_Check3DFloors(line_t *line)
|
|
{
|
|
TArray<F3DFloor*> &ff_front = line->frontsector->e->XFloor.ffloors;
|
|
TArray<F3DFloor*> &ff_back = line->backsector->e->XFloor.ffloors;
|
|
|
|
// No 3D floors so there's no boundary
|
|
if (ff_back.Size() == 0 && ff_front.Size() == 0) return false;
|
|
|
|
int realfrontcount = 0;
|
|
int realbackcount = 0;
|
|
|
|
for(unsigned i=0;i<ff_front.Size();i++)
|
|
{
|
|
F3DFloor *rover = ff_front[i];
|
|
if (rover->flags & FF_THISINSIDE) continue;
|
|
if (!(rover->flags & FF_EXISTS)) continue;
|
|
if (rover->alpha == 0) continue;
|
|
realfrontcount++;
|
|
}
|
|
|
|
for(unsigned i=0;i<ff_back.Size();i++)
|
|
{
|
|
F3DFloor *rover = ff_back[i];
|
|
if (rover->flags & FF_THISINSIDE) continue;
|
|
if (!(rover->flags & FF_EXISTS)) continue;
|
|
if (rover->alpha == 0) continue;
|
|
realbackcount++;
|
|
}
|
|
// if the amount of 3D floors does not match there is a boundary
|
|
if (realfrontcount != realbackcount) return true;
|
|
|
|
for(unsigned i=0;i<ff_front.Size();i++)
|
|
{
|
|
F3DFloor *rover = ff_front[i];
|
|
if (rover->flags & FF_THISINSIDE) continue; // only relevant for software rendering.
|
|
if (!(rover->flags & FF_EXISTS)) continue;
|
|
if (rover->alpha == 0) continue;
|
|
|
|
bool found = false;
|
|
for(unsigned j=0;j<ff_back.Size();j++)
|
|
{
|
|
F3DFloor *rover2 = ff_back[j];
|
|
if (rover2->flags & FF_THISINSIDE) continue; // only relevant for software rendering.
|
|
if (!(rover2->flags & FF_EXISTS)) continue;
|
|
if (rover2->alpha == 0) continue;
|
|
if (rover->model == rover2->model && rover->flags == rover2->flags)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
// At least one 3D floor in the front sector didn't have a match in the back sector so there is a boundary.
|
|
if (!found) return true;
|
|
}
|
|
// All 3D floors could be matched so let's not draw a boundary.
|
|
return false;
|
|
}
|
|
|
|
// [TP] Check whether a sector can trigger a special that satisfies the provided function.
|
|
// If found, specialptr and argsptr will be filled by the special and the arguments
|
|
// If needUseActivated is true, the special must be activated by use.
|
|
bool AM_checkSectorActions (sector_t *sector, bool (*function)(int, int *), int *specialptr, int **argsptr, bool needUseActivated)
|
|
{
|
|
// This code really stands in the way of a more generic and flexible implementation of sector actions because it makes far too many assumptions
|
|
// about their internal workings. Well, it can't be helped. Let's just hope that nobody abuses the special and the health field in a way that breaks this.
|
|
for (AActor* action = sector->SecActTarget; action; action = action->tracer)
|
|
{
|
|
if (((action->health & (SECSPAC_Use | SECSPAC_UseWall)) || false == needUseActivated)
|
|
&& (*function)(action->special, action->args)
|
|
&& !(action->flags & MF_FRIENDLY))
|
|
{
|
|
*specialptr = action->special;
|
|
*argsptr = action->args;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// [TP] Check whether there's a boundary on the provided line for a special that satisfies the provided function.
|
|
// It's a boundary if the line can activate the special or the line's bordering sectors can activate it.
|
|
// If found, specialptr and argsptr will be filled with special and args if given.
|
|
bool AM_checkSpecialBoundary (line_t &line, bool (*function)(int, int *), int *specialptr = nullptr, int **argsptr = nullptr)
|
|
{
|
|
if (specialptr == nullptr)
|
|
{
|
|
static int sink;
|
|
specialptr = &sink;
|
|
}
|
|
|
|
if (argsptr == nullptr)
|
|
{
|
|
static int *sink;
|
|
argsptr = &sink;
|
|
}
|
|
|
|
// Check if the line special qualifies for this
|
|
if ((line.activation & SPAC_PlayerActivate) && (*function)(line.special, line.args))
|
|
{
|
|
*specialptr = line.special;
|
|
*argsptr = line.args;
|
|
return true;
|
|
}
|
|
|
|
// Check sector actions in the line's front sector -- the action has to be use-activated in order to
|
|
// show up if this is a one-sided line, because the player cannot trigger sector actions by crossing
|
|
// a one-sided line (since that's impossible, duh).
|
|
if (AM_checkSectorActions(line.frontsector, function, specialptr, argsptr, line.backsector == nullptr))
|
|
return true;
|
|
|
|
// If it has a back sector, check sector actions in that.
|
|
return (line.backsector && AM_checkSectorActions(line.backsector, function, specialptr, argsptr, false));
|
|
}
|
|
|
|
bool AM_isTeleportBoundary (line_t &line)
|
|
{
|
|
return AM_checkSpecialBoundary(line, [](int special, int *)
|
|
{
|
|
return (special == Teleport ||
|
|
special == Teleport_NoFog ||
|
|
special == Teleport_ZombieChanger ||
|
|
special == Teleport_Line);
|
|
});
|
|
}
|
|
|
|
bool AM_isExitBoundary (line_t& line)
|
|
{
|
|
return AM_checkSpecialBoundary(line, [](int special, int *)
|
|
{
|
|
return (special == Teleport_NewMap ||
|
|
special == Teleport_EndGame ||
|
|
special == Exit_Normal ||
|
|
special == Exit_Secret);
|
|
});
|
|
}
|
|
|
|
bool AM_isTriggerBoundary (line_t &line)
|
|
{
|
|
return am_showtriggerlines == 1? AM_checkSpecialBoundary(line, [](int special, int *)
|
|
{
|
|
FLineSpecial *spec = P_GetLineSpecialInfo(special);
|
|
return spec != nullptr
|
|
&& spec->max_args >= 0
|
|
&& special != Door_Open
|
|
&& special != Door_Close
|
|
&& special != Door_CloseWaitOpen
|
|
&& special != Door_Raise
|
|
&& special != Door_Animated
|
|
&& special != Generic_Door;
|
|
}) : AM_checkSpecialBoundary(line, [](int special, int *)
|
|
{
|
|
FLineSpecial *spec = P_GetLineSpecialInfo(special);
|
|
return spec != nullptr
|
|
&& spec->max_args >= 0;
|
|
});
|
|
}
|
|
|
|
bool AM_isLockBoundary (line_t &line, int *lockptr = nullptr)
|
|
{
|
|
if (lockptr == nullptr)
|
|
{
|
|
static int sink;
|
|
lockptr = &sink;
|
|
}
|
|
|
|
if (line.locknumber)
|
|
{
|
|
*lockptr = line.locknumber;
|
|
return true;
|
|
}
|
|
|
|
int special;
|
|
int *args;
|
|
bool result = AM_checkSpecialBoundary(line, [](int special, int* args)
|
|
{
|
|
return special == Door_LockedRaise
|
|
|| special == ACS_LockedExecute
|
|
|| special == ACS_LockedExecuteDoor
|
|
|| (special == Door_Animated && args[3] != 0)
|
|
|| (special == Generic_Door && args[4] != 0)
|
|
|| (special == FS_Execute && args[2] != 0);
|
|
}, &special, &args);
|
|
|
|
if (result)
|
|
{
|
|
switch (special)
|
|
{
|
|
case FS_Execute:
|
|
*lockptr = args[2];
|
|
break;
|
|
|
|
case Door_Animated:
|
|
case Door_LockedRaise:
|
|
*lockptr = args[3];
|
|
break;
|
|
|
|
default:
|
|
*lockptr = args[4];
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
// Determines visible lines, draws them.
|
|
// This is LineDef based, not LineSeg based.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawWalls (bool allmap)
|
|
{
|
|
static mline_t l;
|
|
int lock, color;
|
|
|
|
int numportalgroups = am_portaloverlay ? Level->Displacements.size : 0;
|
|
|
|
for (int p = numportalgroups - 1; p >= -1; p--)
|
|
{
|
|
if (p == MapPortalGroup) continue;
|
|
|
|
|
|
for (auto &line : Level->lines)
|
|
{
|
|
int pg;
|
|
|
|
if (line.sidedef[0]->Flags & WALLF_POLYOBJ)
|
|
{
|
|
// For polyobjects we must test the surrounding sector to get the proper group.
|
|
pg = Level->PointInSector(line.v1->fX() + line.Delta().X / 2, line.v1->fY() + line.Delta().Y / 2)->PortalGroup;
|
|
}
|
|
else
|
|
{
|
|
pg = line.frontsector->PortalGroup;
|
|
}
|
|
DVector2 offset;
|
|
bool portalmode = numportalgroups > 0 && pg != MapPortalGroup;
|
|
if (pg == p)
|
|
{
|
|
offset = Level->Displacements.getOffset(pg, MapPortalGroup);
|
|
}
|
|
else if (p == -1 && (pg == MapPortalGroup || !am_portaloverlay))
|
|
{
|
|
offset = { 0, 0 };
|
|
}
|
|
else continue;
|
|
|
|
l.a.x = (line.v1->fX() + offset.X);
|
|
l.a.y = (line.v1->fY() + offset.Y);
|
|
l.b.x = (line.v2->fX() + offset.X);
|
|
l.b.y = (line.v2->fY() + offset.Y);
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint(&l.a.x, &l.a.y);
|
|
rotatePoint(&l.b.x, &l.b.y);
|
|
}
|
|
|
|
if (am_cheat != 0 || (line.flags & ML_MAPPED))
|
|
{
|
|
if ((line.flags & ML_DONTDRAW) && (am_cheat == 0 || am_cheat >= 4))
|
|
{
|
|
if (!am_showallenabled || CheckCheatmode(false))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (line.automapstyle > AMLS_Default && line.automapstyle < AMLS_COUNT
|
|
&& (am_cheat == 0 || am_cheat >= 4))
|
|
{
|
|
drawMline(&l, AUTOMAP_LINE_COLORS[line.automapstyle]);
|
|
continue;
|
|
}
|
|
|
|
if (portalmode)
|
|
{
|
|
drawMline(&l, AMColors.PortalColor);
|
|
}
|
|
else if (AM_CheckSecret(&line) == 1)
|
|
{
|
|
// map secret sectors like Boom
|
|
drawMline(&l, AMColors.SecretSectorColor);
|
|
}
|
|
else if (AM_CheckSecret(&line) == 2)
|
|
{
|
|
drawMline(&l, AMColors.UnexploredSecretColor);
|
|
}
|
|
else if (line.flags & ML_SECRET)
|
|
{ // secret door
|
|
if (am_cheat != 0 && line.backsector != nullptr)
|
|
drawMline(&l, AMColors.SecretWallColor);
|
|
else
|
|
drawMline(&l, AMColors.WallColor);
|
|
}
|
|
else if (AM_isTeleportBoundary(line) && AMColors.isValid(AMColors.IntraTeleportColor))
|
|
{ // intra-level teleporters
|
|
drawMline(&l, AMColors.IntraTeleportColor);
|
|
}
|
|
else if (AM_isExitBoundary(line) && AMColors.isValid(AMColors.InterTeleportColor))
|
|
{ // inter-level/game-ending teleporters
|
|
drawMline(&l, AMColors.InterTeleportColor);
|
|
}
|
|
else if (AM_isLockBoundary(line, &lock))
|
|
{
|
|
if (AMColors.displayLocks)
|
|
{
|
|
color = P_GetMapColorForLock(lock);
|
|
|
|
AMColor c;
|
|
|
|
if (color >= 0) c.FromRGB(RPART(color), GPART(color), BPART(color));
|
|
else c = AMColors[AMColors.LockedColor];
|
|
|
|
drawMline(&l, c);
|
|
}
|
|
else
|
|
{
|
|
drawMline(&l, AMColors.LockedColor); // locked special
|
|
}
|
|
}
|
|
else if (am_showtriggerlines
|
|
&& AMColors.isValid(AMColors.SpecialWallColor)
|
|
&& AM_isTriggerBoundary(line))
|
|
{
|
|
drawMline(&l, AMColors.SpecialWallColor); // wall with special non-door action the player can do
|
|
}
|
|
else if (line.backsector == nullptr)
|
|
{
|
|
drawMline(&l, AMColors.WallColor); // one-sided wall
|
|
}
|
|
else if (line.backsector->floorplane
|
|
!= line.frontsector->floorplane)
|
|
{
|
|
drawMline(&l, AMColors.FDWallColor); // floor level change
|
|
}
|
|
else if (line.backsector->ceilingplane
|
|
!= line.frontsector->ceilingplane)
|
|
{
|
|
drawMline(&l, AMColors.CDWallColor); // ceiling level change
|
|
}
|
|
else if (AM_Check3DFloors(&line))
|
|
{
|
|
drawMline(&l, AMColors.EFWallColor); // Extra floor border
|
|
}
|
|
else if (am_cheat > 0 && am_cheat < 4)
|
|
{
|
|
drawMline(&l, AMColors.TSWallColor);
|
|
}
|
|
}
|
|
else if (allmap || (line.flags & ML_REVEALED))
|
|
{
|
|
if ((line.flags & ML_DONTDRAW) && (am_cheat == 0 || am_cheat >= 4))
|
|
{
|
|
if (!am_showallenabled || CheckCheatmode(false))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
drawMline(&l, AMColors.NotSeenColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
// Rotation in 2D.
|
|
// Used to rotate player arrow line character.
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::rotate(double *xp, double *yp, DAngle a)
|
|
{
|
|
static DAngle angle_saved = nullAngle;
|
|
static double sinrot = 0;
|
|
static double cosrot = 1;
|
|
|
|
if (angle_saved != a)
|
|
{
|
|
angle_saved = a;
|
|
sinrot = sin(a.Radians());
|
|
cosrot = cos(a.Radians());
|
|
}
|
|
|
|
double x = *xp;
|
|
double y = *yp;
|
|
double tmpx = (x * cosrot) - (y * sinrot);
|
|
y = (x * sinrot) + (y * cosrot);
|
|
x = tmpx;
|
|
*xp = x;
|
|
*yp = y;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::rotatePoint (double *x, double *y)
|
|
{
|
|
double pivotx = m_x + m_w/2;
|
|
double pivoty = m_y + m_h/2;
|
|
*x -= pivotx;
|
|
*y -= pivoty;
|
|
rotate (x, y, -players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw + DAngle::fromDeg(90.));
|
|
*x += pivotx;
|
|
*y += pivoty;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawLineCharacter(const mline_t *lineguy, size_t lineguylines, double scale, DAngle angle, const AMColor &color, double x, double y)
|
|
{
|
|
mline_t l;
|
|
|
|
for (size_t i=0;i<lineguylines;i++)
|
|
{
|
|
l.a.x = lineguy[i].a.x;
|
|
l.a.y = lineguy[i].a.y;
|
|
|
|
if (scale)
|
|
{
|
|
l.a.x *= scale;
|
|
l.a.y *= scale;
|
|
}
|
|
|
|
if (angle != nullAngle)
|
|
rotate(&l.a.x, &l.a.y, angle);
|
|
|
|
l.a.x += x;
|
|
l.a.y += y;
|
|
|
|
l.b.x = lineguy[i].b.x;
|
|
l.b.y = lineguy[i].b.y;
|
|
|
|
if (scale)
|
|
{
|
|
l.b.x *= scale;
|
|
l.b.y *= scale;
|
|
}
|
|
|
|
if (angle != nullAngle)
|
|
rotate(&l.b.x, &l.b.y, angle);
|
|
|
|
l.b.x += x;
|
|
l.b.y += y;
|
|
|
|
drawMline(&l, color);
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawPlayers ()
|
|
{
|
|
if (am_cheat >= 2 && am_cheat != 4 && am_showthingsprites > 0)
|
|
{
|
|
// Player sprites are drawn with the others
|
|
return;
|
|
}
|
|
|
|
mpoint_t pt;
|
|
DAngle angle;
|
|
int i;
|
|
|
|
if (!multiplayer)
|
|
{
|
|
mline_t *arrow;
|
|
int numarrowlines;
|
|
|
|
double vh = players[consoleplayer].viewheight;
|
|
DVector2 pos = players[consoleplayer].camera->InterpolatedPosition(r_viewpoint.TicFrac).XY();
|
|
pt.x = pos.X;
|
|
pt.y = pos.Y;
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
angle = DAngle::fromDeg(90.);
|
|
rotatePoint (&pt.x, &pt.y);
|
|
}
|
|
else
|
|
{
|
|
angle = players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw;
|
|
}
|
|
|
|
if (am_cheat != 0 && CheatMapArrow.Size() > 0)
|
|
{
|
|
arrow = &CheatMapArrow[0];
|
|
numarrowlines = CheatMapArrow.Size();
|
|
}
|
|
else
|
|
{
|
|
arrow = &MapArrow[0];
|
|
numarrowlines = MapArrow.Size();
|
|
}
|
|
drawLineCharacter(arrow, numarrowlines, 0, angle, AMColors[AMColors.YourColor], pt.x, pt.y);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < MAXPLAYERS; i++)
|
|
{
|
|
player_t *p = &players[i];
|
|
AMColor color;
|
|
|
|
if (!playeringame[i] || p->mo == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// We don't always want to show allies on the automap.
|
|
if (dmflags2 & DF2_NO_AUTOMAP_ALLIES && i != consoleplayer)
|
|
continue;
|
|
|
|
if (deathmatch && !demoplayback &&
|
|
!p->mo->IsTeammate (players[consoleplayer].mo) &&
|
|
p != players[consoleplayer].camera->player)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (p->mo->Alpha < 1.)
|
|
{
|
|
color = AMColors[AMColors.AlmostBackgroundColor];
|
|
}
|
|
else
|
|
{
|
|
float h, s, v, r, g, b;
|
|
|
|
D_GetPlayerColor (i, &h, &s, &v, nullptr);
|
|
HSVtoRGB (&r, &g, &b, h, s, v);
|
|
|
|
color.FromRGB(clamp (int(r*255.f),0,255), clamp (int(g*255.f),0,255), clamp (int(b*255.f),0,255));
|
|
}
|
|
|
|
if (p->mo != nullptr)
|
|
{
|
|
DVector3 pos = p->mo->PosRelative(MapPortalGroup);
|
|
pt.x = pos.X;
|
|
pt.y = pos.Y;
|
|
|
|
angle = p->mo->InterpolatedAngles(r_viewpoint.TicFrac).Yaw;
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&pt.x, &pt.y);
|
|
angle -= players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - DAngle::fromDeg(90.);
|
|
}
|
|
|
|
drawLineCharacter(&MapArrow[0], MapArrow.Size(), 0, angle, color, pt.x, pt.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawKeys ()
|
|
{
|
|
AMColor color;
|
|
mpoint_t p;
|
|
DAngle angle;
|
|
|
|
auto it = Level->GetThinkerIterator<AActor>(NAME_Key);
|
|
AActor *key;
|
|
|
|
while ((key = it.Next()) != nullptr)
|
|
{
|
|
DVector3 pos = key->PosRelative(MapPortalGroup);
|
|
p.x = pos.X;
|
|
p.y = pos.Y;
|
|
|
|
angle = key->InterpolatedAngles(r_viewpoint.TicFrac).Yaw;
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&p.x, &p.y);
|
|
angle += -players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw + DAngle::fromDeg(90.);
|
|
}
|
|
|
|
if (key->flags & MF_SPECIAL)
|
|
{
|
|
// Find the key's own color.
|
|
// Only works correctly if single-key locks have lower numbers than any-key locks.
|
|
// That is the case for all default keys, however.
|
|
int c = P_GetMapColorForKey(key);
|
|
|
|
if (c >= 0) color.FromRGB(RPART(c), GPART(c), BPART(c));
|
|
else color = AMColors[AMColors.ThingColor_CountItem];
|
|
drawLineCharacter(&EasyKey[0], EasyKey.Size(), 0, nullAngle, color, p.x, p.y);
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
void DAutomap::drawThings ()
|
|
{
|
|
AMColor color;
|
|
AActor* t;
|
|
mpoint_t p;
|
|
DAngle angle;
|
|
|
|
for (auto &sec : Level->sectors)
|
|
{
|
|
t = sec.thinglist;
|
|
while (t)
|
|
{
|
|
if (am_cheat > 0 || !(t->flags6 & MF6_NOTONAUTOMAP)
|
|
|| (am_thingrenderstyles && !(t->renderflags & RF_INVISIBLE) && !(t->flags6 & MF6_NOTONAUTOMAP)))
|
|
{
|
|
DVector3 fracPos = t->InterpolatedPosition(r_viewpoint.TicFrac);
|
|
FVector2 pos = FVector2(float(fracPos.X),float(fracPos.Y)) + FVector2(t->Level->Displacements.getOffset(sec.PortalGroup, MapPortalGroup)) + FVector2(t->AutomapOffsets);
|
|
p.x = pos.X;
|
|
p.y = pos.Y;
|
|
|
|
if (am_showthingsprites > 0 && t->sprite > 0)
|
|
{
|
|
FGameTexture *texture = nullptr;
|
|
spriteframe_t *frame;
|
|
int rotation = 0;
|
|
|
|
// try all modes backwards until a valid texture has been found.
|
|
for(int show = am_showthingsprites; show > 0 && texture == nullptr; show--)
|
|
{
|
|
const spritedef_t& sprite = sprites[t->sprite];
|
|
const size_t spriteIndex = sprite.spriteframes + (show > 1 ? t->frame : 0);
|
|
|
|
frame = &SpriteFrames[spriteIndex];
|
|
DAngle angle = DAngle::fromDeg(270.) - t->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - t->SpriteRotation;
|
|
if (frame->Texture[0] != frame->Texture[1]) angle += DAngle::fromDeg(180. / 16);
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
angle += players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw - DAngle::fromDeg(90.);
|
|
}
|
|
rotation = int((angle.Normalized360() * (16. / 360.)).Degrees());
|
|
|
|
const FTextureID textureID = frame->Texture[show > 2 ? rotation : 0];
|
|
texture = TexMan.GetGameTexture(textureID, true);
|
|
}
|
|
|
|
if (texture == nullptr) goto drawTriangle; // fall back to standard display if no sprite can be found.
|
|
|
|
const double spriteXScale = (t->Scale.X * (10. / 16.) * scale_mtof);
|
|
const double spriteYScale = (t->Scale.Y * (10. / 16.) * scale_mtof);
|
|
|
|
if (am_thingrenderstyles) DrawMarker(texture, p.x, p.y, 0, !!(frame->Flip & (1 << rotation)),
|
|
spriteXScale, spriteYScale, t->Translation, t->Alpha, t->fillcolor, t->RenderStyle);
|
|
else DrawMarker(texture, p.x, p.y, 0, !!(frame->Flip & (1 << rotation)),
|
|
spriteXScale, spriteYScale, t->Translation, 1., 0, LegacyRenderStyles[STYLE_Normal]);
|
|
}
|
|
else
|
|
{
|
|
drawTriangle:
|
|
angle = t->InterpolatedAngles(r_viewpoint.TicFrac).Yaw;
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&p.x, &p.y);
|
|
angle += -players[consoleplayer].camera->InterpolatedAngles(r_viewpoint.TicFrac).Yaw + DAngle::fromDeg(90.);
|
|
}
|
|
|
|
color = AMColors[AMColors.ThingColor];
|
|
|
|
// use separate colors for special thing types
|
|
if (t->flags3&MF3_ISMONSTER && !(t->flags&MF_CORPSE))
|
|
{
|
|
if (t->flags & MF_FRIENDLY) color = AMColors[AMColors.ThingColor_Friend];
|
|
else if (!(t->flags & MF_COUNTKILL)) color = AMColors[AMColors.ThingColor_NocountMonster];
|
|
else color = AMColors[AMColors.ThingColor_Monster];
|
|
}
|
|
else if (t->flags&MF_SPECIAL)
|
|
{
|
|
// Find the key's own color.
|
|
// Only works correctly if single-key locks have lower numbers than any-key locks.
|
|
// That is the case for all default keys, however.
|
|
if (t->IsKindOf(NAME_Key))
|
|
{
|
|
if (G_SkillProperty(SKILLP_EasyKey) || am_showkeys_always)
|
|
{
|
|
// Already drawn by AM_drawKeys(), so don't draw again
|
|
color.RGB = 0;
|
|
}
|
|
else if (am_showkeys)
|
|
{
|
|
int c = P_GetMapColorForKey(t);
|
|
|
|
if (c >= 0) color.FromRGB(RPART(c), GPART(c), BPART(c));
|
|
else color = AMColors[AMColors.ThingColor_CountItem];
|
|
drawLineCharacter(&CheatKey[0], CheatKey.Size(), 0, nullAngle, color, p.x, p.y);
|
|
color.RGB = 0;
|
|
}
|
|
else
|
|
{
|
|
color = AMColors[AMColors.ThingColor_Item];
|
|
}
|
|
}
|
|
else if (t->flags&MF_COUNTITEM)
|
|
color = AMColors[AMColors.ThingColor_CountItem];
|
|
else
|
|
color = AMColors[AMColors.ThingColor_Item];
|
|
}
|
|
|
|
if (color.isValid())
|
|
{
|
|
drawLineCharacter(thintriangle_guy.data(), thintriangle_guy.size(), 16, angle, color, p.x, p.y);
|
|
}
|
|
|
|
if (am_cheat == 3 || am_cheat == 6)
|
|
{
|
|
static const mline_t box[4] =
|
|
{
|
|
{ { -1, -1 }, { 1, -1 } },
|
|
{ { 1, -1 }, { 1, 1 } },
|
|
{ { 1, 1 }, { -1, 1 } },
|
|
{ { -1, 1 }, { -1, -1 } },
|
|
};
|
|
|
|
drawLineCharacter (box, 4, t->radius, angle - t->InterpolatedAngles(r_viewpoint.TicFrac).Yaw, color, p.x, p.y);
|
|
}
|
|
}
|
|
}
|
|
t = t->snext;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::DrawMarker (FGameTexture *tex, double x, double y, int yadjust,
|
|
INTBOOL flip, double xscale, double yscale, FTranslationID translation, double alpha, uint32_t fillcolor, FRenderStyle renderstyle)
|
|
{
|
|
if (tex == nullptr || !tex->isValid())
|
|
{
|
|
return;
|
|
}
|
|
if (xscale < 0)
|
|
{
|
|
flip = !flip;
|
|
xscale = -xscale;
|
|
}
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&x, &y);
|
|
}
|
|
DrawTexture(twod, tex, CXMTOF(x) + f_x, CYMTOF(y) + yadjust + f_y,
|
|
DTA_DestWidthF, tex->GetDisplayWidth() * CleanXfac * xscale,
|
|
DTA_DestHeightF, tex->GetDisplayHeight() * CleanYfac * yscale,
|
|
DTA_ClipTop, f_y,
|
|
DTA_ClipBottom, f_y + f_h,
|
|
DTA_ClipLeft, f_x,
|
|
DTA_ClipRight, f_x + f_w,
|
|
DTA_FlipX, flip,
|
|
DTA_TranslationIndex, translation.index(),
|
|
DTA_Alpha, alpha,
|
|
DTA_FillColor, fillcolor,
|
|
DTA_RenderStyle, renderstyle.AsDWORD,
|
|
TAG_DONE);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawMarks ()
|
|
{
|
|
FFont* font;
|
|
bool fontloaded = false;
|
|
|
|
for (int i = 0; i < AM_NUMMARKPOINTS; i++)
|
|
{
|
|
if (markpoints[i].x != -1)
|
|
{
|
|
if (!fontloaded)
|
|
{
|
|
font = stricmp(*am_markfont, DEFAULT_FONT_NAME) == 0 ? nullptr : V_GetFont(am_markfont);
|
|
fontloaded = true;
|
|
}
|
|
|
|
if (font == nullptr)
|
|
{
|
|
DrawMarker(TexMan.GetGameTexture(marknums[i], true), markpoints[i].x, markpoints[i].y, -3, 0,
|
|
1, 1, NO_TRANSLATION, 1, 0, LegacyRenderStyles[STYLE_Normal]);
|
|
}
|
|
else
|
|
{
|
|
char numstr[2] = { char('0' + i), 0 };
|
|
double x = markpoints[i].x;
|
|
double y = markpoints[i].y;
|
|
|
|
if (am_rotate == 1 || (am_rotate == 2 && viewactive))
|
|
{
|
|
rotatePoint (&x, &y);
|
|
}
|
|
|
|
DrawText(twod, font, am_markcolor, CXMTOF(x), CYMTOF(y), numstr, TAG_DONE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawAuthorMarkers ()
|
|
{
|
|
// [RH] Draw any actors derived from AMapMarker on the automap.
|
|
// If args[0] is 0, then the actor's sprite is drawn at its own location.
|
|
// Otherwise, its sprite is drawn at the location of any actors whose TIDs match args[0].
|
|
auto it = Level->GetThinkerIterator<AActor>(NAME_MapMarker, STAT_MAPMARKER);
|
|
AActor *mark;
|
|
|
|
while ((mark = it.Next()) != nullptr)
|
|
{
|
|
if (mark->flags2 & MF2_DORMANT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FTextureID picnum;
|
|
FGameTexture *tex;
|
|
uint16_t flip = 0;
|
|
|
|
if (mark->picnum.isValid())
|
|
{
|
|
tex = TexMan.GetGameTexture(mark->picnum, true);
|
|
if (tex->GetRotations() != 0xFFFF)
|
|
{
|
|
spriteframe_t *sprframe = &SpriteFrames[tex->GetRotations()];
|
|
picnum = sprframe->Texture[0];
|
|
flip = sprframe->Flip & 1;
|
|
tex = TexMan.GetGameTexture(picnum);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spritedef_t *sprdef = &sprites[mark->sprite];
|
|
if (mark->frame >= sprdef->numframes)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
spriteframe_t *sprframe = &SpriteFrames[sprdef->spriteframes + mark->frame];
|
|
picnum = sprframe->Texture[0];
|
|
flip = sprframe->Flip & 1;
|
|
tex = TexMan.GetGameTexture(picnum);
|
|
}
|
|
}
|
|
auto it = Level->GetActorIterator(mark->args[0]);
|
|
AActor *marked = mark->args[0] == 0 ? mark : it.Next();
|
|
|
|
double xscale = mark->Scale.X;
|
|
double yscale = mark->Scale.Y;
|
|
// [MK] scale with automap zoom if args[2] is 1, otherwise keep a constant scale
|
|
if (mark->args[2] == 1)
|
|
{
|
|
xscale = MTOF(xscale);
|
|
yscale = MTOF(yscale);
|
|
}
|
|
|
|
while (marked != nullptr)
|
|
{
|
|
if (mark->args[1] == 0 || (mark->args[1] == 1 && (marked->subsector->flags & SSECMF_DRAWN)))
|
|
{
|
|
DrawMarker (tex, marked->X(), marked->Y(), 0, flip, xscale, yscale, mark->Translation,
|
|
mark->Alpha, mark->fillcolor, mark->RenderStyle);
|
|
}
|
|
marked = mark->args[0] != 0 ? it.Next() : nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::drawCrosshair (const AMColor &color)
|
|
{
|
|
twod->AddPixel(f_w/2, (f_h+1)/2, color.RGB);
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::Drawer (int bottom)
|
|
{
|
|
static uint64_t LastMS = 0;
|
|
// Use a delta to zoom/pan at a constant speed regardless of current FPS
|
|
uint64_t ms = screen->FrameTime;
|
|
double delta = (ms - LastMS) * 0.001;
|
|
|
|
if (!automapactive)
|
|
return;
|
|
|
|
if (am_followplayer)
|
|
{
|
|
doFollowPlayer();
|
|
}
|
|
else
|
|
{
|
|
m_paninc.x = m_paninc.y = 0;
|
|
if (buttonMap.ButtonDown(Button_AM_PanLeft))
|
|
m_paninc.x -= FTOM(F_PANINC) * delta * TICRATE;
|
|
if (buttonMap.ButtonDown(Button_AM_PanRight))
|
|
m_paninc.x += FTOM(F_PANINC) * delta * TICRATE;
|
|
if (buttonMap.ButtonDown(Button_AM_PanUp))
|
|
m_paninc.y += FTOM(F_PANINC) * delta * TICRATE;
|
|
if (buttonMap.ButtonDown(Button_AM_PanDown))
|
|
m_paninc.y -= FTOM(F_PANINC) * delta * TICRATE;
|
|
}
|
|
|
|
// Change the zoom if necessary
|
|
if (buttonMap.ButtonDown(Button_AM_ZoomIn) || buttonMap.ButtonDown(Button_AM_ZoomOut) || am_zoomdir != 0)
|
|
changeWindowScale(delta);
|
|
|
|
// Change x,y location
|
|
changeWindowLoc();
|
|
|
|
bool allmap = (Level->flags2 & LEVEL2_ALLMAP) != 0;
|
|
bool allthings = allmap && players[consoleplayer].mo->FindInventory(NAME_PowerScanner, true) != nullptr;
|
|
|
|
if (am_portaloverlay)
|
|
{
|
|
sector_t *sec;
|
|
double vh = players[consoleplayer].viewheight;
|
|
players[consoleplayer].camera->GetPortalTransition(vh, &sec);
|
|
MapPortalGroup = sec->PortalGroup;
|
|
}
|
|
else MapPortalGroup = 0;
|
|
AM_initColors (viewactive);
|
|
|
|
if (!viewactive)
|
|
{
|
|
// [RH] Set f_? here now to handle automap overlaying
|
|
// and view size adjustments.
|
|
f_x = f_y = 0;
|
|
f_w = twod->GetWidth ();
|
|
f_h = bottom;
|
|
|
|
clearFB(AMColors[AMColors.Background]);
|
|
}
|
|
else
|
|
{
|
|
f_x = viewwindowx;
|
|
f_y = viewwindowy;
|
|
f_w = viewwidth;
|
|
f_h = viewheight;
|
|
}
|
|
activateNewScale();
|
|
|
|
if (am_textured && !viewactive)
|
|
drawSubsectors();
|
|
|
|
if (am_showgrid)
|
|
drawGrid(AMColors.GridColor);
|
|
|
|
drawWalls(allmap);
|
|
drawPlayers();
|
|
if (G_SkillProperty(SKILLP_EasyKey) || am_showkeys_always)
|
|
drawKeys();
|
|
if ((am_cheat >= 2 && am_cheat != 4) || allthings)
|
|
drawThings();
|
|
|
|
drawAuthorMarkers();
|
|
|
|
if (!viewactive)
|
|
drawCrosshair(AMColors[AMColors.XHairColor]);
|
|
|
|
drawMarks();
|
|
showSS();
|
|
|
|
LastMS = ms;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::Serialize(FSerializer &arc)
|
|
{
|
|
Super::Serialize(arc);
|
|
// This only stores those variables which do not get set each time the automap is either activated or drawn.
|
|
// Especially the screen coordinates can not be brought over because the display settings may have changed.
|
|
arc("markpointnum", markpointnum)
|
|
.Array("markpoints", &markpoints[0].x, AM_NUMMARKPOINTS * 2) // write as a double array.
|
|
("scale_mtof", scale_mtof)
|
|
("scale_ftom", scale_ftom)
|
|
("bigstate", bigstate)
|
|
("min_x", min_x)
|
|
("min_y", min_y)
|
|
("max_x", max_x)
|
|
("max_y", max_y)
|
|
("min_w", min_w)
|
|
("min_h", min_h)
|
|
("max_w", max_w)
|
|
("max_h", max_h)
|
|
("min_scale_mtof", min_scale_mtof)
|
|
("max_scale_mtof", max_scale_mtof)
|
|
("mapback", mapback)
|
|
("level", Level);
|
|
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void DAutomap::UpdateShowAllLines()
|
|
{
|
|
int val = am_showalllines;
|
|
int flagged = 0;
|
|
int total = 0;
|
|
if (val > 0 && Level->lines.Size() > 0)
|
|
{
|
|
for (auto &line : Level->lines)
|
|
{
|
|
// disregard intra-sector lines
|
|
if (line.frontsector == line.backsector) continue;
|
|
|
|
// disregard control sectors for deep water
|
|
if (line.frontsector->e->FakeFloor.Sectors.Size() > 0) continue;
|
|
|
|
// disregard control sectors for 3D-floors
|
|
if (line.frontsector->e->XFloor.attached.Size() > 0) continue;
|
|
|
|
total++;
|
|
if (line.flags & ML_DONTDRAW) flagged++;
|
|
}
|
|
am_showallenabled = (flagged * 100 / total >= val);
|
|
}
|
|
else if (val == 0)
|
|
{
|
|
am_showallenabled = true;
|
|
}
|
|
else
|
|
{
|
|
am_showallenabled = false;
|
|
}
|
|
}
|
|
|
|
void DAutomap::GoBig()
|
|
{
|
|
bigstate = !bigstate;
|
|
if (bigstate)
|
|
{
|
|
saveScaleAndLoc();
|
|
minOutWindowScale();
|
|
}
|
|
else
|
|
restoreScaleAndLoc();
|
|
}
|
|
|
|
void DAutomap::ResetFollowLocation()
|
|
{
|
|
f_oldloc.x = FLT_MAX;
|
|
}
|
|
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void AM_Stop()
|
|
{
|
|
automapactive = false;
|
|
viewactive = true;
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
void AM_ToggleMap()
|
|
{
|
|
if (gamestate != GS_LEVEL)
|
|
return;
|
|
|
|
// Don't activate the automap if we're not allowed to use it.
|
|
if (dmflags2 & DF2_NO_AUTOMAP)
|
|
return;
|
|
|
|
// ... or if there is no automap.
|
|
if (!primaryLevel || !primaryLevel->automap)
|
|
return;
|
|
|
|
if (!automapactive)
|
|
{
|
|
// Reset AM buttons
|
|
buttonMap.ClearButton(Button_AM_PanLeft);
|
|
buttonMap.ClearButton(Button_AM_PanRight);
|
|
buttonMap.ClearButton(Button_AM_PanUp);
|
|
buttonMap.ClearButton(Button_AM_PanDown);
|
|
buttonMap.ClearButton(Button_AM_ZoomIn);
|
|
buttonMap.ClearButton(Button_AM_ZoomOut);
|
|
|
|
primaryLevel->automap->startDisplay();
|
|
automapactive = true;
|
|
viewactive = (am_overlay != 0.f);
|
|
}
|
|
else
|
|
{
|
|
if (am_overlay == 1 && viewactive)
|
|
{
|
|
viewactive = false;
|
|
}
|
|
else
|
|
{
|
|
AM_Stop();
|
|
}
|
|
}
|
|
}
|
|
|
|
//=============================================================================
|
|
//
|
|
//
|
|
//
|
|
//=============================================================================
|
|
|
|
DAutomapBase *AM_Create(FLevelLocals *Level)
|
|
{
|
|
auto am = Create<DAutomap>();
|
|
am->Level = Level;
|
|
return am;
|
|
}
|