//------------------------------------------------------------------------- /* Copyright (C) 1996, 2003 - 3D Realms Entertainment Copyright (C) 2020 - Christoph Oelckers This file is part of Duke Nukem 3D version 1.5 - Atomic Edition Duke Nukem 3D 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Original Source: 1996 - Todd Replogle Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms Modifications for JonoF's port by Jonathon Fowler (jf@jonof.id.au) */ //------------------------------------------------------------------------- #include "automap.h" #include "cstat.h" #include "c_dispatch.h" #include "c_cvars.h" #include "gstrings.h" #include "printf.h" #include "serializer.h" #include "v_2ddrawer.h" #include "earcut.hpp" #include "buildtiles.h" #include "d_event.h" #include "c_bind.h" #include "gamestate.h" #include "gamecontrol.h" #include "quotemgr.h" #include "v_video.h" #include "gamestruct.h" #include "v_draw.h" CVAR(Bool, am_followplayer, true, CVAR_ARCHIVE) CVAR(Bool, am_rotate, true, CVAR_ARCHIVE) CVAR(Bool, am_textfont, false, CVAR_ARCHIVE) CVAR(Bool, am_showlabel, false, CVAR_ARCHIVE) CVAR(Bool, am_nameontop, false, CVAR_ARCHIVE) int automapMode; static float am_zoomdir; int follow_x = INT_MAX, follow_y = INT_MAX, follow_a = INT_MAX; static int gZoom = 768; bool automapping; bool gFullMap; FixedBitArray show2dsector; FixedBitArray show2dwall; FixedBitArray show2dsprite; static int x_min_bound = INT_MAX, y_min_bound, x_max_bound, y_max_bound; CVAR(Color, am_twosidedcolor, 0xaaaaaa, CVAR_ARCHIVE) CVAR(Color, am_onesidedcolor, 0xaaaaaa, CVAR_ARCHIVE) CVAR(Color, am_playercolor, 0xaaaaaa, CVAR_ARCHIVE) CVAR(Color, am_ovtwosidedcolor, 0xaaaaaa, CVAR_ARCHIVE) CVAR(Color, am_ovonesidedcolor, 0xaaaaaa, CVAR_ARCHIVE) CVAR(Color, am_ovplayercolor, 0xaaaaaa, CVAR_ARCHIVE) //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- CCMD(allmap) { if (!CheckCheatmode(true, false)) { gFullMap = !gFullMap; Printf("%s\n", GStrings(gFullMap ? "SHOW MAP: ON" : "SHOW MAP: OFF")); } } CCMD(togglemap) { if (gamestate == GS_LEVEL) { automapMode++; if (automapMode == am_count) automapMode = am_off; if (isBlood() && automapMode == am_overlay) automapMode = am_full; // todo: investigate if this can be re-enabled } } CCMD(togglefollow) { am_followplayer = !am_followplayer; auto msg = quoteMgr.GetQuote(am_followplayer ? 84 : 83); if (!msg || !*msg) msg = am_followplayer ? GStrings("FOLLOW MODE ON") : GStrings("FOLLOW MODE Off"); Printf(PRINT_NOTIFY, "%s\n", msg); if (am_followplayer) follow_x = INT_MAX; } CCMD(togglerotate) { am_rotate = !am_rotate; auto msg = am_rotate ? GStrings("TXT_ROTATE_ON") : GStrings("TXT_ROTATE_OFF"); Printf(PRINT_NOTIFY, "%s\n", msg); } CCMD(am_zoom) { if (argv.argc() >= 2) { am_zoomdir = (float)atof(argv[1]); } } //========================================================================== // // AM_Responder // Handle automap exclusive bindings. // //========================================================================== bool AM_Responder(event_t* ev, bool last) { if (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; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- static void CalcMapBounds() { x_min_bound = INT_MAX; y_min_bound = INT_MAX; x_max_bound = INT_MIN; y_max_bound = INT_MIN; for (int i = 0; i < numwalls; i++) { // get map min and max coordinates if (wall[i].x < x_min_bound) x_min_bound = wall[i].x; if (wall[i].y < y_min_bound) y_min_bound = wall[i].y; if (wall[i].x > x_max_bound) x_max_bound = wall[i].x; if (wall[i].y > y_max_bound) y_max_bound = wall[i].y; } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void AutomapControl() { static int nonsharedtimer; int ms = screen->FrameTime; int interval; int panvert = 0, panhorz = 0; if (nonsharedtimer > 0 || ms < nonsharedtimer) { interval = ms - nonsharedtimer; } else { interval = 0; } nonsharedtimer = screen->FrameTime; if (System_WantGuiCapture()) return; if (automapMode != am_off) { const int keymove = 4; if (am_zoomdir > 0) { gZoom = xs_CRoundToInt(gZoom * am_zoomdir); } else if (am_zoomdir < 0) { gZoom = xs_CRoundToInt(gZoom / -am_zoomdir); } am_zoomdir = 0; double j = interval * 35. / gZoom; if (buttonMap.ButtonDown(gamefunc_Enlarge_Screen)) gZoom += (int)MulScaleF(j, max(gZoom, 256), 6); if (buttonMap.ButtonDown(gamefunc_Shrink_Screen)) gZoom -= (int)MulScaleF(j, max(gZoom, 256), 6); gZoom = clamp(gZoom, 48, 2048); if (!am_followplayer) { if (buttonMap.ButtonDown(gamefunc_AM_PanLeft)) panhorz += keymove; if (buttonMap.ButtonDown(gamefunc_AM_PanRight)) panhorz -= keymove; if (buttonMap.ButtonDown(gamefunc_AM_PanUp)) panvert += keymove; if (buttonMap.ButtonDown(gamefunc_AM_PanDown)) panvert -= keymove; int momx = MulScale(panvert, bcos(follow_a), 9); int momy = MulScale(panvert, bsin(follow_a), 9); momx += MulScale(panhorz, bsin(follow_a), 9); momy += MulScale(panhorz, -bcos(follow_a), 9); follow_x += int(momx * j); follow_y += int(momy * j); if (x_min_bound == INT_MAX) CalcMapBounds(); follow_x = clamp(follow_x, x_min_bound, x_max_bound); follow_y = clamp(follow_y, y_min_bound, y_max_bound); } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void SerializeAutomap(FSerializer& arc) { if (arc.BeginObject("automap")) { arc("automapping", automapping) ("fullmap", gFullMap) // Only store what's needed. Unfortunately for sprites it is not that easy .SerializeMemory("mappedsectors", show2dsector.Storage(), (numsectors + 7) / 8) .SerializeMemory("mappedwalls", show2dwall.Storage(), (numwalls + 7) / 8) .SerializeMemory("mappedsprites", show2dsprite.Storage(), MAXSPRITES / 8) .EndObject(); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void ClearAutomap() { show2dsector.Zero(); show2dwall.Zero(); show2dsprite.Zero(); x_min_bound = INT_MAX; } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void MarkSectorSeen(int i) { if (i >= 0) { show2dsector.Set(i); auto wal = &wall[sector[i].wallptr]; for (int j = sector[i].wallnum; j > 0; j--, wal++) { i = wal->nextsector; if (i < 0) continue; if (wal->cstat & 0x0071) continue; if (wall[wal->nextwall].cstat & 0x0071) continue; if (sector[i].lotag == 32767) continue; if (sector[i].ceilingz >= sector[i].floorz) continue; show2dsector.Set(i); } } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void drawlinergb(int32_t x1, int32_t y1, int32_t x2, int32_t y2, PalEntry p) { twod->AddLine(x1 / 4096.f, y1 / 4096.f, x2 / 4096.f, y2 / 4096.f, windowxy1.x, windowxy1.y, windowxy2.x, windowxy2.y, p); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- PalEntry RedLineColor() { // todo: // Blood uses palette index 12 (99,99,99) // Exhumed uses palette index 111 (roughly 170,170,170) but darkens the line in overlay mode the farther it is away from the player in vertical direction. // Shadow Warrior uses palette index 152 in overlay mode and index 12 in full map mode. (152: 84, 88, 40) return automapMode == am_overlay? *am_ovtwosidedcolor : *am_twosidedcolor; } PalEntry WhiteLineColor() { // todo: // Blood uses palette index 24 // Exhumed uses palette index 111 (roughly 170,170,170) but darkens the line in overlay mode the farther it is away from the player in vertical direction. // Shadow Warrior uses palette index 24 (60,60,60) return automapMode == am_overlay ? *am_ovonesidedcolor : *am_onesidedcolor; } PalEntry PlayerLineColor() { return automapMode == am_overlay ? *am_ovplayercolor : *am_playercolor; } CCMD(printpalcol) { if (argv.argc() < 2) return; int i = atoi(argv[1]); Printf("%d, %d, %d\n", GPalette.BaseColors[i].r, GPalette.BaseColors[i].g, GPalette.BaseColors[i].b); } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- bool ShowRedLine(int j, int i) { auto wal = &wall[j]; if (!(g_gameType & GAMEFLAG_SW)) { return !gFullMap && !show2dsector[wal->nextsector]; } else { if (!gFullMap) { if (!show2dwall[j]) return false; int k = wal->nextwall; if (k > j && !show2dwall[k]) return false; //??? } if (automapMode == am_full) { if (sector[i].floorz != sector[i].ceilingz) if (sector[wal->nextsector].floorz != sector[wal->nextsector].ceilingz) if (((wal->cstat | wall[wal->nextwall].cstat) & (16 + 32)) == 0) if (sector[i].floorz == sector[wal->nextsector].floorz) return false; if (sector[i].floorpicnum != sector[wal->nextsector].floorpicnum) return false; if (sector[i].floorshade != sector[wal->nextsector].floorshade) return false; } return true; } } //--------------------------------------------------------------------------- // // two sided lines // //--------------------------------------------------------------------------- void drawredlines(int cposx, int cposy, int czoom, int cang) { int xvect = -bsin(cang) * czoom; int yvect = -bcos(cang) * czoom; int xvect2 = MulScale(xvect, yxaspect, 16); int yvect2 = MulScale(yvect, yxaspect, 16); for (int i = 0; i < numsectors; i++) { if (!gFullMap && !show2dsector[i]) continue; int startwall = sector[i].wallptr; int endwall = sector[i].wallptr + sector[i].wallnum; int z1 = sector[i].ceilingz; int z2 = sector[i].floorz; walltype* wal; int j; for (j = startwall, wal = &wall[startwall]; j < endwall; j++, wal++) { int k = wal->nextwall; if (k < 0 || k >= MAXWALLS) continue; if (sector[wal->nextsector].ceilingz == z1 && sector[wal->nextsector].floorz == z2) if (((wal->cstat | wall[wal->nextwall].cstat) & (16 + 32)) == 0) continue; if (ShowRedLine(j, i)) { int ox = wal->x - cposx; int oy = wal->y - cposy; int x1 = DMulScale(ox, xvect, -oy, yvect, 16) + (xdim << 11); int y1 = DMulScale(oy, xvect2, ox, yvect2, 16) + (ydim << 11); auto wal2 = &wall[wal->point2]; ox = wal2->x - cposx; oy = wal2->y - cposy; int x2 = DMulScale(ox, xvect, -oy, yvect, 16) + (xdim << 11); int y2 = DMulScale(oy, xvect2, ox, yvect2, 16) + (ydim << 11); drawlinergb(x1, y1, x2, y2, RedLineColor()); } } } } //--------------------------------------------------------------------------- // // one sided lines // //--------------------------------------------------------------------------- static void drawwhitelines(int cposx, int cposy, int czoom, int cang) { int xvect = -bsin(cang) * czoom; int yvect = -bcos(cang) * czoom; int xvect2 = MulScale(xvect, yxaspect, 16); int yvect2 = MulScale(yvect, yxaspect, 16); for (int i = numsectors - 1; i >= 0; i--) { if (!gFullMap && !show2dsector[i] && !(g_gameType & GAMEFLAG_SW)) continue; int startwall = sector[i].wallptr; int endwall = sector[i].wallptr + sector[i].wallnum; walltype* wal; int j; for (j = startwall, wal = &wall[startwall]; j < endwall; j++, wal++) { if (wal->nextwall >= 0) continue; if (!tileGetTexture(wal->picnum)->isValid()) continue; if ((g_gameType & GAMEFLAG_SW) && !gFullMap && !show2dwall[j]) continue; int ox = wal->x - cposx; int oy = wal->y - cposy; int x1 = DMulScale(ox, xvect, -oy, yvect, 16) + (xdim << 11); int y1 = DMulScale(oy, xvect2, ox, yvect2, 16) + (ydim << 11); int k = wal->point2; auto wal2 = &wall[k]; ox = wal2->x - cposx; oy = wal2->y - cposy; int x2 = DMulScale(ox, xvect, -oy, yvect, 16) + (xdim << 11); int y2 = DMulScale(oy, xvect2, ox, yvect2, 16) + (ydim << 11); drawlinergb(x1, y1, x2, y2, WhiteLineColor()); } } } void DrawPlayerArrow(int cposx, int cposy, int cang, int pl_x, int pl_y, int zoom, int pl_angle) { int arrow[] = { 0, 65536, 0, -65536, 0, 65536, -32768, 32878, 0, 65536, 32768, 32878, }; int xvect = -bsin(cang) * zoom; int yvect = -bcos(cang) * zoom; int xvect2 = MulScale(xvect, yxaspect, 16); int yvect2 = MulScale(yvect, yxaspect, 16); int pxvect = -bsin(pl_angle); int pyvect = -bcos(pl_angle); for (int i = 0; i < 12; i += 4) { int px1 = DMulScale(arrow[i], pxvect, -arrow[i+1], pyvect, 16); int py1 = DMulScale(arrow[i+1], pxvect, arrow[i], pyvect, 16) + (ydim << 11); int px2 = DMulScale(arrow[i+2], pxvect, -arrow[i + 3], pyvect, 16); int py2 = DMulScale(arrow[i + 3], pxvect, arrow[i+2], pyvect, 16) + (ydim << 11); int ox1 = px1 - cposx; int oy1 = py1 - cposx; int ox2 = px2 - cposx; int oy2 = py2 - cposx; int sx1 = DMulScale(ox1, xvect, -oy1, yvect, 16) + (xdim << 11); int sy1 = DMulScale(oy1, xvect2, ox1, yvect2, 16) + (ydim << 11); int sx2 = DMulScale(ox2, xvect, -oy2, yvect, 16) + (xdim << 11); int sy2 = DMulScale(oy2, xvect2, ox2, yvect2, 16) + (ydim << 11); drawlinergb(sx1, sy1, sx2, sy2, WhiteLineColor()); } } //--------------------------------------------------------------------------- // // // //--------------------------------------------------------------------------- void DrawOverheadMap(int pl_x, int pl_y, int pl_angle, double const smoothratio) { if (am_followplayer || follow_x == INT_MAX) { follow_x = pl_x; follow_y = pl_y; } int x = follow_x; int y = follow_y; follow_a = am_rotate ? pl_angle : 0; AutomapControl(); if (automapMode == am_full) { twod->ClearScreen(); renderDrawMapView(x, y, gZoom, follow_a); } int32_t tmpydim = (xdim * 5) / 8; renderSetAspect(65536, DivScale(tmpydim * 320, xdim * 200, 16)); drawredlines(x, y, gZoom, follow_a); drawwhitelines(x, y, gZoom, follow_a); if (!gi->DrawAutomapPlayer(x, y, gZoom, follow_a, smoothratio)) DrawPlayerArrow(x, y, follow_a, pl_x, pl_y, gZoom, -pl_angle); }