DataMuseum.dk

Presents historical artifacts from the history of:

DKUUG/EUUG Conference tapes

This is an automatic "excavation" of a thematic subset of
artifacts from Datamuseum.dk's BitArchive.

See our Wiki for more about DKUUG/EUUG Conference tapes

Excavated with: AutoArchaeologist - Free & Open Source Software.


top - metrics - download
Index: T d

⟦8c20d3968⟧ TextFile

    Length: 16834 (0x41c2)
    Types: TextFile
    Names: »draw.c«

Derivation

└─⟦b20c6495f⟧ Bits:30007238 EUUGD18: Wien-båndet, efterår 1987
    └─⟦this⟧ »EUUGD18/X/Xconq/draw.c« 

TextFile

/* Copyright (c) 1987, 1988  Stanley T. Shebs, University of Utah. */
/* This program may be used, copied, modified, and redistributed freely */
/* for noncommercial purposes, so long as this notice remains intact. */

/* RCS $Header: draw.c,v 1.1 88/06/21 12:30:08 shebs Exp $ */

/* Geometry is somewhat tricky because our viewports are supposed */
/* to wrap around a cylinder transparently.  The general idea is that */
/* if modulo opns map x coordinates onto a cylinder, adding and subtracting */
/* the diameter of the cylinder "un-modulos" things.  If this doesn't make */
/* any sense to you, then be careful about fiddling with the code! */

/* If that wasn't bad enough, the hexes constitute an oblique coordinate */
/* where the axes form a 60 degree angle to each other.  Fortunately, no */
/* trig is necessary - to convert to/from rectangular, add/subtract 1/2 of */
/* the y coordinate to x, and leave the y coordinate alone. */

/* The graphical code uses mostly text drawing functions, which are more */
/* likely to be efficient than is random bitblting.  (The interface may */
/* implement the operations as random blitting, but that's OK.) */

#include "config.h"
#include "misc.h"
#include "period.h"
#include "side.h"
#include "unit.h"
#include "map.h"

extern bool populations;        /* used to decide about running pop display */

char rowbuf[BUFSIZE];           /* buffer for terrain row drawing */

/* Completely redo a screen, making no assumptions about appearance. */
/* This one is used frequently, especially when a window is exposed. */

redraw(side)
Side *side;
{
    if (active_display(side)) {
	erase_cursor(side);
	clear_window(side, side->main);
	show_note(side);
	show_info(side);
	show_prompt(side);
	show_all_sides(side);
	show_timemode(side);
	show_clock(side);
	show_state(side);
	show_map(side);
	show_world(side);
	flush_output(side);
	flush_input(side);
    }
}

/* Ensure that given location is visible.  We also flush the input because */
/* any input relating to a different screen is probably worthless. */

put_on_screen(side, x, y)
Side *side;
int x, y;
{
    /* Ugly hack to prevent extra boxes being drawn during init - don't ask!*/
    if (x == 0 && y == 0) return;
    if (active_display(side)) {
	if (!in_middle(side, x, y)) {
	    side->vcx = wrap(x);
	    side->vcy = min(max(side->vh2-(1-(side->vh&1)), y),
			    (world.height-1)-side->vh2);
	    if (side->lastvcx >= 0) undraw_box(side);
	    show_map(side);
	    flush_output(side);
            flush_input(side);
	}
    }
}

/* Decide whether given location is not too close to edge of screen. */
/* We do this because it's a pain to move units when half the adjacent */
/* places aren't even visible.  This routine effectively places a lower */
/* limit of 5x5 for the map window. (I think) */

in_middle(side, x, y)
Side *side;
int x, y;
{
    int vcx = side->vcx, vcy = side->vcy;
    int vw2 = side->vw2, vh2 = side->vh2;

    if (!between(vcy-vh2+2, y, vcy+vh2-2) && between(2, y, world.height-3))
	return FALSE;
    x = unwrap(side, x + (y - vcy) / 2);
    return between(vcx-vw2+2, x, vcx+vw2-3+(side->vw&1));
}

