/* Emacs style mode select   -*- C++ -*- */
/*----------------------------------------------------------------------------- */
/* */
/* $Id:$ */
/* */
/* Copyright (C) 1993-1996 by id Software, Inc. */
/* */
/* 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 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. */
/* */
/* */
/* */
/* $Log:$ */
/* */
/* DESCRIPTION:  the automap code */
/* */
/*----------------------------------------------------------------------------- */

static const char rcsid[] = "$Id: am_map.c,v 1.4 1997/02/03 21:24:33 b1 Exp $";

#include <stdio.h>


#include "z_zone.h"
#include "doomdef.h"
#include "st_stuff.h"
#include "p_local.h"
#include "w_wad.h"

#include "m_cheat.h"
#include "i_system.h"

/* Needs access to LFB. */
#include "v_video.h"

/* State. */
#include "doomstat.h"
#include "r_state.h"

/* Data. */
#include "dstrings.h"

#include "am_map.h"


/* For use if I do walls with outsides/insides */
#define REDS            (256-5*16)
#define REDRANGE        16
#define BLUES           (256-4*16+8)
#define BLUERANGE       8
#define GREENS          (7*16)
#define GREENRANGE      16
#define GRAYS           (6*16)
#define GRAYSRANGE      16
#define BROWNS          (4*16)
#define BROWNRANGE      16
#define YELLOWS         (256-32+7)
#define YELLOWRANGE     1
#define BLACK           0
#define WHITE           (256-47)

/* Automap colors */
#define BACKGROUND      BLACK
#define YOURCOLORS      WHITE
#define YOURRANGE       0
#define WALLCOLORS      REDS
#define WALLRANGE       REDRANGE
#define TSWALLCOLORS    GRAYS
#define TSWALLRANGE     GRAYSRANGE
#define FDWALLCOLORS    BROWNS
#define FDWALLRANGE     BROWNRANGE
#define CDWALLCOLORS    YELLOWS
#define CDWALLRANGE     YELLOWRANGE
#define THINGCOLORS     GREENS
#define THINGRANGE      GREENRANGE
#define SECRETWALLCOLORS WALLCOLORS
#define SECRETWALLRANGE WALLRANGE
#define GRIDCOLORS      (GRAYS + GRAYSRANGE/2)
#define GRIDRANGE       0
#define XHAIRCOLORS     GRAYS

/* drawing stuff */
#define FB              0

#define AM_PANDOWNKEY   KEY_DOWNARROW
#define AM_PANUPKEY     KEY_UPARROW
#define AM_PANRIGHTKEY  KEY_RIGHTARROW
#define AM_PANLEFTKEY   KEY_LEFTARROW
#define AM_ZOOMINKEY    '='
#define AM_ZOOMOUTKEY   '-'
#define AM_STARTKEY     KEY_TAB
#define AM_ENDKEY       KEY_TAB
#define AM_GOBIGKEY     '0'
#define AM_FOLLOWKEY    'f'
#define AM_GRIDKEY      'g'
#define AM_MARKKEY      'm'
#define AM_CLEARMARKKEY 'c'

#define AM_NUMMARKPOINTS 10

/* scale on entry */
#define INITSCALEMTOF (.2*FRACUNIT)
/* how much the automap moves window per tic in frame-buffer coordinates */
/* moves 140 pixels in 1 second */
#define F_PANINC        4
/* how much zoom-in per tic */
/* goes to 2x in 1 second */
#define M_ZOOMIN        ((int) (1.02*FRACUNIT))
/* how much zoom-out per tic */
/* pulls out to 0.5x in 1 second */
#define M_ZOOMOUT       ((int) (FRACUNIT/1.02))

/* translates between frame-buffer and map distances */
#define FTOM(x) FixedMul(((x)<<16),scale_ftom)
#define MTOF(x) (FixedMul((x),scale_mtof)>>16)
/* translates between frame-buffer and map coordinates */
#define CXMTOF(x)  (f_x + MTOF((x)-m_x))
#define CYMTOF(y)  (f_y + (f_h - MTOF((y)-m_y)))

/* the following is crap */
#define LINE_NEVERSEE ML_DONTDRAW

typedef struct
{
	int x, y;
} fpoint_t;

typedef struct
{
	fpoint_t a, b;
} fline_t;

