/*

Copyright (C) 2015-2018 Night Dive Studios, LLC.

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/>.

*/
/*
 * $Source: r:/prj/cit/src/RCS/amap.c $
 * $Revision: 1.41 $
 * $Author: dc $
 * $Date: 1994/11/28 06:38:12 $
 *
 * routines for creation and modification of the automap on a level
 * uses the map's internal state for showing stuff
 * as well as a list of objects (map notes) for highlights and such
 * this is the core canvas rendering/wall stuff
 * it is called by the mfd based system, or in full screen mode
 */

// color set....
// player/focused object - red
// elevators             - dark brown
// walls                 - bright green, mid green, dim green
// security              - cycling red
// bio                   - yellow
// radiation             - flat red
// mutant                - purple
// robot                 - metalblue
// cyborg                - brightbrownx
// doors                 - maize?

#include <string.h>

#include "cit2d.h"
#include "colors.h"
#include "cybstrng.h"
#include "frquad.h"
#include "gamescr.h"
#include "gamestrn.h"
#include "gr2ss.h"
#include "lvldata.h"
#include "map.h"
#include "mapflags.h"
#include "musicai.h"
#include "objbit.h"
#include "objgame.h"
#include "objprop.h"
#include "objuse.h"
#include "otrip.h"
#include "player.h"
#include "rcolors.h"
#include "refstuf.h"
#include "tilename.h"

#define FMK_INT_XX (FMK_INT_NW || FMK_INT_SW || FMK_INT_EW || FMK_INT_WW)
#define FMK_INT_INT (1 << 8)

// actual dimensions of a typical monitor, for calculating pixel ratio.
#define STD_SCR_WID 11
#define STD_SCR_HGT 8

#define NORTH 0
#define EAST  1
#define SOUTH 2
#define WEST  3

#define TERR_OBJ_PASS  0
#define REAL_OBJ_PASS  1
#define NOTE_OBJ_PASS  2
#define NUM_OBJ_PASSES 3

#define FIN_SWEEP 0x5800
#define FIN_NOSE  fix_make(0, 0xB000)
#define FIN_TAIL  fix_make(0, 0x4000)

#define DRAW_MASK_SEEN 0x1
#define DRAW_MASK_RAD  0x2
#define DRAW_MASK_FULL 0x4
#define DRAW_MASK_TERR 0x8
#define DRAW_MASK_SENS 0x10

//#define AMAP_SENS_TILEBOUND
#define AMAP_SENS_CIRCLE

#define CORRECT_PIXEL_RATIO
#define FIX_PIXRATIO

// beware the shifting version.  If pixratio_shf is negative,
// C does not define what happens.  This does not happen to
// occur in any of the screen modes we plan to support, and
// might work right anyway.
//
#ifdef CORRECT_PIXEL_RATIO
#ifdef FIX_PIXRATIO
fix pixratio_yx = FIX_UNIT;
fix pixratio_xy = FIX_UNIT;
//#define coor_to_pix(y) fast_fix_mul(pixratio_yx,(y))	 KLC - Changed this
//#define pix_to_coor(y) fast_fix_mul(pixratio_xy,(y))
#define coor_to_pix(y) fix_mul(pixratio_yx, (y))
#define pix_to_coor(y) fix_mul(pixratio_xy, (y))
#else
int pixratio_shf = 0;
#define coor_to_pix(y) ((y) << pixratio_shf)
#define pix_to_coor(y) ((y) >> pixratio_shf)
#endif

// fix times int can use regular multiply
#define am_vline(x, y0, y1)         gr_vline(x, coor_to_pix(y0), coor_to_pix(y1))
#define am_hline(x0, y, x1)         gr_hline(x0, coor_to_pix(y), x1)
#define am_rect(x0, y0, x1, y1)     gr_rect(x0, coor_to_pix(y0), x1, coor_to_pix(y1))
#define am_int_line(x0, y0, x1, y1) gr_int_line(x0, coor_to_pix(y0), x1, coor_to_pix(y1))
#define am_fix_line(x0, y0, x1, y1) gr_fix_line(x0, coor_to_pix(y0), x1, coor_to_pix(y1))
#define am_int_circle(xc, yc, r)    gr_int_circle(xc, coor_to_pix(yc), r)
#else
#define am_vline      gr_vline
#define am_hline      gr_hline
#define am_rect       gr_rect
#define am_int_line   gr_int_line
#define am_fix_line   gr_fix_line
#define am_int_circle gr_int_circle
#endif

//-------------------
// Prototypes
//-------------------
void obj_draw(int xm, int ym, Obj *cobj, int tsize, int so, int color);
void line_draw(int xm, int ym, Obj *cobj, int tsize, int full, int color);
void obj_mess(curAMap *amptr, MapElem *curmp, int drw, int xm, int ym, int tsize, int pass);
uchar wall_seen_p(int wallcode, int csbits, MapElem *cur);
void tile_draw(int xm, int ym, int tiletype, int size, int offs, int color);
void wall_draw(int xm, int ym, int wallcode, int size, MapElem *cur);
void draw_radius_obj(curAMap *amptr, short OtoF, int col, int zeroscrx, int zeroscry, int rad);
void draw_full_obj(curAMap *amptr, short OtoF, int col, int zeroscrx, int zeroscry);
void *amap_loc_to_sq(curAMap *amptr, int *x, int *y);
ObjID amap_loc_get_note(void *map_sq);
ObjID amap_loc_note_check(curAMap *amptr, void *curmp, int *x, int *y, int *to_do);
void amap_fixup_existing(int tolera, int delta);