/* Transform map coordinates into screen coordinates, relative to the given */
/* side.  Allow for cylindricalness and number of pixels in a hex. */

xform(side, x, y, sxp, syp)
Side *side;
int x, y, *sxp, *syp;
{
    *sxp = ((side->hw * (x - (side->vcx - side->vw2))) +
	    (side->hw * (y - side->vcy)) / 2);
    *syp = side->hch * ((side->vcy + side->vh2) - y);
}

/* Undo the wrapping effect, relative to viewport location. */
/* Note that both conditions cannot both be true at the same time, */
/* since viewport is smaller than map. */

unwrap(side, x)
Side *side;
int x;
{
    int vcx = side->vcx, vw2 = side->vw2, vw34 = side->vw2 + (side->vw2 >> 1);

    if (vcx - vw2 < 0 && x > vcx + vw34) x -= world.width;
    if (vcx + vw2 > world.width-1 && x < vcx - vw34) x += world.width;
    return x;
}

/* Un-transform screen coordinates (as supplied by mouse perhaps) into */
/* map coordinates.  This doesn't actually account for the details of */
/* hexagonal boundaries, and actually discriminates box-shaped areas. */

deform(side, sx, sy, xp, yp)
Side *side;
int sx, sy, *xp, *yp;
{
    int vcx = side->vcx, vcy = side->vcy, adjust;

    *yp = (vcy + side->vh2) - (sy / side->hch);
    adjust = (((*yp - vcy) & 1) ? ((side->hw/2) * (*yp >= vcy ? 1 : -1)) : 0);
    *xp = wrap(((sx - adjust) /	side->hw) - (*yp - vcy) / 2 +
	       (vcx - side->vw2));
}

/* Transform coordinates in the world display.  Simpler, since no moving */
/* viewport nonsense. */

w_xform(side, x, y, sxp, syp)
Side *side;
int x, y, *sxp, *syp;
{
    *sxp = side->mm * x + (side->mm * y) / 2;
    *syp = side->mm * (world.height - 1 - y);
}

/* Redraw the map of the whole world.  We use square blobs instead of icons, */
/* since individual "hexes" may be as little as 1x1 pixels in size, and */
/* there are lots of them.  Algorithm uses run-length encoding to find and */
/* draw bars of constant color.  Monochrome just draws units - no good */
/* choices for terrain display. */

show_world(side)
Side *side;
{
    int x, y, color, barcolor, x1;

    if (active_display(side) && world_display(side)) {
	clear_window(side, side->world);
	for (y = world.height-1; y >= 0; --y) {
	    x1 = 0;
	    barcolor = world_color(side, x1, y);
	    for (x = 0; x < world.width; ++x) {
		color = world_color(side, x, y);
		if (color != barcolor) {
		    draw_bar(side, x1, y, x - x1, barcolor);
		    x1 = x;
		    barcolor = color;
		}
	    }
	    draw_bar(side, x1, y, world.width - x1, barcolor);
	}
	if (side->vcy >= 0) draw_box(side);
    }
}

/* Compute the color representing a hex from the given side's point of view. */

world_color(side, x, y)
Side *side;
int x, y;
{
    int view = side_view(side, x, y);
    Side *side2;

    if (side->monochrome) {
	return ((view == UNSEEN || view == EMPTY) ? side->bgcolor :
		                                    side->fgcolor);
    } else {
	if (view == UNSEEN) {
	    return (side->bgcolor);
	} else if (view == EMPTY) {
	    return (side->hexcolor[terrain_at(x, y)]);
	} else {
	    side2 = side_n(vside(view));
	    return ((side2 == NULL) ? side->neutcolor :
		    (allied_side(side2, side) ? side->altcolor :
		     side->enemycolor));
	}
    }
}

/* Draw an outline box on the world map.  Since we adopt the dubious trick */
/* of inverting through all planes, must be careful to undo before moving; */
/* also, draw/undraw shiftedly so both boxes appear on both sides of world. */