typedef struct
{
	fixed_t x,y;
} mpoint_t;

typedef struct
{
	mpoint_t a, b;
} mline_t;

typedef struct
{
	fixed_t slp, islp;
} islope_t;



/* */
/* The vector graphics for the automap. */
/*  A line drawing of the player pointing right, */
/*   starting from the middle. */
/* */
#define R ((8*PLAYERRADIUS)/7)
mline_t player_arrow[] = {
	{ { -R+R/8, 0 }, { R, 0 } }, /* ----- */
	{ { R, 0 }, { R-R/2, R/4 } }, /* -----> */
	{ { R, 0 }, { R-R/2, -R/4 } },
	{ { -R+R/8, 0 }, { -R-R/8, R/4 } }, /* >----> */
	{ { -R+R/8, 0 }, { -R-R/8, -R/4 } },
	{ { -R+3*R/8, 0 }, { -R+R/8, R/4 } }, /* >>---> */
	{ { -R+3*R/8, 0 }, { -R+R/8, -R/4 } }
};
#undef R
#define NUMPLYRLINES (sizeof(player_arrow)/sizeof(mline_t))

#define R ((8*PLAYERRADIUS)/7)
mline_t cheat_player_arrow[] = {
        { { -R+R/8, 0 }, { R, 0 } }, /* ----- */
        { { R, 0 }, { R-R/2, R/6 } }, /* -----> */
        { { R, 0 }, { R-R/2, -R/6 } },
        { { -R+R/8, 0 }, { -R-R/8, R/6 } }, /* >-----> */
        { { -R+R/8, 0 }, { -R-R/8, -R/6 } },
        { { -R+3*R/8, 0 }, { -R+R/8, R/6 } }, /* >>-----> */
        { { -R+3*R/8, 0 }, { -R+R/8, -R/6 } },
        { { -R/2, 0 }, { -R/2, -R/6 } }, /* >>-d---> */
        { { -R/2, -R/6 }, { -R/2+R/6, -R/6 } },
        { { -R/2+R/6, -R/6 }, { -R/2+R/6, R/4 } },
        { { -R/6, 0 }, { -R/6, -R/6 } }, /* >>-dd--> */
        { { -R/6, -R/6 }, { 0, -R/6 } },
        { { 0, -R/6 }, { 0, R/4 } },
        { { R/6, R/4 }, { R/6, -R/7 } }, /* >>-ddt-> */
        { { R/6, -R/7 }, { R/6+R/32, -R/7-R/32 } },
        { { R/6+R/32, -R/7-R/32 }, { R/6+R/10, -R/7 } }
};
#undef R
#define NUMCHEATPLYRLINES (sizeof(cheat_player_arrow)/sizeof(mline_t))

#define R (FRACUNIT)
mline_t triangle_guy[] = {
        { { -.867*R, -.5*R }, { .867*R, -.5*R } },
        { { .867*R, -.5*R }, { 0, R } },
        { { 0, R }, { -.867*R, -.5*R } }
};
#undef R
#define NUMTRIANGLEGUYLINES (sizeof(triangle_guy)/sizeof(mline_t))

#define R (FRACUNIT)
mline_t thintriangle_guy[] = {
        { { -.5*R, -.7*R }, { R, 0 } },
        { { R, 0 }, { -.5*R, .7*R } },
        { { -.5*R, .7*R }, { -.5*R, -.7*R } }
};
#undef R
#define NUMTHINTRIANGLEGUYLINES (sizeof(thintriangle_guy)/sizeof(mline_t))




static int cheating = 0;
static int grid = 0;

static int leveljuststarted = 1;        /* kluge until AM_LevelInit() is called */

boolean automapactive = false;
static int finit_width = SCREENWIDTH;
static int finit_height = SCREENHEIGHT - 32;

/* location of window on screen */
static int f_x;
static int f_y;

/* size of window on screen */
static int f_w;
static int f_h;

static int lightlev;                    /* used for funky strobing effect */
static byte*    fb;                     /* pseudo-frame buffer */
static int amclock;

static mpoint_t m_paninc; /* how far the window pans each tic (map coords) */
static fixed_t mtof_zoommul;  /* how far the window zooms in each tic (map coords) */
static fixed_t ftom_zoommul;  /* how far the window zooms in each tic (fb coords) */