// for now, create a default automap thingy
void amap_version_set(int id, int new_ver) {
    curAMap *amptr = oAMap(id);

    switch (new_ver) {
    case -1:
        amptr->flags = AMAP_TRACK_OBJ | AMAP_SHOW_SEC | AMAP_SHOW_CRIT;
        amptr->sensor_rad = 0x800;
        break;
    case 0:
    case 1:
        amptr->flags = AMAP_TRACK_OBJ | AMAP_SHOW_SEC | AMAP_SHOW_MSG;
        amptr->sensor_rad = 0x400;
        break;
    case 2:
        amptr->flags = AMAP_TRACK_OBJ | AMAP_SHOW_SEC | AMAP_SHOW_ROB | AMAP_SHOW_MSG;
        amptr->sensor_rad = 0x600;
        break;
    case 3:
        amptr->flags = AMAP_TRACK_OBJ | AMAP_SHOW_SEC | AMAP_SHOW_CRIT | AMAP_SHOW_HAZ | AMAP_SHOW_MSG;
        amptr->sensor_rad = 0x800;
        break;
    }
    amptr->avail_flags = amptr->flags | AMAP_AVAIL_ALWAYS;
    amptr->version_id = new_ver;
}

void automap_init(int version, int id) {
    curAMap *amptr;
    amptr = oAMap(id);
    amptr->xf = fix_make((MAP_YSIZE >> 1) - 1, 0x8000);
    amptr->yf = fix_make((MAP_YSIZE >> 1) - 1, 0x8000);
    amptr->obj_to_follow = amptr->sensor_obj = PLAYER_OBJ;
    amptr->note_obj = 0;
    amptr->zoom = 2;
    amptr->lh = grd_bm.h;
    amptr->lw = grd_bm.w;
    amap_version_set(id, version);
    amptr->init = TRUE;
}

void amap_invalidate(int id) { oAMap(id)->init = FALSE; }

void amap_settings_copy(curAMap *from, curAMap *to) {
    to->flags = from->flags;
    to->zoom = from->zoom;
}

#ifdef USE_COMPILED_WALLS
uchar wall_seen_p(int wallcode, int csbits, MapElem *cur) {
    // if(textprops[me_tmap_flr(cur)].force_dir==1)
    // return 0;

    if (wallcode < FMK_INT_INT) {
        return csbits & wallcode;
    } else {
        int mo1, mo2, lb1, lb2, ck1, ck2;
        switch (me_tiletype(cur)) {
        case TILE_SOLID_NW:
            mo1 = -MAP_XSIZE;
            mo2 = 1;
            ck1 = FMK_NW;
            ck2 = FMK_WW;
            break;
        case TILE_SOLID_NE:
            mo1 = -MAP_XSIZE;
            mo2 = -1;
            ck1 = FMK_NW;
            ck2 = FMK_EW;
            break;
        case TILE_SOLID_SE:
            mo1 = MAP_XSIZE;
            mo2 = 1;
            ck1 = FMK_SW;
            ck2 = FMK_WW;
            break;
        case TILE_SOLID_SW:
            mo1 = MAP_XSIZE;
            mo2 = -1;
            ck1 = FMK_SW;
            ck2 = FMK_EW;
            break;
        default:
            return 0;
        }
        lb1 = me_clearsolid(cur + mo1);
        lb2 = me_clearsolid(cur + mo2);
        return ((csbits & FMK_INT_XX) || (((lb1 | lb2) != 0) && (((lb1 & ck1) != 0) || ((lb2 & ck2) != 0))));
    }
}
#else
#include "fredge.h"
uchar wall_seen_p(int wallcode, int csbits, MapElem *cur) {
    // if(textprops[me_tmap_flr(cur)].force_dir)
    // return 0;

    if (wallcode == FMK_INT_INT) {
        switch (me_tiletype(cur)) {
        case TILE_SOLID_NW:
            return 1;
        case TILE_SOLID_NE:
            return 1;
        case TILE_SOLID_SE:
            return 1;
        case TILE_SOLID_SW:
            return 1;
        }
    } else {
        switch (get_edge_code(cur, csbits)) {
        case MEDGE_NO_EGRESS:
            return 1;
        case MEDGE_CLIFF_THING:
            return 2;
        case MEDGE_LARGE_STEP:
            return 3;
        case MEDGE_SMALL_STEP:
            return 4;
        }
    }
    return 0;
}
#endif

#ifdef REAL_XIST_CHECK
uchar wall_xist_p(int wallcode, int csbits, MapElem *cur) {
    if (wallcode < FMK_INT_INT)
        return ((csbits & (wallcode >> 4)) == 0); // wow, this is super wacky (tm)? punt diags and go with?
    else {
        switch (me_tiletype(cur)) {
        case TILE_SOLID_NW:
            return 1;
        case TILE_SOLID_NE:
            return 1;
        case TILE_SOLID_SE:
            return 1;
        case TILE_SOLID_SW:
            return 1;
        }
        return 0;
    }
}
#else
#define wall_xist_p(wc, cs, cur) (wallcode < FMK_INT_INT)
#endif

void wall_draw(int xm, int ym, int wallcode, int size, MapElem *cur) {
    switch (wallcode) {
    case FMK_INT_SW:
        am_hline(xm, ym, xm + size);
        break;
    case FMK_INT_NW:
        am_hline(xm, ym - size, xm + size);
        break;
    case FMK_INT_WW:
        am_vline(xm, ym, ym - size);
        break;
    case FMK_INT_EW:
        am_vline(xm + size, ym, ym - size);
        break;
    case FMK_INT_INT:
        if ((me_tiletype(cur) == TILE_SOLID_NW) || (me_tiletype(cur) == TILE_SOLID_SE))
            am_int_line(xm, ym, xm + size, ym - size);
        else
            am_int_line(xm, ym - size, xm + size, ym);
        break;
    }
}