draw_box(side)
Side *side;
{
    invert_box(side, side->vcx, side->vcy);
    invert_box(side, side->vcx - world.width, side->vcy);
    side->lastvcx = side->vcx;  side->lastvcy = side->vcy;
}

undraw_box(side)
Side *side;
{
    invert_box(side, side->lastvcx, side->lastvcy);
    invert_box(side, side->lastvcx - world.width, side->lastvcy);
}

/* Draw immediate area in more detail.  We make a little effort to avoid */
/* drawing hexes off the visible part of the screen, but are still somewhat */
/* conservative, so as not to get holes in the display.  Implication is that */
/* some lower level of routines has to be able to clip the map window. */

show_map(side)
Side *side;
{
    int y1, y2, y, x1, x2, adj;

    if (active_display(side)) {
	clear_window(side, side->map);
	y1 = side->vcy + side->vh2;
	y2 = side->vcy - side->vh2 + 1 - (side->vh & 1);
	for (y = y1; y >= y2; --y) {
	    adj = (y - side->vcy) / 2;
	    x1 = side->vcx - side->vw2 - adj - 1;
	    x2 = side->vcx + side->vw2 - adj + 1 + (side->vw & 1);
	    draw_row(side, x1, y, x2 - x1);
	}
	draw_cursor(side);
	flush_output(side);
	draw_box(side);  /* must be after flush */
    }
}

/* Draw an individual detailed hex, as a row of one. */
/* This routine may be called in cases where the hex is not on the main */
/* screen;  if so, then the world map but not the local map is drawn on. */
/* (should the display be shifted to make visible?) */

draw_hex(side, x, y, flushit)
Side *side;
int x, y;
bool flushit;
{
    int sx, sy;

    if (active_display(side)) {
	if (side->monochrome || side->showmode == TERRICONS) {
	    xform(side, unwrap(side, x), y, &sx, &sy);
	    draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
	}
	draw_row(side, unwrap(side, x), y, 1);
	draw_bar(side, x, y, 1, world_color(side, x, y));
	if (flushit) flush_output(side);
    }
}

/* The basic map drawing routine does an entire row at a time, which yields */
/* order-of-magnitude speedups (!).  This routine is complicated by several */
/* tricks:  1) in monochrome, the entire line can be drawn at once; 2) in */
/* color, run-length encoding maximizes the length of constant-color strings */
/* and 3) anything which is in the background color need not be drawn. */
/* In general, this routine dominates the map viewing process, so efficiency */
/* here is very important. */

draw_row(side, x0, y0, len)
Side *side;
int x0, y0, len;
{
    bool empty = TRUE;
    char ch;
    int i = 0, x, x1, color, segcolor, sx, sy;

    if (side->monochrome) {
	xform(side, x0, y0, &sx, &sy);
	for (x = x0; x < x0 + len; ++x) {
	    if (side_view(side, wrap(x), y0) == EMPTY) {
		rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
		empty = FALSE;
	    } else {
		rowbuf[i++] = ' ';
	    }
	}
	if (!empty) draw_terrain_row(side, sx, sy, rowbuf, i, side->fgcolor);
    } else {
	x1 = x0;
	segcolor = hex_color(side, x0, y0);
	for (x = x0; x < x0 + len; ++x) {
	    color = hex_color(side, x, y0);
	    if (color != segcolor) {
		if (segcolor != side->bgcolor) {
		    xform(side, x1, y0, &sx, &sy);
		    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
		}
		i = 0;
		x1 = x;
		segcolor = color;
	    }
	    switch(side->showmode) {
	    case FULLHEX:
	    case BOTHICONS:
		ch = HEX;
		break;
	    case BORDERHEX:
		ch = OHEX;
		break;
	    case TERRICONS:
		ch = ttypes[terrain_at(wrap(x), y0)].tchar;
		break;
	    }
	    rowbuf[i++] = ch;
	}
	if (len == 1) i = 1;
	xform(side, x1, y0, &sx, &sy);
	draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
	if (side->showmode == BOTHICONS) {
	    i = 0;
	    x1 = x0;
	    segcolor = terricon_color(side, x0, y0);
	    for (x = x0; x < x0 + len; ++x) {
		color = terricon_color(side, x, y0);
		if (color != segcolor) {
		    xform(side, x1, y0, &sx, &sy);
		    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
		    i = 0;
		    x1 = x;
		    segcolor = color;
		}
		rowbuf[i++] = ttypes[terrain_at(wrap(x), y0)].tchar;
	    }
	    if (len == 1) i = 1;
	    xform(side, x1, y0, &sx, &sy);
	    draw_terrain_row(side, sx, sy, rowbuf, i, segcolor);
	}
    }
    /* Units are much harder to optimize - fortunately they're sparse */
    for (x = x0; x < x0 + len; ++x) {
	draw_unit(side, x, y0);
    }
}