static fixed_t m_x, m_y;    /* LL x,y where the window is on the map (map coords) */
static fixed_t m_x2, m_y2;  /* UR x,y where the window is on the map (map coords) */

/* */
/* width/height of window on map (map coords) */
/* */
static fixed_t m_w;
static fixed_t m_h;

/* based on level size */
static fixed_t min_x;
static fixed_t min_y;
static fixed_t max_x;
static fixed_t max_y;

static fixed_t max_w;  /* max_x-min_x, */
static fixed_t max_h;  /* max_y-min_y */

/* based on player size */
static fixed_t min_w;
static fixed_t min_h;


static fixed_t min_scale_mtof;  /* used to tell when to stop zooming out */
static fixed_t max_scale_mtof;  /* used to tell when to stop zooming in */

/* old stuff for recovery later */
static fixed_t old_m_w, old_m_h;
static fixed_t old_m_x, old_m_y;

/* old location used by the Follower routine */
static mpoint_t f_oldloc;

/* used by MTOF to scale from map-to-frame-buffer coords */
static fixed_t scale_mtof = INITSCALEMTOF;
/* used by FTOM to scale from frame-buffer-to-map coords (=1/scale_mtof) */
static fixed_t scale_ftom;

static player_t *plr; /* the player represented by an arrow */

static patch_t *marknums[10]; /* numbers used for marking by the automap */
static mpoint_t markpoints[AM_NUMMARKPOINTS]; /* where the points are */
static int markpointnum = 0; /* next point to be assigned */

static int followplayer = 1; /* specifies whether to follow the player around */

static unsigned char cheat_amap_seq[] = { 0xb2, 0x26, 0x26, 0x2e, 0xff };
static cheatseq_t cheat_amap = { cheat_amap_seq, 0 };

static boolean stopped = true;

extern boolean viewactive;
/*extern byte screens[][SCREENWIDTH*SCREENHEIGHT]; */



void
V_MarkRect
        ( int x,
        int y,
        int width,
        int height );

/* Calculates the slope and slope according to the x-axis of a line */
/* segment in map coordinates (with the upright y-axis n' all) so */
/* that it can be used with the brain-dead drawing stuff. */

void
AM_getIslope
        ( mline_t*      ml,
        islope_t*     is )
{
        int dx, dy;

        dy = ml->a.y - ml->b.y;
        dx = ml->b.x - ml->a.x;
        if (!dy) is->islp = (dx<0 ? -MAXINT : MAXINT);
        else is->islp = FixedDiv(dx, dy);
        if (!dx) is->slp = (dy<0 ? -MAXINT : MAXINT);
        else is->slp = FixedDiv(dy, dx);

}