static void tri_draw(int x1, int y1, int x2, int y2, int x3, int y3, int color) {
    grs_vertex vert[3], *pervert[3];

    vert[0].x = fix_make(x1, 0);
    vert[0].y = pix_to_coor(fix_make(y1, 0));
    vert[1].x = fix_make(x2, 0);
    vert[1].y = pix_to_coor(fix_make(y2, 0));
    vert[2].x = fix_make(x3, 0);
    vert[2].y = pix_to_coor(fix_make(y3, 0));

    pervert[0] = &vert[0];
    pervert[1] = &vert[1];
    pervert[2] = &vert[2];

    gr_poly(color, 3, pervert);
}

void tile_draw(int xm, int ym, int tiletype, int size, int offs, int color) {
    while ((offs > 0) && (size < 2 * offs))
        offs--;
    switch (tiletype) {
    case TILE_SOLID_NW:
        tri_draw(xm + size + 1, ym - size + offs - 1, xm + size + 1, ym + 1, xm + offs - 1, ym + 1, color);
        break;
    case TILE_SOLID_SE:
        tri_draw(xm + offs, ym - size + offs, xm + size, ym - size + offs, xm + offs, ym, color);
        break;
    case TILE_SOLID_SW:
        tri_draw(xm + offs, ym - size + offs, xm + size + 1, ym - size + offs, xm + size + 1, ym + 1, color);
        break;
    case TILE_SOLID_NE:
        tri_draw(xm + offs, ym - size + offs, xm + size + 1, ym + 1, xm + offs, ym + 1, color);
        break;
    default:
        gr_set_fcolor(color);
        am_rect(xm + offs, ym - size + offs, xm + size + 1, ym + 1);
    }
}

void obj_draw(int xm, int ym, Obj *cobj, int tsize, int so, int color) {
    xm += ((cobj->loc.x & 0xff) * tsize) >> 8;
    ym -= ((cobj->loc.y & 0xff) * tsize) >> 8;
    gr_set_fcolor(color);
    if (so <= 1)
        so = 1;
    am_rect(xm - so, ym - so, xm + so, ym + so);
}

void line_draw(int xm, int ym, Obj *cobj, int tsize, int full, int color) {
    if (cobj->loc.h & ~0xc0)
        return;
    gr_set_fcolor(color);
    if (cobj->loc.h & 0x40) // ns
    {
        if (cobj->loc.x == 0xff)
            xm += tsize + 1;
        else
            xm += ((cobj->loc.x & 0xff) * tsize) >> 8;
        if (full)
            am_vline(xm, ym, ym - tsize);
        else
            am_vline(xm, ym - (tsize >> 2), ym - 3 * (tsize >> 2));
    } else {
        if (cobj->loc.y == 0xff)
            ym -= tsize + 1;
        else
            ym -= ((cobj->loc.y & 0xff) * tsize) >> 8;
        if (full)
            am_hline(xm, ym, xm + tsize);
        else
            am_hline(xm + (tsize >> 2), ym, xm + 3 * (tsize >> 2));
    }
}

#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MAX(a, b) ((a) < (b) ? (b) : (a))