/* Return the color of the hex. (for color displays only) */

hex_color(side, x, y)
Side *side;
int x, y;
{
    return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
	    side->hexcolor[terrain_at(wrap(x), y)]);
}

/* Return the color of a terrain icon overlaying a colored hex. */

terricon_color(side, x, y)
Side *side;
int x, y;
{
    return ((side_view(side, wrap(x), y) == UNSEEN) ? side->bgcolor :
	    (ttypes[terrain_at(wrap(x), y)].dark ? side->fgcolor :
	     side->bgcolor));
}

/* Draw a single unit icon as appropriate.  This *also* has a bunch of */
/* details to worry about: centering of icon in hex, clearing a rectangular */
/* area for the icon, picking a color for the unit, using either a bitmap */
/* or font char, and adding a side number for many-player games. */
/* Must also be careful not to draw black-on-black for units in space. */
/* This routine has also been drafted into drawing populace side numbers */
/* for otherwise empty hexes. */

draw_unit(side, x, y)
Side *side;
int x, y;
{
    int view = side_view(side, wrap(x), y), sx, sy, ucolor, hcolor, n;
    int terr = terrain_at(wrap(x), y), pop, pcolor;
    Side *side2;

    if (view != UNSEEN) {
	if (view == EMPTY) {
	    if (populations) {
		pop = people_at(wrap(x), y);
		if (pop != NOBODY) {
		    side2 = side_n(pop-8);
		    pcolor = (allied_side(side, side2) ? side->owncolor :
			      (enemy_side(side, side2) ? side->enemycolor :
			       side->neutcolor));
		    if (pcolor == side->owncolor && 
			(ttypes[terr].dark ||
			 side->monochrome ||
			 (side->showmode == TERRICONS)))
			pcolor = side->fgcolor;
		    xform(side, x, y, &sx, &sy);
		    draw_side_number(side, side->map, sx, sy, pop-8, pcolor);
		}
	    }
	} else {
	    xform(side, x, y, &sx, &sy);
	    side2 = side_n(vside(view));
	    ucolor = (allied_side(side, side2) ? side->owncolor :
		      (enemy_side(side, side2) ? side->enemycolor :
		       side->neutcolor));
	    if (ucolor == side->owncolor && 
		(ttypes[terr].dark ||
		 side->monochrome ||
		 (side->showmode == TERRICONS)))
		ucolor = side->fgcolor;
	    if (side->monochrome && side != side2)
		ucolor = side->bgcolor;
	    hcolor = (side == side2 ? side->bgcolor : side->fgcolor);
	    if (side->monochrome) {
		/* erasing background */
		draw_hex_icon(side, side->map, sx, sy, hcolor, HEX);
	    } else if (side->showmode != TERRICONS) {
		draw_hex_icon(side, side->map, sx, sy, hex_color(side, x, y),
			      ((side->showmode == BORDERHEX) ? OHEX : HEX));
	    }
	    draw_unit_icon(side, side->map, sx, sy, vtype(view), ucolor);
	    n = side_number(side2);
	    if ((numsides > 2 || side->monochrome) && n != side_number(side)) {
		draw_side_number(side, side->map, sx, sy, n, ucolor);
	    }
	}
    }
}