/* */
/* */
/* */
void AM_activateNewScale(void)
{
        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 AM_saveScaleAndLoc(void)
{
        old_m_x = m_x;
        old_m_y = m_y;
        old_m_w = m_w;
        old_m_h = m_h;
}

/* */
/* */
/* */
void AM_restoreScaleAndLoc(void)
{

        m_w = old_m_w;
        m_h = old_m_h;
        if (!followplayer)
        {
                m_x = old_m_x;
                m_y = old_m_y;
	} else {
                m_x = plr->mo->x - m_w/2;
                m_y = plr->mo->y - m_h/2;
	}
        m_x2 = m_x + m_w;
        m_y2 = m_y + m_h;

        /* Change the scaling multipliers */
        scale_mtof = FixedDiv(f_w<<FRACBITS, m_w);
        scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
}

/* */
/* adds a marker at the current location */
/* */
void AM_addMark(void)
{
        markpoints[markpointnum].x = m_x + m_w/2;
        markpoints[markpointnum].y = m_y + m_h/2;
        markpointnum = (markpointnum + 1) % AM_NUMMARKPOINTS;

}

/* */
/* Determines bounding box of all vertices, */
/* sets global variables controlling zoom range. */
/* */
void AM_findMinMaxBoundaries(void)
{
        int i;
        fixed_t a;
        fixed_t b;

        min_x = min_y =  MAXINT;
        max_x = max_y = -MAXINT;

        for (i=0; i<numvertexes; i++)
        {
                if (vertexes[i].x < min_x)
			min_x = vertexes[i].x;
                else if (vertexes[i].x > max_x)
			max_x = vertexes[i].x;

                if (vertexes[i].y < min_y)
			min_y = vertexes[i].y;
                else if (vertexes[i].y > max_y)
			max_y = vertexes[i].y;
	}

        max_w = max_x - min_x;
        max_h = max_y - min_y;

        min_w = 2*PLAYERRADIUS; /* const? never changed? */
        min_h = 2*PLAYERRADIUS;

        a = FixedDiv(f_w<<FRACBITS, max_w);
        b = FixedDiv(f_h<<FRACBITS, max_h);

        min_scale_mtof = a < b ? a : b;
        max_scale_mtof = FixedDiv(f_h<<FRACBITS, 2*PLAYERRADIUS);

}


/* */
/* */
/* */
void AM_changeWindowLoc(void)
{
        if (m_paninc.x || m_paninc.y)
        {
                followplayer = 0;
                f_oldloc.x = MAXINT;
	}

        m_x += m_paninc.x;
        m_y += m_paninc.y;

        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 AM_initVariables(void)
{
        int pnum;
        static event_t st_notify = { ev_keyup, AM_MSGENTERED };

        automapactive = true;
        fb = screens[0];

        f_oldloc.x = MAXINT;
        amclock = 0;
        lightlev = 0;

        m_paninc.x = m_paninc.y = 0;
        ftom_zoommul = FRACUNIT;
        mtof_zoommul = FRACUNIT;

        m_w = FTOM(f_w);
        m_h = FTOM(f_h);

        /* find player to center on initially */
        if (!playeringame[pnum = consoleplayer])
		for (pnum=0; pnum<MAXPLAYERS; pnum++)
			if (playeringame[pnum])
				break;

        plr = &players[pnum];
        m_x = plr->mo->x - m_w/2;
        m_y = plr->mo->y - m_h/2;
        AM_changeWindowLoc();

        /* for saving & restoring */
        old_m_x = m_x;
        old_m_y = m_y;
        old_m_w = m_w;
        old_m_h = m_h;

        /* inform the status bar of the change */
        ST_Responder(&st_notify);

}

/* */
/* */
/* */
void AM_loadPics(void)
{
        int i;
        char namebuf[9];

        for (i=0; i<10; i++)
        {
                sprintf(namebuf, "AMMNUM%d", i);
                marknums[i] = W_CacheLumpName(namebuf, PU_STATIC);
	}

}

void AM_unloadPics(void)
{
        int i;

        for (i=0; i<10; i++)
		Z_ChangeTag(marknums[i], PU_CACHE);

}

void AM_clearMarks(void)
{
        int i;

        for (i=0; i<AM_NUMMARKPOINTS; i++)
		markpoints[i].x = -1; /* means empty */
        markpointnum = 0;
}

/* */
/* should be called at the start of every level */
/* right now, i figure it out myself */
/* */
void AM_LevelInit(void)
{
        leveljuststarted = 0;

        f_x = f_y = 0;
        f_w = finit_width;
        f_h = finit_height;

        AM_clearMarks();

        AM_findMinMaxBoundaries();
        scale_mtof = FixedDiv(min_scale_mtof, (int) (0.7*FRACUNIT));
        if (scale_mtof > max_scale_mtof)
		scale_mtof = min_scale_mtof;
        scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
}




/* */
/* */
/* */
void AM_Stop (void)
{
        static event_t st_notify = { 0, ev_keyup, AM_MSGEXITED };

        AM_unloadPics();
        automapactive = false;
        ST_Responder(&st_notify);
        stopped = true;
}

/* */
/* */
/* */
void AM_Start (void)
{
        static int lastlevel = -1, lastepisode = -1;

        if (!stopped) AM_Stop();
        stopped = false;
        if (lastlevel != gamemap || lastepisode != gameepisode)
        {
                AM_LevelInit();
                lastlevel = gamemap;
                lastepisode = gameepisode;
	}
        AM_initVariables();
        AM_loadPics();
}

/* */
/* set the window scale to the maximum size */
/* */
void AM_minOutWindowScale(void)
{
        scale_mtof = min_scale_mtof;
        scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
        AM_activateNewScale();
}

/* */
/* set the window scale to the minimum size */
/* */
void AM_maxOutWindowScale(void)
{
        scale_mtof = max_scale_mtof;
        scale_ftom = FixedDiv(FRACUNIT, scale_mtof);
        AM_activateNewScale();
}


/* */
/* Handle events (user inputs) in automap mode */
/* */
boolean
AM_Responder
        ( event_t*      ev )
{

        int rc;
        static int cheatstate=0;
        static int bigstate=0;
        static char buffer[20];

        rc = false;

        if (!automapactive)
        {
                if (ev->type == ev_keydown && ev->data1 == AM_STARTKEY)
                {
                        AM_Start ();
                        viewactive = false;
                        rc = true;
		}
	}

        else if (ev->type == ev_keydown)
        {

                rc = true;
                switch(ev->data1)
                {
		case AM_PANRIGHTKEY: /* pan right */
			if (!followplayer) m_paninc.x = FTOM(F_PANINC);
			else rc = false;
			break;
		case AM_PANLEFTKEY: /* pan left */
			if (!followplayer) m_paninc.x = -FTOM(F_PANINC);
			else rc = false;
			break;
		case AM_PANUPKEY: /* pan up */
			if (!followplayer) m_paninc.y = FTOM(F_PANINC);
			else rc = false;
			break;
		case AM_PANDOWNKEY: /* pan down */
			if (!followplayer) m_paninc.y = -FTOM(F_PANINC);
			else rc = false;
			break;
		case AM_ZOOMOUTKEY: /* zoom out */
			mtof_zoommul = M_ZOOMOUT;
			ftom_zoommul = M_ZOOMIN;
			break;
		case AM_ZOOMINKEY: /* zoom in */
			mtof_zoommul = M_ZOOMIN;
			ftom_zoommul = M_ZOOMOUT;
			break;
		case AM_ENDKEY:
			bigstate = 0;
			viewactive = true;
			AM_Stop ();
			break;
		case AM_GOBIGKEY:
			bigstate = !bigstate;
			if (bigstate)
			{
			        AM_saveScaleAndLoc();
			        AM_minOutWindowScale();
			}
			else AM_restoreScaleAndLoc();
			break;
		case AM_FOLLOWKEY:
			followplayer = !followplayer;
			f_oldloc.x = MAXINT;
			plr->message = followplayer ? AMSTR_FOLLOWON : AMSTR_FOLLOWOFF;
			break;
		case AM_GRIDKEY:
			grid = !grid;
			plr->message = grid ? AMSTR_GRIDON : AMSTR_GRIDOFF;
			break;
		case AM_MARKKEY:
			sprintf(buffer, "%s %d", AMSTR_MARKEDSPOT, markpointnum);
			plr->message = buffer;
			AM_addMark();
			break;
		case AM_CLEARMARKKEY:
			AM_clearMarks();
			plr->message = AMSTR_MARKSCLEARED;
			break;
		default:
			cheatstate=0;
			rc = false;
		}
                if (!deathmatch && cht_CheckCheat(&cheat_amap, ev->data1))
                {
                        rc = false;
                        cheating = (cheating+1) % 3;
		}
	}

        else if (ev->type == ev_keyup)
        {
                rc = false;
                switch (ev->data1)
                {
		case AM_PANRIGHTKEY:
			if (!followplayer) m_paninc.x = 0;
			break;
		case AM_PANLEFTKEY:
			if (!followplayer) m_paninc.x = 0;
			break;
		case AM_PANUPKEY:
			if (!followplayer) m_paninc.y = 0;
			break;
		case AM_PANDOWNKEY:
			if (!followplayer) m_paninc.y = 0;
			break;
		case AM_ZOOMOUTKEY:
		case AM_ZOOMINKEY:
			mtof_zoommul = FRACUNIT;
			ftom_zoommul = FRACUNIT;
			break;
		}
	}

        return rc;

}


/* */
/* Zooming */
/* */
void AM_changeWindowScale(void)
{

        /* Change the scaling multipliers */
        scale_mtof = FixedMul(scale_mtof, mtof_zoommul);
        scale_ftom = FixedDiv(FRACUNIT, scale_mtof);

        if (scale_mtof < min_scale_mtof)
		AM_minOutWindowScale();
        else if (scale_mtof > max_scale_mtof)
		AM_maxOutWindowScale();
        else
		AM_activateNewScale();
}


/* */
/* */
/* */
void AM_doFollowPlayer(void)
{

        if (f_oldloc.x != plr->mo->x || f_oldloc.y != plr->mo->y)
        {
                m_x = FTOM(MTOF(plr->mo->x)) - m_w/2;
                m_y = FTOM(MTOF(plr->mo->y)) - m_h/2;
                m_x2 = m_x + m_w;
                m_y2 = m_y + m_h;
                f_oldloc.x = plr->mo->x;
                f_oldloc.y = plr->mo->y;

                /*  m_x = FTOM(MTOF(plr->mo->x - m_w/2)); */
                /*  m_y = FTOM(MTOF(plr->mo->y - m_h/2)); */
                /*  m_x = plr->mo->x - m_w/2; */
                /*  m_y = plr->mo->y - m_h/2; */

	}

}

/* */
/* */
/* */
void AM_updateLightLev(void)
{
        static nexttic = 0;
        /*static int litelevels[] = { 0, 3, 5, 6, 6, 7, 7, 7 }; */
        static int litelevels[] = { 0, 4, 7, 10, 12, 14, 15, 15 };
        static int litelevelscnt = 0;

        /* Change light level */
        if (amclock>nexttic)
        {
                lightlev = litelevels[litelevelscnt++];
                if (litelevelscnt == sizeof(litelevels)/sizeof(int)) litelevelscnt = 0;
                nexttic = amclock + 6 - (amclock % 6);
	}

}


/* */
/* Updates on Game Tick */
/* */
void AM_Ticker (void)
{

        if (!automapactive)
		return;

        amclock++;

        if (followplayer)
		AM_doFollowPlayer();

        /* Change the zoom if necessary */
        if (ftom_zoommul != FRACUNIT)
		AM_changeWindowScale();

        /* Change x,y location */
        if (m_paninc.x || m_paninc.y)
		AM_changeWindowLoc();

        /* Update light level */
        /* AM_updateLightLev(); */

}


/* */
/* Clear automap frame buffer. */
/* */
void AM_clearFB(int color)
{
        memset(fb, color, f_w*f_h);
}


/* */
/* 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. */
/* */
boolean
AM_clipMline
        ( mline_t*      ml,
        fline_t*      fl )
{
        enum
        {
                LEFT    =1,
                RIGHT   =2,
                BOTTOM  =4,
                TOP     =8
	};

        register outcode1 = 0;
        register outcode2 = 0;
        register outside;

        fpoint_t tmp;
        int dx;
        int dy;


#define DOOUTCODE(oc, mx, 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 + (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 + (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 + (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 + (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;
}
#undef DOOUTCODE


/* */
/* Classic Bresenham w/ whatever optimizations needed for speed */
/* */
void
AM_drawFline
        ( fline_t*      fl,
        int color )
{
        register int x;
        register int y;
        register int dx;
        register int dy;
        register int sx;
        register int sy;
        register int ax;
        register int ay;
        register int d;

        static fuck = 0;

        /* For debugging only */
        if (      fl->a.x < 0 || fl->a.x >= f_w
                  || fl->a.y < 0 || fl->a.y >= f_h
                  || fl->b.x < 0 || fl->b.x >= f_w
                  || fl->b.y < 0 || fl->b.y >= f_h)
        {
                fprintf(stderr, "fuck %d \r", fuck++);
                return;
	}

#define PUTDOT(xx,yy,cc) fb[(yy)*f_w+(xx)]=(cc)

        dx = fl->b.x - fl->a.x;
        ax = 2 * (dx<0 ? -dx : dx);
        sx = dx<0 ? -1 : 1;

        dy = fl->b.y - fl->a.y;
        ay = 2 * (dy<0 ? -dy : dy);
        sy = dy<0 ? -1 : 1;

        x = fl->a.x;
        y = fl->a.y;

        if (ax > ay)
        {
                d = ay - ax/2;
                while (1)
                {
                        PUTDOT(x,y,color);
                        if (x == fl->b.x) return;
                        if (d>=0)
                        {
                                y += sy;
                                d -= ax;
			}
                        x += sx;
                        d += ay;
		}
	}
        else
        {
                d = ax - ay/2;
                while (1)
                {
                        PUTDOT(x, y, color);
                        if (y == fl->b.y) return;
                        if (d >= 0)
                        {
                                x += sx;
                                d -= ay;
			}
                        y += sy;
                        d += ax;
		}
	}
}


/* */
/* Clip lines, draw visible part sof lines. */
/* */
void
AM_drawMline
        ( mline_t*      ml,
        int color )
{
        static fline_t fl;

        if (AM_clipMline(ml, &fl))
		AM_drawFline(&fl, color); /* draws it on frame buffer using fb coords */
}



/* */
/* Draws flat (floor/ceiling tile) aligned grid lines. */
/* */
void AM_drawGrid(int color)
{
        fixed_t x, y;
        fixed_t start, end;
        mline_t ml;

        /* Figure out start of vertical gridlines */
        start = m_x;
        if ((start-bmaporgx)%(MAPBLOCKUNITS<<FRACBITS))
		start += (MAPBLOCKUNITS<<FRACBITS)
		         - ((start-bmaporgx)%(MAPBLOCKUNITS<<FRACBITS));
        end = m_x + m_w;

        /* draw vertical gridlines */
        ml.a.y = m_y;
        ml.b.y = m_y+m_h;
        for (x=start; x<end; x+=(MAPBLOCKUNITS<<FRACBITS))
        {
                ml.a.x = x;
                ml.b.x = x;
                AM_drawMline(&ml, color);
	}

        /* Figure out start of horizontal gridlines */
        start = m_y;
        if ((start-bmaporgy)%(MAPBLOCKUNITS<<FRACBITS))
		start += (MAPBLOCKUNITS<<FRACBITS)
		         - ((start-bmaporgy)%(MAPBLOCKUNITS<<FRACBITS));
        end = m_y + m_h;

        /* draw horizontal gridlines */
        ml.a.x = m_x;
        ml.b.x = m_x + m_w;
        for (y=start; y<end; y+=(MAPBLOCKUNITS<<FRACBITS))
        {
                ml.a.y = y;
                ml.b.y = y;
                AM_drawMline(&ml, color);
	}

}

/* */
/* Determines visible lines, draws them. */
/* This is LineDef based, not LineSeg based. */
/* */
void AM_drawWalls(void)
{
        int i;
        static mline_t l;

        for (i=0; i<numlines; i++)
        {
                l.a.x = lines[i].v1->x;
                l.a.y = lines[i].v1->y;
                l.b.x = lines[i].v2->x;
                l.b.y = lines[i].v2->y;
                if (cheating || (lines[i].flags & ML_MAPPED))
                {
                        if ((lines[i].flags & LINE_NEVERSEE) && !cheating)
				continue;
                        if (!lines[i].backsector)
                        {
                                AM_drawMline(&l, WALLCOLORS+lightlev);
			}
                        else
                        {
                                if (lines[i].special == 39)
                                { /* teleporters */
                                        AM_drawMline(&l, WALLCOLORS+WALLRANGE/2);
				}
                                else if (lines[i].flags & ML_SECRET) /* secret door */
                                {
                                        if (cheating) AM_drawMline(&l, SECRETWALLCOLORS + lightlev);
                                        else AM_drawMline(&l, WALLCOLORS+lightlev);
				}
                                else if (lines[i].backsector->floorheight
                                         != lines[i].frontsector->floorheight) {
                                        AM_drawMline(&l, FDWALLCOLORS + lightlev); /* floor level change */
				}
                                else if (lines[i].backsector->ceilingheight
                                         != lines[i].frontsector->ceilingheight) {
                                        AM_drawMline(&l, CDWALLCOLORS+lightlev); /* ceiling level change */
				}
                                else if (cheating) {
                                        AM_drawMline(&l, TSWALLCOLORS+lightlev);
				}
			}
		}
                else if (plr->powers[pw_allmap])
                {
                        if (!(lines[i].flags & LINE_NEVERSEE)) AM_drawMline(&l, GRAYS+3);
		}
	}
}


/* */
/* Rotation in 2D. */
/* Used to rotate player arrow line character. */
/* */
void
AM_rotate
        ( fixed_t*      x,
        fixed_t*      y,
        angle_t a )
{
        fixed_t tmpx;

        tmpx =
		FixedMul(*x,finecosine[a>>ANGLETOFINESHIFT])
		- FixedMul(*y,finesine[a>>ANGLETOFINESHIFT]);

        *y   =
		FixedMul(*x,finesine[a>>ANGLETOFINESHIFT])
		+ FixedMul(*y,finecosine[a>>ANGLETOFINESHIFT]);

        *x = tmpx;
}

void
AM_drawLineCharacter
        ( mline_t*      lineguy,
        int lineguylines,
        fixed_t scale,
        angle_t angle,
        int color,
        fixed_t x,
        fixed_t y )
{
        int i;
        mline_t l;

        for (i=0; i<lineguylines; i++)
        {
                l.a.x = lineguy[i].a.x;
                l.a.y = lineguy[i].a.y;

                if (scale)
                {
                        l.a.x = FixedMul(scale, l.a.x);
                        l.a.y = FixedMul(scale, l.a.y);
		}

                if (angle)
			AM_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 = FixedMul(scale, l.b.x);
                        l.b.y = FixedMul(scale, l.b.y);
		}

                if (angle)
			AM_rotate(&l.b.x, &l.b.y, angle);

                l.b.x += x;
                l.b.y += y;

                AM_drawMline(&l, color);
	}
}

void AM_drawPlayers(void)
{
        int i;
        player_t*   p;
        static int their_colors[] = { GREENS, GRAYS, BROWNS, REDS };
        int their_color = -1;
        int color;

        if (!netgame)
        {
                if (cheating)
			AM_drawLineCharacter
			        (cheat_player_arrow, NUMCHEATPLYRLINES, 0,
			        plr->mo->angle, WHITE, plr->mo->x, plr->mo->y);
                else
			AM_drawLineCharacter
			        (player_arrow, NUMPLYRLINES, 0, plr->mo->angle,
			        WHITE, plr->mo->x, plr->mo->y);
                return;
	}

        for (i=0; i<MAXPLAYERS; i++)
        {
                their_color++;
                p = &players[i];

                if ( (deathmatch && !singledemo) && p != plr)
			continue;

                if (!playeringame[i])
			continue;

                if (p->powers[pw_invisibility])
			color = 246; /* *close* to black */
                else
			color = their_colors[their_color];

                AM_drawLineCharacter
                        (player_arrow, NUMPLYRLINES, 0, p->mo->angle,
                        color, p->mo->x, p->mo->y);
	}

}

void
AM_drawThings
        ( int colors,
        int colorrange)
{
        int i;
        mobj_t*     t;

        for (i=0; i<numsectors; i++)
        {
                t = sectors[i].thinglist;
                while (t)
                {
                        AM_drawLineCharacter
                                (thintriangle_guy, NUMTHINTRIANGLEGUYLINES,
                                16<<FRACBITS, t->angle, colors+lightlev, t->x, t->y);
                        t = t->snext;
		}
	}
}

void AM_drawMarks(void)
{
        int i, fx, fy, w, h;

        for (i=0; i<AM_NUMMARKPOINTS; i++)
        {
                if (markpoints[i].x != -1)
                {
                        /*      w = SHORT(marknums[i]->width); */
                        /*      h = SHORT(marknums[i]->height); */
                        w = 5; /* because something's wrong with the wad, i guess */
                        h = 6; /* because something's wrong with the wad, i guess */
                        fx = CXMTOF(markpoints[i].x);
                        fy = CYMTOF(markpoints[i].y);
                        if (fx >= f_x && fx <= f_w - w && fy >= f_y && fy <= f_h - h)
				V_DrawPatch(fx, fy, FB, marknums[i]);
		}
	}

}

void AM_drawCrosshair(int color)
{
        fb[(f_w*(f_h+1))/2] = color; /* single point for now */

}

void AM_Drawer (void)
{
        if (!automapactive) return;

        AM_clearFB(BACKGROUND);
        if (grid)
		AM_drawGrid(GRIDCOLORS);
        AM_drawWalls();
        AM_drawPlayers();
        if (cheating==2)
		AM_drawThings(THINGCOLORS, THINGRANGE);
        AM_drawCrosshair(XHAIRCOLORS);

        AM_drawMarks();

        V_MarkRect(f_x, f_y, f_w, f_h);

}