// if objs, look for creatures, security systems, messages
// need to add bridges... perhaps something for energy, switches and levers
// works in three sorting passes: terrain type stuff (e.g., doors),
// other "real" objects, and map notes and stuff.
void obj_mess(curAMap *amptr, MapElem *curmp, int drw, int xm, int ym, int tsize, int pass) {
    Obj *cobj;
    ObjID cobjid;
    ObjRefID curORef;
    int col, px, md;
    short w, h;
    char buf[50];
    grs_font *fon;

    curORef = curmp->objRef;
    while (curORef != OBJ_REF_NULL) {
        cobjid = objRefs[curORef].obj;
        if ((amptr->flags & AMAP_TRACK_OBJ) && (cobjid == amptr->obj_to_follow)) {
            curORef = objRefs[curORef].next;
            continue;
        }
        if (CitrefCheckHomeSq(curORef)) { // this is the home square... so do stuff
            cobj = &objs[cobjid];
            px = ObjProps[OPNUM(cobjid)].physics_xr;
            px = (px << amptr->zoom) >> 6;
            switch (cobj->obclass) { // critters, doors, elevators, or do we do that with music bits, yes!!! special
            case CLASS_CRITTER:
                if (pass != REAL_OBJ_PASS)
                    break;
                if ((drw & (DRAW_MASK_FULL | DRAW_MASK_RAD)) && (amptr->flags & (AMAP_SHOW_CRIT | AMAP_SHOW_ROB))) {
                    static uchar base_col[3] = {PURPLE_8_BASE + 7, METALBLUE_8_BASE + 7, BRIGHTBROWN_8_BASE + 7};
                    uchar col;

                    if ((cobj->subclass != CRITTER_SUBCLASS_ROBOT) && ((amptr->flags & AMAP_SHOW_CRIT) == 0))
                        break;
                    if (cobj->subclass < 3)
                        col = base_col[cobj->subclass];
                    else
                        col = GRAY_8_BASE;
                    // pick color and intensity based on creature type and all
                    if (cobj->info.type < 5)
                        col -= cobj->info.type;
                    else
                        col -= 5;
                    // okay, this is a hack, but hey, I felt sorry for the
                    // poor guys.
                    if (ID2TRIP(cobjid) != ASSASSIN_TRIPLE)
                        obj_draw(xm, ym, cobj, tsize, px, col);
                }
                break;
            case CLASS_DOOR:
                if (pass != TERR_OBJ_PASS)
                    break;
                // perhaps only draw physics doors???
                if ((ObjProps[OPNUM(cobjid)].flags & TERRAIN_OBJECT) != 0)
                    if (drw & DRAW_MASK_SEEN)                 // used to do FULL too
                        if ((cobj->loc.p | cobj->loc.b) == 0) // if pitched or banked, hit the road
                        {
                            int col = MAIZE_8_BASE;
                            int trip = ID2TRIP(cobjid);
                            if ((trip >= SECRET_DOOR1_TRIPLE) && (trip <= SECRET_DOOR3_TRIPLE))
                                col = GREEN_BASE + 2;
                            if (USE_MODE(cobjid) == NULL_USE_MODE)
                                col = GREEN_BASE + 2;
                            //                     if (trip==INVISO_DOOR_TRIPLE) col=AQUA_8_BASE+2;
                            line_draw(xm, ym, cobj, tsize, cobj->info.current_frame > 0 ? 0 : 1,
                                      col); // if frame, then open, else closed
                        }
                break;
            default:                       // special
                switch (ID2TRIP(cobjid)) { // security cameras, automap notes
                case LARGCPU_TRIPLE:
                case CAMERA_TRIPLE:
                    if (pass != REAL_OBJ_PASS)
                        break;
                    if ((drw & DRAW_MASK_SEEN) && (amptr->flags & AMAP_SHOW_SEC)) {
                        px = tsize >> 3;
                        obj_draw(xm, ym, cobj, tsize, px, RED_8_BASE + 5);
                    }
                    break;
                case MAPNOTE_TRIPLE:
                    if (pass != NOTE_OBJ_PASS)
                        break;
                    if (amptr->flags & AMAP_SHOW_MSG) {
                        if (amptr->note_obj == cobjid) {
                            px = tsize >> 1;
                            col = 2;
                        } else {
                            px = tsize >> 2;
                            col = 6;
                        }
                        obj_draw(xm, ym, cobj, tsize, px, AQUA_8_BASE + col);

                        if (amptr->flags & AMAP_FULL_MSG) {
                            strncpy(buf, amap_note_string(cobjid), 49);
                            buf[49] = 0;
                            fon = gr_get_font();
                            gr_set_font(ResLock(RES_tinyTechFont));
                            md = tsize - 2;
                            gr_string_wrap(buf, md);
                            gr_string_size(buf, &w, &h);
                            if (h > md) {
                                // square up text iteratively
                                md = (w + h) / 2;
                                gr_font_string_unwrap(buf);
                                gr_string_wrap(buf, md);
                                gr_string_size(buf, &w, &h);
                                if (abs(w - h) > 4) {
                                    md = (w + h) / 2;
                                    gr_font_string_unwrap(buf);
                                    gr_string_wrap(buf, md);
                                }
                            }
                            gr_set_fcolor(BLACK + 1);
#ifdef SVGA_SUPPORT
                            {
                                extern uchar shadow_scale;
                                shadow_scale = FALSE;
#endif
#ifdef CORRECT_PIXEL_RATIO
                                draw_shadowed_string(buf, xm + 1, coor_to_pix(ym - tsize + 1), AQUA_8_BASE + col);
#else
                            draw_shadowed_string(buf, xm + 1, ym - tsize + 1, AQUA_8_BASE + col);
#endif
#ifdef SVGA_SUPPORT
                                shadow_scale = TRUE;
                            }
#endif
                            ResUnlock(RES_tinyTechFont);
                            gr_set_font(fon);
                        }
                    }
                    break;
                case ENRG_CHARGE_TRIPLE:
                case CYB_TERM_TRIPLE:
                default:
                    break;
                }
                break;
            }
        } // end of in home square
        curORef = objRefs[curORef].next;
    } // end of ORef loop
} // end of objs loop

void draw_radius_obj(curAMap *amptr, short OtoF, int col, int zeroscrx, int zeroscry, int rad) {
    int xm, ym;

    xm = zeroscrx + ((objs[OtoF].loc.x << amptr->zoom) >> 8);
    ym = zeroscry - ((objs[OtoF].loc.y << amptr->zoom) >> 8);

    gr_set_fcolor(col);
    am_int_circle(xm, ym, (rad << amptr->zoom) >> 8);
}

void draw_full_obj(curAMap *amptr, short OtoF, int col, int zeroscrx, int zeroscry) {
    int hd = objs[amptr->obj_to_follow].loc.h; // should add pitch and bank, really
    int xm, ym;
    fix tx, ty, lx, ly, rx, ry;
    fix csin, ccos;

    xm = zeroscrx + ((objs[OtoF].loc.x << amptr->zoom) >> 8);
    xm = fix_make(xm, 0);
    ym = zeroscry - ((objs[OtoF].loc.y << amptr->zoom) >> 8);
    ym = fix_make(ym, 0);

    hd = (((hd - 64) + 256) & 0xff) << 8;

    fix_sincos(hd, &csin, &ccos);
    ty = ym + fast_fix_mul(csin, FIN_NOSE << amptr->zoom);
    tx = xm + fast_fix_mul(ccos, FIN_NOSE << amptr->zoom);

    fix_sincos((hd + (FIN_SWEEP)) & 0xffff, &csin, &ccos);
    ly = ym + fast_fix_mul(csin, FIN_TAIL << amptr->zoom);
    lx = xm + fast_fix_mul(ccos, FIN_TAIL << amptr->zoom);

    fix_sincos((hd + (0x10000 - FIN_SWEEP)) & 0xffff, &csin, &ccos);
    ry = ym + fast_fix_mul(csin, FIN_TAIL << amptr->zoom);
    rx = xm + fast_fix_mul(ccos, FIN_TAIL << amptr->zoom);

    gr_set_fcolor(col);

    am_fix_line(tx, ty, lx, ly);
    am_fix_line(lx, ly, xm, ym);
    am_fix_line(xm, ym, rx, ry);
    am_fix_line(rx, ry, tx, ty);
}