/* Cursor drawing also draws the unit in some other color if it's not the */
/* "top-level" unit in a hex, as well as getting the player's attention */
/* if the new location is sufficiently far from the last. */

draw_cursor(side)
Side *side;
{
    int sx, sy;

    if (active_display(side)) {
	/* ugly hack to prevent extra cursor draw */
	if (side->cury == 0) return;
	xform(side, unwrap(side, side->curx), side->cury, &sx, &sy);
	if (side->curunit != NULL && side->curunit->transport != NULL) {
	    if (side->monochrome) {
		draw_hex_icon(side, side->map, sx, sy, side->bgcolor, HEX);
		draw_unit_icon(side, side->map, sx, sy,
			       side->curunit->type, side->fgcolor);
	    } else {
		draw_unit_icon(side, side->map, sx, sy,
			       side->curunit->type, side->diffcolor);
	    }
	}
	/* Flash something to draw the eye a long ways */
	if (humanside(side)) {
	    if (distance(side->curx, side->cury, side->lastx, side->lasty) > 3)
		flash_position(side, sx, sy);
	    draw_cursor_icon(side, sx, sy);
	    side->lastx = side->curx;  side->lasty = side->cury;
	}
    }
}

/* Get rid of cursor by redrawing the hex. */

erase_cursor(side)
Side *side;
{
    if (side->lasty > 0) draw_hex(side, side->lastx, side->lasty, TRUE);
}

/* Draw a splat visible to both sides at a given location.  Several splats */
/* available, depending on the seriousness of the hit.  Make an extra-flashy */
/* display when The Bomb goes off.  Because of the time delays involved, we */
/* have to update both sides' displays more or less simultaneously. Would be */
/* better to exhibit to all sides maybe, but I'm not going to bother! */

draw_blast(unit, es, hit)
Unit *unit;
Side *es;
int hit;
{
    char ch;
    int ux = unit->x, uy = unit->y, sx, sy, i;
    Side *us = unit->side;

    if (hit >= period.nukehit) {
	if (active_display(us)) invert_whole_map(us);
	if (active_display(es)) invert_whole_map(es);
	/* may need a time delay if X is too speedy */
	if (active_display(us)) invert_whole_map(us);
	if (active_display(es)) invert_whole_map(es);
	for (i = 0; i < 4; ++i) {
	    if (active_display(us)) draw_mushroom(us, ux, uy, i);
	    if (active_display(es)) draw_mushroom(es, ux, uy, i);
	    if (i != 2 && (active_display(us) || active_display(es))) sleep(1);
	}
	if (active_display(us) || active_display(es)) sleep(1);
    } else {
	ch = ((hit >= unit->hp) ? 'd' : ((hit > 0) ? 'c' : 'b'));
	if (active_display(us)) {
	    xform(us, unwrap(us, ux), uy, &sx, &sy);
	    draw_blast_icon(us, us->map, sx, sy, ch, us->enemycolor);
	    flush_output(us);
	}
	if (active_display(es)) {
	    xform(es, unwrap(es, ux), uy, &sx, &sy);
	    draw_blast_icon(es, es->map, sx, sy, ch, es->owncolor);
	    flush_output(es);
	}
    }
}

/* Draw all the units in a column.  They should be spaced so they don't */
/* overlap or get misaligned with text, and can be inverted if desired. */

draw_unit_list(side, hilite)
Side *side;
bool hilite[];
{
    int u, pos = 0, spacing = max(side->hh, side->fh);
    
    if (active_display(side)) {
	for_all_unit_types(u) {
	    draw_hex_icon(side, side->state, side->margin, pos,
			  (hilite[u] ? side->fgcolor : side->bgcolor), OHEX);
	    draw_unit_icon(side, side->state, side->margin, pos, u,
			   (hilite[u] ? side->bgcolor : side->fgcolor));
	    pos += spacing;
	}
    }
}