#ifdef AMAP_SENS_TILEBOUND
static char facecheck[] = {(1 << NORTH) | (1 << WEST), (1 << NORTH) | (1 << EAST), (1 << SOUTH) | (1 << EAST),
                           (1 << SOUTH) | (1 << WEST)};
#endif

#ifdef CORRECT_PIXEL_RATIO
void amap_pixratio_set(fix ratio) {
    if (ratio == 0)
        ratio = fix_make(grd_screen_canvas->bm.h, 0) * STD_SCR_WID / (grd_screen_canvas->bm.w * STD_SCR_HGT);

#ifdef FIX_PIXRATIO
    pixratio_yx = ratio;
    pixratio_xy = fix_div(FIX_UNIT, ratio);
#else
    // note once again that this only works if pixratio_shf is only
    // ever intended to be positive.
    pixratio_shf = 0;
    while (ratio > FIX_UNIT) {
        ratio = ratio >> 1;
        pixratio_shf++;
    }
    if (ratio < ((FIX_UNIT * 707) / 1000)) { // root(2)/2, or half a shift
        pixratio_shf--;
    }
#endif
}
#endif

void amap_draw(curAMap *amptr, int expose) {
    int xc, yc, xm, ym, drw, static_drw, cv; // loop control, so on
    int zeroscrx, zeroscry, init_yc;         // x and y screen coordinate for 0,0 of map
    int tsize = 1 << amptr->zoom;
#ifdef AMAP_SENS_TILEBOUND
    int facemask;
#endif
    int mt, pass;
    ushort sensor_x, sensor_y;
    MapElem *curmp = MAP_GET_XY(0, 0);
    fix amrh, amrw;
    int max_xc, max_yc, xbase, crnr_x, crnr_y; // am w and h radius
    MapElem *mapybase, *init_mapybase;

    if (amptr->flags & AMAP_TRACK_OBJ) {
        amptr->xf = objs[amptr->obj_to_follow].loc.x << 8;
        amptr->yf = fix_make(MAP_YSIZE, 0) - 1 - (objs[amptr->obj_to_follow].loc.y << 8);
    }

    amptr->lw = grd_bm.w;
    amptr->lh = grd_bm.h;

    zeroscrx = (grd_bm.w >> 1) - fix_int(amptr->xf << amptr->zoom);
    zeroscry = pix_to_coor(grd_bm.h >> 1) - fix_int(amptr->yf << amptr->zoom);
    zeroscry = zeroscry + (MAP_YSIZE << amptr->zoom);
    if (amptr->sensor_obj != OBJ_NULL) {
        sensor_x = objs[amptr->sensor_obj].loc.x;
        sensor_y = objs[amptr->sensor_obj].loc.y;
    } else
        sensor_x = sensor_y = 0;

    amrw = (grd_bm.w << 15) >> amptr->zoom;              // w radius of amap, in fix point tiles
    amrh = pix_to_coor((grd_bm.h << 15) >> amptr->zoom); // h radius of amap, in fix point tiles
    xc = fix_int(amptr->xf - amrw);
    max_xc = 1 + fix_int(amptr->xf + amrw);
    yc = MAP_YSIZE - (1 + fix_int(amptr->yf + amrh));
    max_yc = MAP_YSIZE - fix_int(amptr->yf - amrh);
    if (xc < 0)
        xc = 0;
    if (max_xc >= MAP_XSIZE)
        max_xc = MAP_XSIZE - 1;
    if (yc < 0)
        yc = 0;
    if (max_yc >= MAP_XSIZE)
        max_yc = MAP_XSIZE - 1;
    xbase = xc;
    crnr_x = zeroscrx + (xc << amptr->zoom);
    crnr_y = zeroscry - (yc << amptr->zoom);
    mapybase = curmp + (yc * MAP_XSIZE) + xc;

    //   mprintf("rect %d %d and %d %d, crnr %d %d from rw and rh %.2q %.2q cnt %.2q
    //   %.2q....\n",xc,yc,max_xc,max_yc,crnr_x,crnr_y,amrw,amrh,amptr->xf,amptr->yf);

    //   mprintf("Zero at %d %d...\n",zeroscrx,zeroscry);

    if (expose)
        gr_clear(0xFF);
    static_drw = 0;
    if (amptr->flags & AMAP_SHOW_ALL)
        static_drw |= DRAW_MASK_FULL | DRAW_MASK_TERR;
    else if (amptr->flags & AMAP_SHOW_FLR)
        static_drw |= DRAW_MASK_TERR;
    if (amptr->flags & AMAP_SHOW_SENS)
        static_drw |= DRAW_MASK_SENS;
    gr_set_fcolor(GREEN_BASE + 2);
    init_mapybase = mapybase;
    init_yc = yc;

    // draw hazard/elevator floor colors BEFORE drawing walls.
    for (ym = crnr_y; yc < max_yc; yc++, ym -= tsize, mapybase += MAP_XSIZE)
        for (curmp = mapybase, xc = xbase, xm = crnr_x; xc < max_xc; xc++, xm += tsize, curmp++) {
            drw = static_drw;
            if (me_bits_seen(curmp))
                drw |= DRAW_MASK_SEEN;
            if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) < amptr->sensor_rad)
                drw |= DRAW_MASK_RAD;

            if (((mt = me_tiletype(curmp)) != TILE_SOLID) && (drw != 0)) {
                if (drw & (DRAW_MASK_SEEN | DRAW_MASK_RAD)) {
                    // if hazard or elevator, floor draw
                    // check music bits for elevators!
                    if (me_bits_music(curmp) == ELEVATOR_ZONE)
                        tile_draw(xm, ym, mt, tsize, 1, BROWN_8_BASE + 4);
                    if (amptr->flags & AMAP_SHOW_HAZ) { // bio, rad, both
                        static uchar col_map[] = {YELLOW_8_BASE + 6, RED_8_BASE + 6, ORANGE_8_BASE + 5};
                        int hv = 0;
                        if (level_gamedata.hazard.zerogbio == 0)
                            if (me_hazard_bio_x(curmp))
                                hv = 1;
                        if (me_hazard_rad_x(curmp))
                            hv |= 2;
                        if (hv)
                            tile_draw(xm, ym, (drw & DRAW_MASK_SEEN) ? mt : TILE_OPEN, tsize, 1, col_map[hv - 1]);
                    }
                }
            }
        }
    mapybase = init_mapybase;
    yc = init_yc;
    // now draw walls and such
    for (ym = crnr_y; yc < max_yc; yc++, ym -= tsize, mapybase += MAP_XSIZE)
        for (curmp = mapybase, xc = xbase, xm = crnr_x; xc < max_xc; xc++, xm += tsize, curmp++) {
            drw = static_drw;
            if (me_bits_seen(curmp))
                drw |= DRAW_MASK_SEEN;
            if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) < amptr->sensor_rad)
                drw |= DRAW_MASK_RAD;

            if ((me_tiletype(curmp) != TILE_SOLID) && (drw != 0)) {
                int csbits = me_clearsolid(curmp), loop;

#ifndef REAL_XIST_CHECK
                if (drw & DRAW_MASK_TERR) {
                    gr_set_fcolor(GREEN_BASE + 9);
                    wall_draw(xm, ym, FMK_INT_NW, tsize, curmp);
                    wall_draw(xm, ym, FMK_INT_EW, tsize, curmp);
                    wall_draw(xm, ym, FMK_INT_SW, tsize, curmp);
                    wall_draw(xm, ym, FMK_INT_WW, tsize, curmp);
                    gr_set_fcolor(GREEN_BASE + 2);
                }
#endif

                gr_set_fcolor(GREEN_BASE + 2);

#ifdef USE_COMPILED_WALLS
                for (loop = (1 << 4); loop <= FMK_INT_INT; loop <<= 1)
                    if (wall_seen_p(loop, csbits, curmp))
                        put back in later.wall_draw(xm, ym, loop, tsize, curmp);
#else
                for (loop = (1 << 4), csbits = 0; loop <= FMK_INT_INT; csbits++, loop <<= 1)
                    if ((drw & DRAW_MASK_SEEN) &&
                        (cv = wall_seen_p(loop, csbits, curmp))) { // colors are gb+2,5,8 for wall,cliff,bigstep
                        gr_set_fcolor(GREEN_BASE + 2 + (2 * (cv - 1)));
                        wall_draw(xm, ym, loop, tsize, curmp);
                    }
#endif

#ifdef REAL_XIST_CHECK
                    else if (amptr->flags & AMAP_SHOW_ALL)
                        if (wall_xist_p(loop, csbits, curmp)) {
                            gr_set_fcolor(GREEN_BASE + 9);
                            wall_draw(xm, ym, loop, tsize, curmp);
                            gr_set_fcolor(GREEN_BASE + 2);
                        }
#endif

            } // if !tile_solid
#ifdef AMAP_SENS_TILEBOUND
            if ((drw & DRAW_MASK_RAD) && (drw & DRAW_MASK_SENS)) {
                if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) + (1 << 8) >=
                    amptr->sensor_rad) {
                    facemask = 0;

                    if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, ((yc + 1) << 8) + 0x80 - sensor_y) >=
                        amptr->sensor_rad)
                        facemask |= (1 << NORTH);
                    if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, ((yc - 1) << 8) + 0x80 - sensor_y) >=
                        amptr->sensor_rad)
                        facemask |= (1 << SOUTH);
                    if (fix_fast_pyth_dist((xc + 1 << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) >=
                        amptr->sensor_rad)
                        facemask |= (1 << EAST);
                    if (fix_fast_pyth_dist((xc - 1 << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) >=
                        amptr->sensor_rad)
                        facemask |= (1 << WEST);

                    if (facemask) {
                        gr_set_fcolor(GRAY_8_BASE + 3);
                        if (drw & DRAW_MASK_SEEN) {
                            mt = me_tiletype(curmp);
                            if (mt >= TILE_SOLID_NW && mt <= TILE_SOLID_SW) {
                                mt -= TILE_SOLID_NW;
                                if ((facemask & facecheck[mt]) == facecheck[mt]) {
                                    wall_draw(xm, ym, FMK_INT_INT, tsize, curmp);
                                    facemask ^= facecheck[mt];
                                }
                            }
                        }

                        if (facemask & (1 << NORTH))
                            wall_draw(xm, ym, FMK_INT_NW, tsize, curmp);
                        if (facemask & (1 << SOUTH))
                            wall_draw(xm, ym, FMK_INT_SW, tsize, curmp);
                        if (facemask & (1 << EAST))
                            wall_draw(xm, ym, FMK_INT_EW, tsize, curmp);
                        if (facemask & (1 << WEST))
                            wall_draw(xm, ym, FMK_INT_WW, tsize, curmp);
                    }
                }
            }
#endif
        } // for y loop

    for (pass = 0; pass < NUM_OBJ_PASSES; pass++) {
        mapybase = init_mapybase;
        yc = init_yc;
        for (ym = crnr_y; yc < max_yc; yc++, ym -= tsize, mapybase += MAP_XSIZE)
            for (curmp = mapybase, xc = xbase, xm = crnr_x; xc < max_xc; xc++, xm += tsize, curmp++) {
                drw = static_drw;
                if (me_bits_seen(curmp))
                    drw |= DRAW_MASK_SEEN;
                if (fix_fast_pyth_dist((xc << 8) + 0x80 - sensor_x, (yc << 8) + 0x80 - sensor_y) < amptr->sensor_rad)
                    drw |= DRAW_MASK_RAD;

                obj_mess(amptr, curmp, drw, xm, ym, tsize, pass);
            }
    }

    // really should be in a "track_player" mode, not always...
    draw_full_obj(amptr, PLAYER_OBJ, RED_BASE + 3, zeroscrx, zeroscry);
    if ((amptr->flags & AMAP_TRACK_OBJ) && (amptr->obj_to_follow != PLAYER_OBJ))
        draw_full_obj(amptr, amptr->obj_to_follow, PURPLE_8_BASE + 3, zeroscrx, zeroscry);
#ifdef AMAP_SENS_CIRCLE
    if (drw & DRAW_MASK_SENS) {
        int r = amptr->sensor_rad;
        // accound for needing to see the center of square and
        // the fact that we're an octagon, not a circle.
        r = (r * 89) / 100 - (1 << 8) * 707 / 1000;
        draw_radius_obj(amptr, PLAYER_OBJ, GRAY_8_BASE, zeroscrx, zeroscry, r);
        r = amptr->sensor_rad + (1 << 8) * 707 / 1000;
        draw_radius_obj(amptr, PLAYER_OBJ, GRAY_8_BASE + 4, zeroscrx, zeroscry, r);
    }
#endif
}

// x and y are window relative, return the map square?
// returns NULL for not in map, else a mapelem *
// this can be checked for mapnotes, or one can be added
// functions to do these things exist as well
// Note side effects: can set amptr->xf and amptr->yf
// x and y set to map square coordinates on exit.
void *amap_loc_to_sq(curAMap *amptr, int *x, int *y) {
    int offsx, offsy;
    MapElem *curmp = MAP_MAP;
    fix tmpy;

    if (amptr->flags & AMAP_TRACK_OBJ) {
        amptr->xf = objs[amptr->obj_to_follow].loc.x << 8;
        amptr->yf = fix_make(MAP_YSIZE, 0) - 1 - (objs[amptr->obj_to_follow].loc.y << 8);
    }
    tmpy = fix_make(MAP_YSIZE, 0) - 1 - amptr->yf;

    offsx = (*x) - (amptr->lw >> 1) + fix_int(amptr->xf << amptr->zoom);
    offsy = -*y + pix_to_coor(amptr->lh >> 1) + fix_int(tmpy << amptr->zoom);

    offsx >>= amptr->zoom;
    offsy >>= amptr->zoom;
    *x = offsx;
    *y = offsy; // sneaky sneaky
    if ((offsx & (~((1 << MAP_XSHF) - 1))) | (offsy & (~((1 << MAP_YSHF) - 1))))
        return NULL;
    else
        return (void *)(curmp + offsx + (offsy << MAP_XSHF));
}

// Returns ObjID of note or OBJ_NULL if none is there
ObjID amap_loc_get_note(void *map_sq) {
    ObjID cobjid;
    ObjRefID curORef;
    MapElem *curmp = (MapElem *)map_sq;

    curORef = curmp->objRef;
    while (curORef != OBJ_REF_NULL) {
        cobjid = objRefs[curORef].obj;
        if (CitrefCheckHomeSq(curORef))
            if (ID2TRIP(cobjid) == MAPNOTE_TRIPLE)
                return cobjid;
        curORef = objRefs[curORef].next;
    } // end of ORef loop
    return OBJ_NULL;
}

#define MAP_LOOK_AROUND
// sets to_do to AMAP_OFF_MAP, AMAP_HAVE_NOTE, AMAP_NO_NOTE
// returns OBJ_NULL if NO_NOTE or OFF_MAP
// returns Obj of the map_note if HAVE_NOTE
ObjID amap_loc_note_check(curAMap *amptr, void *curmp, int *x, int *y, int *to_do) {
    ObjID map_note;
    if (curmp == NULL) {
        *to_do = AMAP_OFF_MAP;
        return OBJ_NULL;
    }
#ifdef MAP_LOOK_AROUND
    map_note = amap_loc_get_note(curmp);
    if (map_note == OBJ_NULL) { // check around, in the traditional way - big zoom = small map
        int extloop, inloop, clen, dvec[2] = {0, 1}, rad = 2 * (3 - amptr->zoom), mx = *x, my = *y;
        //      mprintf("Looking around %d from %x %x dv %d %d\n",rad,mx,my,dvec[0],dvec[1]);
        for (clen = 1; clen < rad; clen++)            // for each radius
            for (extloop = 0; extloop < 2; extloop++) // two of each
            {
                for (inloop = 0; inloop < clen; inloop++) {
                    mx += dvec[0];
                    my += dvec[1];
                    if (((mx >= 0) && (mx < MAP_XSIZE)) && ((my >= 0) && (my < MAP_YSIZE))) {
                        map_note = amap_loc_get_note(MAP_GET_XY(mx, my));
                        if (map_note != OBJ_NULL) {
                            *x = mx;
                            *y = my;
                            goto hack_breakout;
                        }
                    }
                }
                if (dvec[0] != 0) {
                    dvec[1] = -dvec[0];
                    dvec[0] = 0;
                } else {
                    dvec[0] = dvec[1];
                    dvec[1] = 0;
                }
            }
    }
hack_breakout:
#else
    map_note = amap_loc_get_note(curmp);
#endif
    if (map_note != OBJ_NULL) { // a note is there...
        *to_do = AMAP_HAVE_NOTE;
        return map_note;
    } else {
        *to_do = AMAP_NO_NOTE;
        return OBJ_NULL;
    }
}

uchar amap_flags(curAMap *amptr, int flags, int set) {
    flags &= amptr->avail_flags;
    if (flags == 0)
        return FALSE;
    switch (set) {
    case AMAP_SET:
        amptr->flags |= flags;
        break;
    case AMAP_UNSET:
        amptr->flags &= ~flags;
        break;
    case AMAP_TOGGLE:
        if (amptr->flags & flags)
            amptr->flags &= ~flags;
        else
            amptr->flags |= flags;
        break;
    }
    return TRUE;
}

uchar amap_zoom(curAMap *amptr, uchar set, int zoom_delta) {
    if (set)
        amptr->zoom = zoom_delta;
    else {
        if (zoom_delta > 0) {
            if (amptr->zoom + zoom_delta >= AMAP_MAX_ZOOM)
                return FALSE;
        } else {
            if (amptr->zoom + zoom_delta < AMAP_MIN_ZOOM)
                return FALSE;
        }
        amptr->zoom += zoom_delta;
    }
    return TRUE;
}

void amap_pan(curAMap *amptr, int dir, int *dist) {
    int d;
    d = *dist >> amptr->zoom;
    *dist -= d << amptr->zoom;
    switch (dir) {
    case AMAP_PAN_E:
        amptr->xf += d;
        break;
    case AMAP_PAN_W:
        amptr->xf -= d;
        break;
    case AMAP_PAN_N:
        amptr->yf -= d;
        break;
    case AMAP_PAN_S:
        amptr->yf += d;
        break;
    }
    amptr->flags &= ~AMAP_TRACK_OBJ;
}

void *amap_deal_with_map_click(curAMap *amptr, int *x, int *y) {
    int todo;
    // actually clicked on the map, we should deal...
    MapElem *curmp = (MapElem *)amap_loc_to_sq(amptr, x, y);
    ObjID note = amap_loc_note_check(amptr, curmp, x, y, &todo);
    switch (todo) {
    case AMAP_NO_NOTE:
        amptr->note_obj = OBJ_NULL;
        return curmp;
    case AMAP_HAVE_NOTE:
        amptr->note_obj = note;
        return curmp;
    }
    //   case AMAP_OFF_MAP:   return NULL;
    return NULL;
}

// string hacks
char amap_strings[AMAP_STRING_SIZE];
char *amap_str_ptr = &amap_strings[0];

char *amap_str_next(void) { return amap_str_ptr; }

void amap_str_grab(char *str) { amap_str_ptr = str + strlen(str) + 1; }

int amap_str_deref(char *str) {
    return str - &amap_strings[0]; // get offset into string cluster
}

char *amap_str_reref(int offs) {
    return (&amap_strings[0]) + offs; // go offset into string cluster
}

void amap_str_startup(int magic_num) { amap_str_ptr = &amap_strings[0] + magic_num; }

void amap_fixup_existing(int tolera, int delta) {
    ObjSpecID pmo;
    ObjID cur_obj;
    for (pmo = objTraps[OBJ_SPEC_NULL].id; pmo != OBJ_SPEC_NULL; pmo = objTraps[pmo].next) {
        cur_obj = objTraps[pmo].id;
        if (ID2TRIP(cur_obj) == MAPNOTE_TRIPLE)
            if (amap_note_value(cur_obj) > tolera)
                amap_note_value(cur_obj) -= delta;
    }
}

// simply recompact
void amap_str_delete(char *toast_str) {
    int del_len = strlen(toast_str) + 1;  // how much to delete
    char *s = toast_str + del_len;        // beginning of real data
    int recompact_len = amap_str_ptr - s; // how much to copy around
    if (s == amap_str_ptr)
        amap_str_ptr = toast_str; // we are freeing the last created string
    else {
        LG_memmove(toast_str, s, recompact_len);                 // move over the data
        amap_str_ptr -= del_len;                                 // move the next pointer back
        amap_fixup_existing(amap_str_deref(toast_str), del_len); // set current notes up right...
    }
}

uchar amap_get_note(curAMap *amptr, char *buf) {
    uchar retval = TRUE;
// later, do this for real
// ie base on the string stuff
#ifdef USE_OBJ
    if (amptr->note_obj != 0) {
        strcpy(buf, "map note 0000");
        buf[9] = '0' + (((int)amptr->note_obj) / 1000) % 10;
        buf[10] = '0' + (((int)amptr->note_obj) / 100) % 10;
        buf[11] = '0' + (((int)amptr->note_obj) / 10) % 10;
        buf[12] = '0' + (((int)amptr->note_obj) % 10);
    }
#else
    if (amptr->note_obj != 0)
        strcpy(buf, amap_note_string(amptr->note_obj));
#endif
    else {
        retval = FALSE;
        strcpy(buf, get_temp_string(REF_STR_NoMapMessage));
    }
    return retval;
}

grs_bitmap *screen_automap_bitmap(char c) {
    extern grs_bitmap *static_bitmap;
    return (static_bitmap);
}
