1 //
2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
4 //
5 // This program is free software; you can redistribute it and/or modify it
6 // under the terms of the GNU General Public License as published by the Free
7 // Software Foundation; either version 2 of the License, or (at your option)
8 // any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but WITHOUT
11 // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13 // more details.
14 //
15 // You should have received a copy of the GNU General Public License along with
16 // this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 // Suite 330, Boston, MA 02111-1307 USA
18 //
19 // Gordon McNutt
20 // gmcnutt@users.sourceforge.net
21 //
22 #include "map.h"
23 #include "sky.h"  // For time/date functions
24 #include "screen.h"
25 #include "place.h"
26 #include "player.h"
27 #include "sprite.h"
28 #include "cursor.h"
29 #include "terrain.h"
30 #include "Missile.h"
31 #include "object.h"
32 #include "vmask.h"
33 #include "session.h"
34 #include "sprite.h"
35 #include "nazghul.h"  // for DeveloperMode
36 
37 #include <SDL.h>
38 #include <math.h>
39 
40 #define PROFILE_REPAINT 0
41 #define PROFILE_ANIMATE 0
42 
43 #define LMAP_W     (VMASK_W)
44 #define LMAP_H     (VMASK_H)
45 
46 #define MVIEW_SZ   (sizeof(struct mview))
47 #define LMAP_SZ    (LMAP_W * LMAP_H)
48 #define MAX_LIGHTS LMAP_SZ
49 #define PEER_ZOOM  2
50 
51 #define LIT        255
52 #define UNLIT      0
53 
54 #define mview_x(mview)        ((mview)->vrect.x)
55 #define mview_y(mview)        ((mview)->vrect.y)
56 #define mview_w(mview)        ((mview)->vrect.w)
57 #define mview_h(mview)        ((mview)->vrect.h)
58 #define mview_center_x(mview) (mview_x(mview) + mview_w(mview) / 2)
59 #define mview_center_y(mview) (mview_y(mview) + mview_h(mview) / 2)
60 
61 /**
62  *  Convert map coords to screen coords
63  */
64 #define MX_TO_SX(x) \
65     (Map.srect.x+((x)-(Map.aview->vrect.x+Map.aview->subrect.x))*TILE_W)
66 #define MY_TO_SY(y) \
67     (Map.srect.y+((y)-(Map.aview->vrect.y+Map.aview->subrect.y))*TILE_H)
68 
69 struct light_source {
70 	int x, y, light;
71 };
72 
73 struct mview {
74 	struct list list;	/* used internally by map lib */
75 	SDL_Rect vrect;		/* map coords of vrect */
76         SDL_Rect subrect;       /* offset into visible subrect of vrect */
77 	//char *vmask;		/* visibility mask */
78 	int rad;		/* light radius */
79 	int zoom;               /* zoom level */
80 	int dirty:1;		/* needs repaint */
81         int blackout:1;         /* erase only on repaint */
82 };
83 
84 static struct map {
85 	SDL_Rect srect;		/* screen coords of viewer */
86 	SDL_Rect latencyRect;	/* screen coords of latency time */
87 	SDL_Rect turnaroundRect; /* screen coords of turnaroud time */
88 	SDL_Rect locRect;	/* screen coords of locater */
89 	SDL_Rect clkRect;	/* screen coords of clock */
90 	struct place *place;	/* subject being viewed */
91 	struct mview *aview;	/* active view */
92 	struct list views;	/* list of all views */
93 	struct mview *cam_view;
94         class Object *subject;
95 	int cam_x, cam_y, cam_max_x, cam_max_y, cam_min_x, cam_min_y;
96 	bool peering;
97 	char *vmask;	/* final mask used to render */
98         SDL_Surface *tile_scratch_surf;
99         Uint32 last_repaint;
100         class Object *selected; /* selected object -- don't shade the tile it's
101                                  * on */
102 
103         /* FIXME: why is this dynamically allocated when we're using
104          * MAX_LIGHTS? */
105         struct light_source *lights;
106         unsigned char *lmap;
107         unsigned char *tmp_lmap;
108         char is_image_mode : 1;
109 } Map;
110 
111 /**
112  * The callback function prototype for rendering a tile.
113  */
114 typedef void (*map_tile_render_t)(struct place *place, int map_x, int map_y,
115                                   int scr_x, int scr_y, int in_los);
116 
117 
myRmView(struct mview * view,void * data)118 static void myRmView(struct mview *view, void *data)
119 {
120 	list_remove(&view->list);
121 }
122 
mapMergeRects(SDL_Rect * src_rect,unsigned char * src,SDL_Rect * dst_rect,unsigned char * dst)123 static void mapMergeRects(SDL_Rect *src_rect, unsigned char *src,
124                           SDL_Rect *dst_rect, unsigned char *dst)
125 {
126 	int r_src, r_src_start, c_src, c_src_start, i_src, r_end, c_end;
127 	int r_dst, r_dst_start, c_dst, c_dst_start, i_dst;
128 	int tmp;
129 
130 	// skip identical merges (yes, it happens)
131 	if (src == dst)
132 		return;
133 
134 	if (src_rect->x < dst_rect->x) {
135 		// Source leftmost
136 		tmp = src_rect->x + src_rect->w - dst_rect->x;
137 		if (tmp < 0)
138 			return;
139 		c_src_start = dst_rect->x - src_rect->x;
140 		c_end = c_src_start + tmp;
141 		c_dst_start = 0;
142 	} else {
143 		// Destination leftmost
144 		tmp = dst_rect->x + dst_rect->w - src_rect->x;
145 		if (tmp < 0)
146 			return;
147 		c_src_start = 0;
148 		c_end = tmp;
149 		c_dst_start = src_rect->x - dst_rect->x;
150 	}
151 
152 	if (src_rect->y < dst_rect->y) {
153 		// Source topmost
154 		tmp = src_rect->y + src_rect->h - dst_rect->y;
155 		if (tmp < 0)
156 			return;
157 		r_src_start = dst_rect->y - src_rect->y;
158 		r_end = r_src_start + tmp;
159 		r_dst_start = 0;
160 	} else {
161 		// Destination topmost
162 		tmp = dst_rect->y + dst_rect->h - src_rect->y;
163 		if (tmp < 0)
164 			return;
165 		r_src_start = 0;
166 		r_end = tmp;
167 		r_dst_start = src_rect->y - dst_rect->y;
168 	}
169 
170 	for (r_src = r_src_start, r_dst = r_dst_start; r_src < r_end;
171 	     r_src++, r_dst++) {
172 		for (c_src = c_src_start, c_dst = c_dst_start; c_src < c_end;
173 		     c_src++, c_dst++) {
174                         int val;
175 			i_src = r_src * src_rect->w + c_src;
176 			i_dst = r_dst * dst_rect->w + c_dst;
177                         val = dst[i_dst] + src[i_src];
178 			dst[i_dst] = (unsigned char)min(val, 255);
179 		}
180 	}
181 }
182 
mapMergeView(struct mview * view,void * data)183 static void mapMergeView(struct mview *view, void *data)
184 {
185 	int r_src, r_src_start, c_src, c_src_start, i_src, r_end, c_end;
186 	int r_dst, r_dst_start, c_dst, c_dst_start, i_dst;
187 	int tmp;
188         char *vmask;
189 
190 	/* Skip this view if it is the active view */
191 	if (view == Map.aview)
192 		return;
193 
194         // ---------------------------------------------------------------------
195         // Find the indices to merge from depending on the relationship between
196         // the map view rectangle and the mview being merged.
197         // ---------------------------------------------------------------------
198 
199 	if (view->vrect.x < Map.aview->vrect.x) {
200 		/* This view leftmost (A) */
201 		tmp = view->vrect.x + view->vrect.w - Map.aview->vrect.x;
202 		if (tmp < 0)
203 			return;
204 		c_src_start = Map.aview->vrect.x - view->vrect.x;
205 		c_end = c_src_start + tmp;
206 		c_dst_start = 0;
207 	} else {
208 		/* Active view leftmost (A) */
209 		tmp = Map.aview->vrect.x + Map.aview->vrect.w - view->vrect.x;
210 		if (tmp < 0)
211 			return;
212 		c_src_start = 0;
213 		c_end = tmp;
214 		c_dst_start = view->vrect.x - Map.aview->vrect.x;
215 	}
216 
217 	if (view->vrect.y < Map.aview->vrect.y) {
218 		/* This view topmost (A) */
219 		tmp = view->vrect.y + view->vrect.h - Map.aview->vrect.y;
220 		if (tmp < 0)
221 			return;
222 		r_src_start = Map.aview->vrect.y - view->vrect.y;
223 		r_end = r_src_start + tmp;
224 		r_dst_start = 0;
225 	} else {
226 		/* Active view topmost (A) */
227 		tmp = Map.aview->vrect.y + Map.aview->vrect.h - view->vrect.y;
228 		if (tmp < 0)
229 			return;
230 		r_src_start = 0;
231 		r_end = tmp;
232 		r_dst_start = view->vrect.y - Map.aview->vrect.y;
233 	}
234 
235         // ---------------------------------------------------------------------
236         // From the vmask cache, fetch the vmask corresponding to the tile in
237         // the center of the view from the vmask cache. (This will automatically
238         // create the vmask if it doesn't already exist).
239         // ---------------------------------------------------------------------
240 
241         vmask = vmask_get(Map.place, mview_center_x(view), mview_center_y(view));
242         assert(vmask);
243         if (NULL == vmask)
244                 return;
245 
246         // ---------------------------------------------------------------------
247         // Copy the contents of the view's vmask to the master vmask.
248         // ---------------------------------------------------------------------
249 
250 	for (r_src = r_src_start, r_dst = r_dst_start; r_src < r_end;
251 	     r_src++, r_dst++) {
252 		for (c_src = c_src_start, c_dst = c_dst_start; c_src < c_end;
253 		     c_src++, c_dst++) {
254 			i_src = r_src * VMASK_W + c_src;
255 			i_dst = r_dst * VMASK_W + c_dst;
256 			Map.vmask[i_dst] |= vmask[i_src];
257 		}
258 	}
259 
260 }
261 
myMarkAsDirty(struct mview * view,void * data)262 static void myMarkAsDirty(struct mview *view, void *data)
263 {
264 	view->dirty = 1;
265 }
266 
mySetViewLightRadius(struct mview * view,void * data)267 static void mySetViewLightRadius(struct mview *view, void *data)
268 {
269 	int rad = *((int*)data);
270 	view->rad = rad;
271 }
272 
mapCalcMaxLightRadius(int light)273 static int mapCalcMaxLightRadius(int light)
274 {
275         // until something faster becomes necessary
276         return (int)sqrt((double)light);
277 }
278 
279 #if 0
280 // debug
281 static void mapDumpRect(char *name, SDL_Rect *rect, unsigned char *data)
282 {
283         int x, y, i;
284 
285         printf("Rect %s (%d %d %d %d):\n", name, rect->x, rect->y, rect->w,
286                rect->h);
287         i = 0;
288         for (y = 0; y < rect->h; y++) {
289                 for (x = 0; x < rect->w; x++, i++) {
290                         printf(" %03d", data[i]);
291                 }
292                 printf("\n");
293         }
294         printf("\n");
295 }
296 #endif
297 
298 /**
299  * Given a light source, add its contribution to the light map (Map.lmap).
300  *
301  * @param light is the light source to add
302  * @param main_view is the view containing the light source
303  */
mapMergeLightSource(struct light_source * light,struct mview * main_view)304 static void mapMergeLightSource(struct light_source *light, struct mview *main_view)
305 {
306         int radius;
307         int vmask_i;
308         struct mview tmp_view;
309         int x;
310         int y;
311         int map_x;
312         int map_y;
313         int D;
314         char *vmask;
315 
316         // Initialize the temporary view to be centered on the light
317         // source. (Note: ignore the subrect, it shouldn't matter)
318         //
319         // REVISIT: not sure I'm calculating vrect.x right: VMASK_W is odd
320         memset(&tmp_view,  0, sizeof(tmp_view));
321         tmp_view.vrect.x = place_wrap_x(Map.place, light->x - (VMASK_W / 2));
322         tmp_view.vrect.y = place_wrap_y(Map.place, light->y - (VMASK_H / 2));
323         tmp_view.vrect.w = VMASK_W;
324         tmp_view.vrect.h = VMASK_H;
325         tmp_view.zoom    = 1;
326 
327         radius = min(mapCalcMaxLightRadius(light->light), VMASK_W / 2);
328 
329         // Fetch the vmask from the cache.
330         vmask = vmask_get(Map.place, light->x, light->y);
331 
332         // For each visible tile in the vmask, calculate how much light is
333         // hitting that tile from the light source. The loop optimizes by only
334         // checking those tiles that are within the radius of the light source.
335         // This optimization makes no difference on my fast box, haven't tested
336         // it yet on my slow one.
337         int min_y = 0;
338         int max_y = VMASK_H;
339         int min_x = 0;
340         int max_x = VMASK_W;
341 
342         //dbg("lightmap %d:%d:%s\n", light->x, light->y, Map.place->name);
343 
344         for (y = min_y; y < max_y; y++) {
345 
346                 map_y = place_wrap_y(Map.place, tmp_view.vrect.y + y);
347                 vmask_i = y * VMASK_W + min_x;
348 
349                 for (x = min_x; x < max_x; x++, vmask_i++) {
350 
351                         // skip non-visible tiles
352                         if (vmask[vmask_i] == 0) {
353                                 Map.tmp_lmap[vmask_i] = 0;
354                                 continue;
355                         }
356 
357                         map_x = place_wrap_x(Map.place, tmp_view.vrect.x + x);
358 
359                         D = place_flying_distance(Map.place, light->x,
360                                                   light->y, map_x, map_y);
361                         D = D * D + 1;
362                         Map.tmp_lmap[vmask_i] = min(light->light / D, 255);
363                 }
364         }
365 
366         // Merge this source's lightmap (contained in the vmask we just built)
367         // with the main lightmap.
368         //
369         // Note: try to optimize this by merging only the portion of the vmask
370         // which is within the light radius. In fact, why don't I just limit
371         // the vrect to the radius? Would that work?
372         mapMergeRects(&tmp_view.vrect, Map.tmp_lmap, &main_view->vrect,
373                       Map.lmap);
374 
375 }
376 
377 /**
378  * This clears and rebuilds Map.lmap, which is a grid of values indicating how
379  * much light is hitting each tile. The results are used in mapShadeScene to
380  * darken the scene.
381  *
382  * @param view specifies which part of the map to use
383  */
mapBuildLightMap(struct mview * view)384 static void mapBuildLightMap(struct mview *view)
385 {
386         int x;
387         int y;
388         int lt_i;
389         int map_x;
390         int map_y;
391         int ambient_light;
392 
393 
394         /* Initialize the main lightmap to ambient light levels. */
395         ambient_light = sky_get_ambient_light(&Session->sky);
396         memset(Map.lmap,
397                (Map.place->underground ? UNLIT : ambient_light),
398                LMAP_SZ);
399 
400         /* Optimization: if we're already getting max light everywhere from the
401          * sun then skip further processing. Building a lightmap usually takes
402          * about 1/3 of the time devoted to rendering. */
403         if (! Map.place->underground
404             && ambient_light == MAX_AMBIENT_LIGHT) {
405                 return;
406         }
407 
408         /* Build the list of light sources visible in the current map viewer
409          * window. This actually searches outside of the current view to the
410          * entire mview rectangle, so light sources that are just out-of-view
411          * may cast light into the view. */
412         lt_i = 0;
413 	for (y = 0; y < LMAP_H; y++) {
414 		map_y = place_wrap_y(Map.place, view->vrect.y + y);
415 		for (x = 0; x < LMAP_W; x++) {
416 			int light;
417 
418 			map_x = place_wrap_x(Map.place, view->vrect.x + x);
419 			light = place_get_light(Map.place, map_x, map_y);
420 			if (!light)
421 				continue;
422 
423 			Map.lights[lt_i].x = map_x;
424 			Map.lights[lt_i].y = map_y;
425 			Map.lights[lt_i].light = light;
426 			lt_i++;
427 		}
428 	}
429 
430 	/* Skip further processing if there are no light sources */
431         if (!lt_i) {
432                 return;
433         }
434 
435 
436         /* For each light source build a lightmap centered on that source and
437          * merge it into the main lightmap. */
438         while (lt_i--) {
439                 mapMergeLightSource(&Map.lights[lt_i], view);
440         }
441 
442 }
443 
myShadeScene(SDL_Rect * subrect)444 static void myShadeScene(SDL_Rect *subrect)
445 {
446 	int x, y;
447 	SDL_Rect rect;
448         int lmap_i;
449 
450 	rect.x = Map.srect.x;
451 	rect.y = Map.srect.y;
452 	rect.w = TILE_W;
453 	rect.h = TILE_H;
454 
455         lmap_i = subrect->y * VMASK_W + subrect->x;
456         //lmap_i = 0;
457 
458 	// Iterate over the tiles in the map window and the corresponding
459 	// values in the lightmap simultaneously */
460 	for (y = 0; y < MAP_TILE_H; y++, rect.y += TILE_H,
461                      lmap_i += LMAP_W /*lmap_i += VMASK_W*/) {
462 		for (x = 0, rect.x = Map.srect.x;
463 		     x < MAP_TILE_W; x++, rect.x += TILE_W) {
464 
465 			/* Set the shading based on the lightmap value. The
466 			 * lightmap values must be converted to opacity values
467 			 * for a black square, so I reverse them by subtracting
468 			 * them from LIT. */
469 			screenShade(&rect, LIT - Map.lmap[lmap_i + x]);
470 		}
471 	}
472 }
473 
myAdjustCameraInBounds(void)474 static inline void myAdjustCameraInBounds(void)
475 {
476 	if (Map.place->wraps)
477 		return;
478 
479 	Map.cam_x = min(Map.cam_x, Map.cam_max_x);
480 	Map.cam_x = max(Map.cam_x, Map.cam_min_x);
481 	Map.cam_y = min(Map.cam_y, Map.cam_max_y);
482 	Map.cam_y = max(Map.cam_y, Map.cam_min_y);
483 }
484 
mapForEachView(void (* fx)(struct mview *,void *),void * data)485 void mapForEachView(void (*fx) (struct mview *, void *), void *data)
486 {
487 	struct list *list;
488 	list = Map.views.next;
489 	while (list != &Map.views) {
490 		struct list *tmp;
491 		struct mview *view;
492 		view = outcast(list, struct mview, list);
493 		tmp = list->next;
494 		fx(view, data);
495 		list = tmp;
496 	}
497 }
498 
mapSetLosStyle(const char * los)499 void mapSetLosStyle(const char *los)
500 {
501         if (LosEngine) {
502                 los_destroy(LosEngine);
503         }
504 	LosEngine = los_create(los, VMASK_W, VMASK_H, -1);
505         assert(LosEngine);
506 }
507 
mapExit(void)508 static void mapExit(void)
509 {
510         if (Map.lights) {
511                 free(Map.lights);
512                 Map.lights = 0;
513         }
514 
515         if (Map.lmap) {
516                 free(Map.lmap);
517                 Map.lmap = 0;
518         }
519 
520         if (Map.tmp_lmap) {
521                 free(Map.tmp_lmap);
522                 Map.tmp_lmap = 0;
523         }
524 
525         if (Map.cam_view) {
526                 mapDestroyView(Map.cam_view);
527                 Map.cam_view = 0;
528         }
529 
530         if (Map.vmask) {
531                 free(Map.vmask);
532                 Map.vmask = 0;
533         }
534 }
535 
mapInit(void)536 int mapInit(void)
537 {
538 
539 	memset(&Map, 0, sizeof(Map));
540 
541         Map.lights = (struct light_source*)calloc(MAX_LIGHTS,
542                                                   sizeof(Map.lights[0]));
543         if (!Map.lights)
544                 goto abort;
545 
546         /* The lightmap only needs to be as big as the map viewer
547            window. Making it larger does allow for lights outside the field of
548            view to be processed, but this makes dungeons appear too bright - I
549            like them dark and gloomy. */
550         Map.lmap = (unsigned char*)calloc(LMAP_SZ, sizeof(Map.lmap[0]));
551         if (!Map.lmap)
552                 goto abort;
553 
554         /* This one is used during mapMergeLightSource */
555         Map.tmp_lmap = (unsigned char*)calloc(LMAP_SZ,
556                                               sizeof(Map.tmp_lmap[0]));
557         if (!Map.tmp_lmap)
558                 goto abort;
559 
560 	if (!(Map.cam_view = mapCreateView()))
561                 goto abort;
562 
563         Map.vmask = (char*)calloc(VMASK_SZ, sizeof(Map.vmask[0]));
564         if (!Map.vmask)
565                 goto abort;
566 
567 	list_init(&Map.views);
568 
569 	Map.srect.x   = MAP_X;
570 	Map.srect.y   = MAP_Y;
571 	Map.srect.w   = MAP_W;
572 	Map.srect.h   = MAP_H;
573 
574 	Map.latencyRect.x = MAP_X;
575 	Map.latencyRect.y = MAP_Y;
576 	Map.latencyRect.w = ASCII_W * 10;
577 	Map.latencyRect.h = ASCII_H;
578 
579 	Map.turnaroundRect.x = MAP_X;
580 	Map.turnaroundRect.y = MAP_Y + ASCII_H;
581 	Map.turnaroundRect.w = ASCII_W * 10;
582 	Map.turnaroundRect.h = ASCII_H;
583 
584 	Map.locRect.x = MAP_X;
585 	Map.locRect.y = MAP_Y + MAP_H - ASCII_H;
586 	Map.locRect.w = ASCII_W * 9;
587 	Map.locRect.h = ASCII_H;
588 
589 	Map.clkRect.w = ASCII_W * 7;
590 	Map.clkRect.h = ASCII_H;
591 	Map.clkRect.x = MAP_X + MAP_W - Map.clkRect.w;
592 	Map.clkRect.y = MAP_Y;
593 
594 	Map.peering   = false;
595         LosEngine     = NULL;
596 
597         Map.tile_scratch_surf = screenCreateSurface(TILE_W, TILE_H);
598         assert(Map.tile_scratch_surf);
599 
600 	return 0;
601 
602 abort:
603         mapExit();
604         return -1;
605 }
606 
mapFlash(int mdelay)607 void mapFlash(int mdelay)
608 {
609 	screenFlash(&Map.srect, mdelay, White);
610 }
611 
mapSetPlace(struct place * place)612 void mapSetPlace(struct place *place)
613 {
614 	Map.place = place;
615 
616 	if (place->wraps)
617 		return;
618 
619 	if (place_w(place) > MAP_TILE_W) {
620 		Map.cam_max_x = place_w(place) - (MAP_TILE_W - 1) / 2 - 1;
621 		Map.cam_min_x = MAP_TILE_W / 2;
622 	} else {
623 		Map.cam_min_x = Map.cam_max_x = (place_w(place) + 1)/ 2 - 1;
624 	}
625 
626 	if (place_h(place) > MAP_TILE_W) {
627 		Map.cam_max_y = place_h(place) - (MAP_TILE_H - 1) / 2 - 1;
628 		Map.cam_min_y = MAP_TILE_H / 2;
629 	} else {
630 		Map.cam_min_y = Map.cam_max_y = (place_h(place) + 1) / 2 - 1;
631 	}
632 }
633 
mapCreateView(void)634 struct mview *mapCreateView(void)
635 {
636 	struct mview *v;
637 
638 	/* Allocate a new view */
639 	if (!(v = (struct mview *) malloc(MVIEW_SZ)))
640 		return 0;
641 
642 	/* Initialize the new view */
643 	memset(v, 0, MVIEW_SZ);
644 	list_init(&v->list);
645 	v->vrect.w   = VMASK_W;
646 	v->vrect.h   = VMASK_H;
647         v->zoom      = 1;
648         v->subrect.w = MAP_TILE_W * v->zoom;
649         v->subrect.h = MAP_TILE_H * v->zoom;
650         v->subrect.x = (v->vrect.w - v->subrect.w) / 2;
651         v->subrect.y = (v->vrect.h - v->subrect.h) / 2;
652 
653         //dbg("mapCreateView: return %08lx\n", v);
654 
655 	return v;
656 
657 }
658 
mapDestroyView(struct mview * view)659 void mapDestroyView(struct mview *view)
660 {
661         //dbg("mapDestroyView(%08lx)\n", view);
662 	free(view);
663 }
664 
mapAddView(struct mview * view)665 void mapAddView(struct mview *view)
666 {
667         //dbg("mapAddView(%08lx)\n", view);
668 	list_add(&Map.views, &view->list);
669 }
670 
mapRmView(struct mview * view)671 void mapRmView(struct mview *view)
672 {
673         //dbg("mapRmView(%08lx)\n", view);
674 	if (view == ALL_VIEWS)
675 		mapForEachView(myRmView, 0);
676 	else
677 		myRmView(view, 0);
678 }
679 
mapCenterView(struct mview * view,int x,int y)680 void mapCenterView(struct mview *view, int x, int y)
681 {
682         x -= view->vrect.w / 2; // back up to corner of vrect
683         y -= view->vrect.h / 2; // back up to corner of vrect
684         view->vrect.x = place_wrap_x(Map.place, x);
685         view->vrect.y = place_wrap_y(Map.place, y);
686 }
687 
mapRepaintClock(void)688 void mapRepaintClock(void)
689 {
690   char * date_time_str = time_HHMM_as_string();
691 
692   if (! DeveloperMode)
693           return;
694 
695   // Show the clock time:
696   screenErase(&Map.clkRect);
697   screenPrint(&Map.clkRect, 0, "%s", date_time_str);
698   screenUpdate(&Map.clkRect);
699 } // mapRepaintClock()
700 
701 /**
702  *  converts points so they appear correctly on wrapping maps
703  */
map_convert_point_to_vrect(int * x,int * y)704 static void map_convert_point_to_vrect(int *x, int *y)
705 {
706         SDL_Rect *vrect = &Map.aview->vrect;
707 
708         // If the view rect extends past the right side of the map, and x is
709         // left of the view rect, then convert x to be right of the view rect.
710         if ((vrect->x + vrect->w) > place_w(Map.place) &&
711             *x < vrect->x) {
712                 *x += place_w(Map.place);
713         }
714 
715         // Likewise if the view rect extends beyond the southern edge of the
716         // map, and y is less than the top of the view rect, then convert y to
717         // be south of the view rect.
718         if ((vrect->y + vrect->h) > place_h(Map.place) &&
719             *y < vrect->y) {
720                 *y += place_h(Map.place);
721         }
722 }
723 
map_paint_cursor(void)724 static void map_paint_cursor(void)
725 {
726         int x, y;
727 
728         if (!Session->crosshair->is_active())
729                 return;
730 
731         /* Convert to view rect offset */
732         x = Session->crosshair->getX();
733         y = Session->crosshair->getY();
734         map_convert_point_to_vrect(&x, &y);
735         if (!point_in_rect(x, y, &Map.aview->vrect)) {
736                 return;
737         }
738 
739         /* Paint it */
740         sprite_paint(Session->crosshair->getSprite(), 0,
741                     MX_TO_SX(x), MY_TO_SY(y));
742 
743 }
744 
745 /**
746  * Paint the terrain sprite for a tile.
747  *
748  * @param place The place to use as the tile source.
749  * @param map_x The tile coordinate in the place.
750  * @param map_y The tile coordinate in the place.
751  * @param scr_x The screen pixel coordinate to blit to.
752  * @param scr_y The screen pixel coordinate to blit to.
753  * @param in_los Zero iff the tile is not in player LOS.
754  */
map_paint_tile_terrain(struct place * place,int map_x,int map_y,int scr_x,int scr_y,int in_los)755 static void map_paint_tile_terrain(struct place *place, int map_x, int map_y,
756                                    int scr_x, int scr_y, int in_los)
757 {
758         if (in_los) {
759 
760                 /* This tile is in player LOS, so paint normally. */
761                 struct terrain *terrain = place_get_terrain(place, map_x, map_y);
762                 struct sprite *sprite = terrain->sprite;
763                 sprite_paint(sprite, 0, scr_x, scr_y);
764 
765         } else if (ShowAllTerrain || XrayVision) {
766 
767                 /* This tile is not in player LOS, but the command-line option
768                  * to show all terrain or the special XrayVision flag is in
769                  * effect, so paint the terrain but then shade it. */
770                 struct terrain *terrain = place_get_terrain(place,map_x,map_y);
771                 struct sprite *sprite = terrain->sprite;
772                 sprite_paint(sprite, 0, scr_x, scr_y);
773 
774                 SDL_Rect shade_rect;
775                 shade_rect.x = scr_x;
776                 shade_rect.y = scr_y;
777                 shade_rect.w = TILE_W;
778                 shade_rect.h = TILE_H;
779                 screenShade(&shade_rect, 128);
780         }
781 
782 }
783 
784 /**
785  * Paint the object sprites for a tile.
786  *
787  * @param place The place to use as the tile source.
788  * @param map_x The tile coordinate in the place.
789  * @param map_y The tile coordinate in the place.
790  * @param scr_x The screen pixel coordinate to blit to.
791  * @param scr_y The screen pixel coordinate to blit to.
792  * @param in_los Zero iff the tile is not in player LOS.
793  */
map_paint_tile_objects(struct place * place,int map_x,int map_y,int scr_x,int scr_y,int in_los)794 static void map_paint_tile_objects(struct place *place, int map_x, int map_y,
795                                    int scr_x, int scr_y, int in_los)
796 {
797         if (in_los) {
798 
799                 /* The tile is visible, so paint the objects normally. */
800                 place_paint_objects(place, map_x, map_y, scr_x, scr_y);
801 
802                 /* If the crosshair is active but this tile is not in range
803                  * then shade the tile. */
804                 if (Session->crosshair->is_active() &&
805                     Session->crosshair->isRangeShaded() &&
806                     ! Session->crosshair->inRange(map_x, map_y)) {
807                         SDL_Rect shade_rect;
808                         shade_rect.x = scr_x;
809                         shade_rect.y = scr_y;
810                         shade_rect.w = TILE_W;
811                         shade_rect.h = TILE_H;
812                         screenShade(&shade_rect, 128);
813                 }
814         }
815 }
816 
817 /**
818  * Loop over the map tiles shown in the map viewer, invoking a callback
819  * function for each tile.
820  *
821  * @param place The place viewed.
822  * @param region The part of the place covered by the visibility mask. Units
823  * are tiles.
824  * @param dest The screen rectangle of the viewer. Units are pixels.
825  * @param mask The visibility mask for the region. Each entry in the mask
826  * covers one tile.
827  * @param subrect The part of the place under the viewer (this is contained
828  * within the region). Units are tiles.
829  * @param tile_w Tile dimension in pixels.
830  * @param tile_h Tile dimension in pixels.
831  */
map_render_loop(struct place * place,SDL_Rect * region,SDL_Rect * dest,unsigned char * mask,SDL_Rect * subrect,int tile_h,int tile_w,map_tile_render_t tile_render)832 static void map_render_loop(struct place *place,
833                             SDL_Rect * region,
834                             SDL_Rect * dest,
835                             unsigned char *mask,
836                             SDL_Rect * subrect,
837                             int tile_h,
838                             int tile_w,
839                             map_tile_render_t tile_render)
840 {
841 	int row;
842 	int col;
843 	int map_y; /* in rows */
844 	int map_x; /* in cols */
845 	int scr_x; /* in pixels */
846 	int scr_y; /* in pixels */
847         int mask_i;
848 	bool use_mask;
849 
850 	if (place->wraps) {
851 		region->x = place_wrap_x(place, region->x);
852 		region->y = place_wrap_y(place, region->y);
853 	}
854 
855         /*
856            +-----------------------------------------------------------------+
857            | region/mask                                                     |
858            |                                                                 |
859            |                    +-------------------------+                  |
860            |                    | subrect                 |                  |
861            |                    |                         |                  |
862            |                    |                         |                  |
863            |                    |                         |                  |
864            |                    |                         |                  |
865            |                    |                         |                  |
866            |                    |                         |                  |
867            |                    |                         |                  |
868            |                    |                         |                  |
869            |                    |                         |                  |
870            |                    |                         |                  |
871            |                    |                         |                  |
872            |                    +-------------------------+                  |
873            |                                                                 |
874            |                                                                 |
875            +-----------------------------------------------------------------+
876         */
877 
878 	use_mask = (mask != NULL);
879 	map_y = region->y + subrect->y;
880         mask_i = (subrect->y * region->w) + subrect->x;
881 
882 	for (row = 0; row < subrect->h; row++, map_y++, mask_i += region->w) {
883 
884                 /* Test if the row is off-map */
885 		if (place->wraps) {
886 			map_y = place_wrap_y(place, map_y);
887 		} else if (map_y < 0) {
888 			continue;
889 		} else if (map_y >= place->terrain_map->h) {
890 			break;
891 		}
892 
893                 /* Set the screen pixel row */
894 		scr_y = row * tile_h + dest->y;
895 
896                 /* Set the initial map column for this row */
897 		map_x = region->x + subrect->x;
898 
899 		for (col = 0; col < subrect->w; col++, map_x++) {
900 
901                         int in_los;
902 
903                         /* Test if the column is off-map */
904 			if (place->wraps) {
905 				map_x = place_wrap_x(place, map_x);
906 			} else if (map_x < 0) {
907 				continue;
908 			} else if (map_x >= place->terrain_map->w) {
909 				break;
910 			}
911 
912                         /* Set the screen pixel column */
913 			scr_x = col * tile_w + dest->x;
914 
915                         /* Set the LOS flag. */
916                         in_los = (!use_mask || mask[mask_i + col]);
917 
918                         /* Invoke the callback function that does the rendering
919                          * for the tile. */
920                         tile_render(place, map_x, map_y, scr_x, scr_y, in_los);
921 		}
922 	}
923 
924 }
925 
mapPaintPlace(struct place * place,SDL_Rect * region,SDL_Rect * dest,unsigned char * mask,SDL_Rect * subrect,int tile_h,int tile_w)926 static void mapPaintPlace(struct place *place,
927                           SDL_Rect * region,   /* portion of place covered by
928                                                 * the vmask */
929                           SDL_Rect * dest,     /* screen rectangle */
930                           unsigned char *mask, /* visibility mask for entire
931                                                 * region */
932                           SDL_Rect * subrect,  /* sub-rectangle within region
933                                                 * that the map viewer sees */
934                           int tile_h,
935                           int tile_w)
936 {
937         /* In order to render giant characters properly over the terrain of
938          * neighboring tiles, rendering must be done in two passes. The first
939          * pass renders the terrain, the second the objects. */
940         map_render_loop(place, region, dest, mask,  subrect, tile_h, tile_w,
941                         map_paint_tile_terrain);
942         map_render_loop(place, region, dest, mask,  subrect, tile_h, tile_w,
943                         map_paint_tile_objects);
944 	place->dirty = 0;
945 }
946 
947 
mapRepaintCoordinates(void)948 static void mapRepaintCoordinates(void)
949 {
950         if (! DeveloperMode) {
951                 return;
952         }
953 
954         if (player_party->isOnMap()) {
955                 screenPrint(&Map.locRect, 0, "[%d,%d]", player_party->getX(),
956                             player_party->getY());
957                 return;
958         }
959 
960         if (NULL != Map.subject)
961                 screenPrint(&Map.locRect, 0, "[%d,%d]", Map.subject->getX(),
962                             Map.subject->getY());
963 }
964 
mapRepaintTurnaround(void)965 static void mapRepaintTurnaround(void)
966 {
967         extern int G_turnaround;
968 
969         if (! DeveloperMode)
970                 return;
971 
972 	screenPrint(&Map.turnaroundRect, 0, "TA: %d", G_turnaround);
973 }
974 
975 extern int G_latency_start;
mapRepaintLatency(void)976 static void mapRepaintLatency(void)
977 {
978         static int latency = 0;
979 
980         if (! DeveloperMode)
981                 return;
982 
983         latency = SDL_GetTicks() - G_latency_start;
984 
985         //printf("repaint: %d\n", latency);
986         screenPrint(&Map.latencyRect, 0, "LAT: %d", latency);
987         screenUpdate(&Map.latencyRect);
988 }
989 
990 /**
991  * This is the main paint routine.
992  *
993  * @param view defines which part of the map to show
994  * @param flags controls controls policies of whether and what to paint
995  */
mapRepaintView(struct mview * view,int flags)996 static void mapRepaintView(struct mview *view, int flags)
997 {
998 	int t1, t2, t3, t4, t5, t6, t7, t8;
999 
1000 	Map.aview = view;
1001 
1002 	if (flags & REPAINT_IF_DIRTY && !view->dirty)
1003 		return;
1004 
1005         if (flags & REPAINT_IF_OLD
1006             && (SDL_GetTicks() - Map.last_repaint) < (Uint32)TickMilliseconds
1007             && (Map.last_repaint < SDL_GetTicks()))
1008                 return;
1009 
1010         Map.last_repaint = SDL_GetTicks();
1011 	view->dirty = 0;
1012 
1013         G_latency_start = SDL_GetTicks();
1014 
1015 	t1 = SDL_GetTicks();
1016 
1017 	screenErase(&Map.srect);
1018 
1019 	t2 = SDL_GetTicks();
1020 
1021         if (Map.aview->blackout) {
1022                 // In blackout mode leave the screen erased
1023                 goto done_painting_place;
1024         }
1025 
1026 	if (Map.aview->zoom > 1) {
1027                 sprite_zoom_out(Map.aview->zoom);
1028 		screenZoomOut(Map.aview->zoom);
1029 		t5 = SDL_GetTicks();
1030 		mapPaintPlace(Map.place, &view->vrect, &Map.srect,
1031                               0/* vmask */, &view->subrect,
1032                               TILE_W / Map.aview->zoom,
1033                               TILE_H / Map.aview->zoom);
1034 		t6 = SDL_GetTicks();
1035 		screenZoomIn(Map.aview->zoom);
1036                 sprite_zoom_in(Map.aview->zoom);
1037 	} else if (flags & REPAINT_NO_LOS) {
1038                 t5 = SDL_GetTicks();
1039 		mapPaintPlace(Map.place, &view->vrect, &Map.srect, 0,
1040                               &view->subrect, TILE_W, TILE_H);
1041 		t6 = SDL_GetTicks();
1042 	} else {
1043 
1044                 // ------------------------------------------------------------
1045                 // Map.vmask serves as the "master" vmask. Start by zeroing it
1046                 // out so that by default nothing is in line-of-sight. Then
1047                 // iterate over all the active views (each player party member
1048                 // has an active view, spells may add others), and for each
1049                 // view merge it's vmask onto the master. The result is the
1050                 // line-of-sight for all party members is always visible to the
1051                 // player.
1052                 // ------------------------------------------------------------
1053 
1054                 memset(Map.vmask, 0, VMASK_SZ);
1055 		t3 = SDL_GetTicks();
1056 		mapForEachView(mapMergeView, 0);
1057                 //vmask_dump(Map.vmask);
1058 		t4 = SDL_GetTicks();
1059 		mapBuildLightMap(view);
1060 		t5 = SDL_GetTicks();
1061 		mapPaintPlace(Map.place, &view->vrect, &Map.srect,
1062                               (unsigned char *) Map.vmask, &view->subrect,
1063                               TILE_W, TILE_H);
1064 		t6 = SDL_GetTicks();
1065                 if (! XrayVision) {
1066                         myShadeScene(&view->subrect);
1067                 }
1068 		t7 = SDL_GetTicks();
1069                 map_paint_cursor();
1070 
1071                 // After shading, repaint the tile with the selected object so
1072                 // that it shows up brightly even in darkness.
1073                 if (Map.selected
1074                     && Map.selected->getPlace() == Map.place)
1075                         mapUpdateTile(Map.place,
1076                                       Map.selected->getX(),
1077                                       Map.selected->getY());
1078 	}
1079 
1080  done_painting_place:
1081 
1082         mapRepaintCoordinates();
1083 	mapRepaintClock();
1084         mapRepaintTurnaround();
1085 	screenUpdate(&Map.srect);
1086 
1087         // ---------------------------------------------------------------------
1088         // Repaint the latency AFTER the screenUpdate because we want that to
1089         // be part of the time measurement.
1090         // ---------------------------------------------------------------------
1091 
1092         mapRepaintLatency();
1093 
1094         t8 = SDL_GetTicks();
1095 
1096 	if (PROFILE_REPAINT) {
1097 	  printf("Total time=%d\n", t8 - t1);
1098 	  printf("    erase screen=%d\n", t2 - t1);
1099 	  printf("          memcpy=%d\n", t3 - t2);
1100 	  printf("    merge vmasks=%d\n", t4 - t3);
1101 	  printf("  build lightmap=%d\n", t5 - t4);
1102 	  printf("     paint place=%d\n", t6 - t5);
1103 	  printf("           shade=%d\n", t7 - t6);
1104 	  printf("   update screen=%d\n", t8 - t7);
1105 	}
1106 }
1107 
mapXToViewX(int x)1108 static int mapXToViewX(int x)
1109 {
1110         SDL_Rect *vrect = &Map.aview->vrect;
1111         int x2 = x - vrect->x;
1112         if (x2 < 0
1113             && place_is_wrapping(Map.place)
1114             && (vrect->x + vrect->w) > place_w(Map.place))
1115                 x2 += place_w(Map.place);
1116         return x2;
1117 }
1118 
mapYToViewY(int y)1119 static int mapYToViewY(int y)
1120 {
1121         SDL_Rect *vrect = &Map.aview->vrect;
1122         int y2 = y - vrect->y;
1123         if (y2 < 0
1124             && place_is_wrapping(Map.place)
1125             && (vrect->y + vrect->h) > place_h(Map.place))
1126                 y2 += place_h(Map.place);
1127         return y2;
1128 }
1129 
mapTileIsWithinViewport(int x,int y)1130 int mapTileIsWithinViewport(int x, int y)
1131 {
1132         SDL_Rect *vrect = &Map.aview->vrect;
1133         int vx = mapXToViewX(x);
1134         int vy = mapYToViewY(y);
1135         if (vx < 0
1136             || vx >= vrect->w
1137             || vy < 0
1138             || vy >= vrect->h)
1139                 return 0;
1140         return 1;
1141 }
1142 
mapTileLightLevel(int x,int y)1143 unsigned char mapTileLightLevel(int x, int y)
1144 {
1145         int vx = mapXToViewX(x);
1146         int vy = mapYToViewY(y);
1147         return Map.lmap[vy * LMAP_W + vx];
1148 }
1149 
mapTileIsVisible(int x,int y)1150 int mapTileIsVisible(int x, int y)
1151 {
1152         SDL_Rect *vrect = &Map.aview->vrect;
1153         int vx = mapXToViewX(x);
1154         int vy = mapYToViewY(y);
1155 
1156         // check if coords in vrect
1157         if (vx < 0
1158             || vx >= vrect->w
1159             || vy < 0
1160             || vy >= vrect->h)
1161                 return 0;
1162 
1163         // If zoomed out then don't bother checking the vmask.
1164         if (Map.aview->zoom > 1)
1165                 return 1;
1166 
1167         // Return if the tile is marked as visible
1168         return Map.vmask[vy * vrect->w + vx];
1169 
1170 }
mapMarkAsDirty(struct mview * view)1171 void mapMarkAsDirty(struct mview *view)
1172 {
1173 	if (view == ALL_VIEWS)
1174 		mapForEachView(myMarkAsDirty, 0);
1175 	else
1176 		myMarkAsDirty(view, 0);
1177 }
1178 
mapSetRadius(struct mview * view,int rad)1179 void mapSetRadius(struct mview *view, int rad)
1180 {
1181 	if (view == ALL_VIEWS)
1182 		mapForEachView(mySetViewLightRadius, (void *) &rad);
1183 	else
1184 		mySetViewLightRadius(view, (void *) &rad);
1185 }
1186 
mapGetRadius(struct mview * view)1187 int mapGetRadius(struct mview *view)
1188 {
1189 	return view->rad;
1190 }
1191 
mapGetMapOrigin(int * x,int * y)1192 void mapGetMapOrigin(int *x, int *y)
1193 {
1194 	assert(Map.aview);
1195 	*x = Map.aview->vrect.x + Map.aview->subrect.x;
1196 	*y = Map.aview->vrect.y + Map.aview->subrect.y;
1197 }
1198 
mapGetScreenOrigin(int * x,int * y)1199 void mapGetScreenOrigin(int *x, int *y)
1200 {
1201 	*x = Map.srect.x;
1202 	*y = Map.srect.y;
1203 }
1204 
mapGetTileDimensions(int * w,int * h)1205 void mapGetTileDimensions(int *w, int *h)
1206 {
1207         *w = TILE_W / Map.aview->zoom;
1208         *h = TILE_H / Map.aview->zoom;
1209 }
1210 
mapSetActiveView(struct mview * view)1211 void mapSetActiveView(struct mview *view)
1212 {
1213 	Map.aview = view;
1214         //dbg("mapSetActiveView: aview=[%d %d]\n", Map.aview->vrect.x, Map.aview->vrect.y);
1215 }
1216 
mapCenterCamera(int x,int y)1217 void mapCenterCamera(int x, int y)
1218 {
1219 	Map.cam_x = x;
1220 	Map.cam_y = y;
1221 
1222 	Map.cam_x = place_wrap_x(Map.place, Map.cam_x);
1223 	Map.cam_y = place_wrap_y(Map.place, Map.cam_y);
1224 
1225 	myAdjustCameraInBounds();
1226 	mapCenterView(Map.cam_view, Map.cam_x, Map.cam_y);
1227 }
1228 
mapMoveCamera(int dx,int dy)1229 void mapMoveCamera(int dx, int dy)
1230 {
1231 	mapCenterCamera(Map.cam_x + dx, Map.cam_y + dy);
1232 }
1233 
mapUpdate(int flags)1234 void mapUpdate(int flags)
1235 {
1236         if (Map.is_image_mode)
1237                 return;
1238 
1239 	mapRepaintView(Map.cam_view, flags);
1240 }
1241 
mapSetDirty(void)1242 void mapSetDirty(void)
1243 {
1244         if (Map.cam_view != NULL)
1245                 Map.cam_view->dirty = 1;
1246 }
1247 
mapJitter(bool val)1248 void mapJitter(bool val)
1249 {
1250 	Map.srect.x = MAP_X;
1251 	Map.srect.y = MAP_Y;
1252 	Map.srect.w = MAP_W;
1253 	Map.srect.h = MAP_H;
1254 
1255 	if (val) {
1256 		Map.srect.x += (rand() % 5) - 2;
1257 		Map.srect.y += (rand() % 5) - 2;
1258 	}
1259 }
1260 
mapPeer(bool val)1261 void mapPeer(bool val)
1262 {
1263 	int dx, dy;
1264 	// Peering will apply to the camera view. Set the scale factor and
1265 	// adjust the pertinent rectangle dimensions.
1266         Map.peering = val;
1267 	if (val) {
1268 		Map.cam_view->zoom = PEER_ZOOM;
1269 		dx = (Map.cam_view->vrect.w / 2) * (PEER_ZOOM - 1);
1270 		dy = (Map.cam_view->vrect.h / 2) * (PEER_ZOOM - 1);
1271 		Map.cam_view->vrect.x -= dx;
1272 		Map.cam_view->vrect.y -= dy;
1273 		Map.cam_view->vrect.w *= PEER_ZOOM;
1274 		Map.cam_view->vrect.h *= PEER_ZOOM;
1275 	} else {
1276 		Map.cam_view->zoom = 1;
1277 		Map.cam_view->vrect.w /= PEER_ZOOM;
1278 		Map.cam_view->vrect.h /= PEER_ZOOM;
1279 		dx = (Map.cam_view->vrect.w / 2) * (PEER_ZOOM - 1);
1280 		dy = (Map.cam_view->vrect.h / 2) * (PEER_ZOOM - 1);
1281 		Map.cam_view->vrect.x += dx;
1282 		Map.cam_view->vrect.y += dy;
1283 	}
1284 
1285         Map.cam_view->subrect.w = MAP_TILE_W * Map.cam_view->zoom;
1286         Map.cam_view->subrect.h = MAP_TILE_H * Map.cam_view->zoom;
1287         Map.cam_view->subrect.x = (Map.cam_view->vrect.w -
1288                                    Map.cam_view->subrect.w) / 2;
1289         Map.cam_view->subrect.y = (Map.cam_view->vrect.h -
1290                                    Map.cam_view->subrect.h) / 2;
1291 
1292         mapCenterCamera(Map.cam_x, Map.cam_y); // recenter
1293 }
1294 
mapTogglePeering(void)1295 void mapTogglePeering(void)
1296 {
1297         mapPeer(!Map.peering);
1298         mapCenterCamera(Map.cam_x, Map.cam_y); // recenter
1299         mapUpdate(0);
1300 }
1301 
mapGetCameraFocus(struct place ** place,int * x,int * y)1302 void mapGetCameraFocus(struct place **place, int *x, int *y)
1303 {
1304         *place = Map.place;
1305         *x = Map.cam_x;
1306         *y = Map.cam_y;
1307 }
1308 
mapIsInCameraView(struct place * place,int x,int y)1309 int mapIsInCameraView(struct place *place, int x, int y)
1310 {
1311         int min;
1312         int max;
1313 
1314         if (place != Map.place)
1315                 return 0;
1316 
1317         min = Map.cam_view->subrect.x + Map.cam_view->vrect.x;
1318         max = min + Map.cam_view->subrect.w;
1319 
1320         if (x < min || x >= max)
1321                 return 0;
1322 
1323         min = Map.cam_view->subrect.y + Map.cam_view->vrect.y;
1324         max = min + Map.cam_view->subrect.h;
1325 
1326         if (y < min || y >= max)
1327                 return 0;
1328 
1329         return 1;
1330 }
1331 
mapBlackout(int val)1332 void mapBlackout(int val)
1333 {
1334         Map.cam_view->blackout = !!val;
1335 }
1336 
mapPaintProjectile(SDL_Rect * rect,struct sprite * sprite,SDL_Surface * surf,int dur,int currentframe,bool beam)1337 static void mapPaintProjectile(SDL_Rect *rect, struct sprite *sprite,
1338                                SDL_Surface *surf, int dur, int currentframe, bool beam)
1339 {
1340 	// The rect coordinates are in SCREEN coordinates (not map) so I need
1341 	// to do some clipping here to make sure we don't paint off the map
1342 	// viewer.
1343 	if (rect->x < MAP_X || rect->y < MAP_Y ||
1344 	    ((rect->x + rect->w) > (MAP_X + MAP_W)) ||
1345 	    ((rect->y + rect->h) > (MAP_Y + MAP_H)))
1346 		return;
1347 
1348 	// Save the backdrop of the new location
1349 	if (!beam)
1350 		screenCopy(rect, NULL, surf);
1351 
1352 	// Paint the missile at the new location
1353         sprite_zoom_out(Map.aview->zoom);
1354         screenZoomOut(Map.aview->zoom);
1355 	sprite_paint_frame(sprite, currentframe, rect->x, rect->y);
1356         sprite_zoom_in(Map.aview->zoom);
1357         screenZoomIn(Map.aview->zoom);
1358 
1359 	screenUpdate(rect);
1360 
1361 	SDL_Delay(dur);
1362 
1363 	// Erase the missile by blitting the background
1364 	if (!beam)
1365 	{
1366 		screenBlit(surf, NULL, rect);
1367 		screenUpdate(rect);
1368 	}
1369 }
1370 
mapPaintDamage(int x,int y)1371 void mapPaintDamage(int x, int y)
1372 {
1373         int tile_w, tile_h;
1374         SDL_Rect rect;
1375 
1376         if (!mapTileIsVisible(x, y))
1377                 return;
1378 
1379         mapGetTileDimensions(&tile_w, &tile_h);
1380         rect.w = tile_w;
1381         rect.h = tile_h;
1382         rect.x = MX_TO_SX(x);
1383         rect.y = MY_TO_SY(y);
1384 
1385         /* Sometimes a damage flash doesn't make sense to the player unless the
1386          * map view is updated first. For example, a character that gets 2x the
1387          * normal action points per turn might move and then attack. The move
1388          * won't be shown until the map is updated. */
1389         mapUpdate(REPAINT_IF_DIRTY);
1390 
1391         mapPaintProjectile(&rect, Session->damage_sprite,
1392                            Map.tile_scratch_surf, 100, 0, false);
1393 }
1394 
1395 //paint damage, but with custom sprite
mapFlashSprite(int x,int y,struct sprite * sprite)1396 void mapFlashSprite(int x, int y, struct sprite *sprite)
1397 {
1398         int tile_w, tile_h;
1399         SDL_Rect rect;
1400 
1401         if (!mapTileIsVisible(x, y))
1402                 return;
1403 
1404         mapGetTileDimensions(&tile_w, &tile_h);
1405         rect.w = tile_w;
1406         rect.h = tile_h;
1407         rect.x = MX_TO_SX(x);
1408         rect.y = MY_TO_SY(y);
1409 
1410         /* Sometimes a damage flash doesn't make sense to the player unless the
1411          * map view is updated first. For example, a character that gets 2x the
1412          * normal action points per turn might move and then attack. The move
1413          * won't be shown until the map is updated. */
1414         mapUpdate(REPAINT_IF_DIRTY);
1415 
1416         mapPaintProjectile(&rect, sprite,
1417                            Map.tile_scratch_surf, 100, 0, false);
1418 }
1419 
mapAnimateProjectile(int Ax,int Ay,int * Bx,int * By,struct sprite * sprite,struct place * place,class Missile * missile,float range)1420 void mapAnimateProjectile(int Ax, int Ay, int *Bx, int *By,
1421                           struct sprite *sprite, struct place *place,
1422                           class Missile *missile, float range)
1423 {
1424 	//
1425 	// Derived from Kenny Hoff's Bresenhaum impl at
1426 	// http://www.cs.unc.edu/~hoff/projects/comp235/bresline/breslin1.txt
1427 	// (no license or copyright noted)
1428 	//
1429 	int framecount = sprite_num_frames(sprite);
1430 	int currentframe = 0;
1431 
1432 	int t1, t2;
1433 	SDL_Surface * surf;	// for saving/restoring the background
1434 
1435 	t1 = SDL_GetTicks();
1436 
1437 	// Get tile dimensions
1438 	int tile_w;
1439 	int tile_h;
1440 	mapGetTileDimensions(&tile_w, &tile_h);
1441 
1442 	// Half tile offset- missiles fly from and to the middle of a tile
1443 	int tile_w_half = tile_w/2;
1444 	int tile_h_half = tile_h/2;
1445 
1446 	MissileType *mistype = missile->getObjectType();
1447 	bool canEnter = missile->canEnter();
1448 
1449 	// Create a scratch surface for saving/restoring the background
1450 	surf = Map.tile_scratch_surf;
1451 	assert(surf);
1452 
1453 	// Get the map coordinates of the view origin (upper left corner)
1454 	int Ox, Oy;
1455 	mapGetMapOrigin(&Ox, &Oy);
1456 
1457 	// Get the screen coordinates of the map viewer origin
1458 	int Sx, Sy;
1459 	mapGetScreenOrigin(&Sx, &Sy);
1460 
1461 	// Copy the place coordinates of the origin of flight. I'll walk these
1462 	// along as the missile flies and check for obstructions.
1463 	int Px, Py, oPx, oPy, orx, ory;
1464 	Px = Ax;
1465 	Py = Ay;
1466 	orx = Ax;
1467 	ory = Ay;
1468 
1469 	// Convert to screen coordinates. (I need to keep the original
1470 	// B-coordinates for field effects at the bottom of this routine).
1471 	int sBx;
1472 	int sBy;
1473 
1474 	if (place_is_wrapping(place))
1475 	{
1476 		if (Ax > Ox)
1477 		      Ax = (Ax - Ox) * tile_w + Sx;
1478 		else
1479 		      Ax = (place_w(place) - Ox + Ax)  * tile_w + Sx;
1480 		if (Ay >= Oy)
1481 		      Ay = (Ay - Oy) * tile_h + Sy;
1482 		else
1483 		      Ay = (place_h(place) - Oy + Ay)  * tile_h + Sy;
1484 
1485 		if (*Bx >= Ox)
1486 		      sBx = (*Bx - Ox) * tile_w + Sx;
1487 		else
1488 		      sBx = (place_w(place) - Ox + *Bx) * tile_w + Sx;
1489 		if (*By >= Oy)
1490 		      sBy = (*By - Oy) * tile_h + Sy;
1491 		else
1492 		      sBy = (place_h(place) - Oy + *By)  * tile_h + Sy;
1493 	}
1494 	else
1495 	{
1496 		Ax = (Ax - Ox) * tile_w + Sx;
1497 		Ay = (Ay - Oy) * tile_h + Sy;
1498 		sBx = (*Bx - Ox) * tile_w + Sx;
1499 		sBy = (*By - Oy) * tile_h + Sy;
1500 	}
1501 
1502 	// Create the rect which bounds the missile's sprite (used to update
1503 	// that portion of the screen after blitting the sprite).
1504 	SDL_Rect rect;
1505 	rect.x = Ax;
1506 	rect.y = Ay;
1507 	rect.w = TILE_W;
1508 	rect.h = TILE_H;
1509 
1510 	// Get the distance components
1511 	int dX = sBx - rect.x;
1512 	int dY = sBy - rect.y;
1513 	int AdX = abs(dX);
1514 	int AdY = abs(dY);
1515 
1516 	// Select the sprite orientation based on direction of travel
1517 	if (sprite)
1518 	{
1519 		if ((sprite_facings_list(sprite) & 495) == 495) //nsew + diagonals
1520 		{
1521 			sprite_set_facing(sprite, vector_to_8facing(dX, dY));
1522 		}
1523 		else if ((sprite_facings_list(sprite) & 170) == 170) //nsew only
1524 		{
1525 			sprite_set_facing(sprite, vector_to_dir(dX, dY));
1526 		}
1527 	}
1528 
1529 	// Moving left?
1530 	int Xincr = (rect.x > sBx) ? -1 : 1;
1531 	// adjust for rounding errors
1532 	if (rect.x < sBx)
1533 	{
1534 		tile_w_half--;
1535 	}
1536 
1537 	// Moving down?
1538 	int Yincr = (rect.y > sBy) ? -1 : 1;
1539 	// adjust for rounding errors
1540 	if (rect.y < sBy)
1541 	{
1542 		tile_h_half--;
1543 	}
1544 
1545 	int dPr, dPru, P, i , Xsubincr, Ysubincr;
1546 	int oldx, oldy, tempx, tempy;
1547 
1548 	//number of steps between missile repaints
1549 	int paintloopsize = 20;
1550 
1551 	// Walk the x-axis?
1552 	if (AdX >= AdY)
1553 	{
1554 		dPr = AdY << 1;
1555 		dPru = dPr - (AdX << 1);
1556 		P = dPr - AdX;
1557 		Xsubincr = Xincr;
1558 		Ysubincr = 0;
1559 		if (range > 0.5) // floating point hence error margins
1560 		{
1561 			i = TILE_W * (place_w(place) + 2); // == "enough": its actually checked in the loop instead
1562 		}
1563 		else
1564 		{
1565 			i = AdX;
1566 		}
1567                 if (AdX==0 && AdY==0) {
1568                     // Fix for 2364311: avoid divide-by-zero
1569                     paintloopsize = 0;
1570                 } else {
1571                     paintloopsize = paintloopsize * (AdX*AdX)/((AdX*AdX)+(AdY*AdY));
1572                 }
1573 	}
1574 	else
1575 	{
1576 		dPr = AdX << 1;
1577 		dPru = dPr - (AdY << 1);
1578 		P = dPr - AdY;
1579 		Xsubincr = 0;
1580 		Ysubincr = Yincr;
1581 
1582 		if (range > 0.5) // floating point hence error margins
1583 		{
1584 			i = TILE_H * (place_h(place) + 2); // == "enough": its actually checked in the loop instead
1585 		}
1586 		else
1587 		{
1588 			i = AdY;
1589 		}
1590 		paintloopsize = paintloopsize * (AdY*AdY)/((AdX*AdX)+(AdY*AdY));
1591 	}
1592 
1593 	// firing past selected range, so work out the map edges if need be.
1594 	bool checkEdge = ((range >= 1) && !(place_is_wrapping(place)));
1595 
1596 	oldx = rect.x;
1597 	oldy = rect.y;
1598 
1599 	int paintloop = paintloopsize;
1600 
1601 	bool beam = missile->getObjectType()->isBeam();
1602 
1603 	// For each step
1604 	for (; i >= 0; i--)
1605 	{
1606 		oPx = Px;
1607 		oPy = Py;
1608 		Px = place_wrap_x(place, ((tile_w_half + rect.x - Sx) / tile_w + Ox));
1609 		Py = place_wrap_y(place, ((tile_h_half + rect.y - Sy) / tile_h + Oy));
1610 
1611 		if (oPx != Px || oPy != Py)
1612 		{
1613 			// check edge if required
1614 			if (checkEdge)
1615 			{
1616 				if (Px < 0 || Py < 0 || Px >= place_w(place) || Py >= place_h(place))
1617 					goto done;
1618 			}
1619 
1620 			// check range if required
1621 			if (range>1)
1622 			{
1623 				if (range < place_flying_distance(place, orx, ory, Px, Py))
1624 				{
1625 					//need to back up one square, since we the missile shouldnt have gotten this far
1626 					Px = oPx;
1627 					Py = oPy;
1628 					goto done;
1629 				}
1630 			}
1631 
1632 			// check if blocked by terrain
1633 			if (!missile->enterTile(place, Px, Py))
1634 				goto done;
1635 
1636 
1637 			//check if callback indicates blocked
1638 			if (canEnter & !mistype->fireEnterTile(missile, place, Px, Py))
1639 				goto done;
1640 		}
1641 
1642 		if (paintloop == 0)
1643 		{
1644 			// if have done paintloop steps, redraw sprite
1645 			paintloop = paintloopsize;
1646 			if (mapTileIsVisible(Px, Py) && sprite)
1647 			{
1648 				tempx = rect.x;
1649 				tempy = rect.y;
1650 				rect.x = (rect.x + oldx)/2;
1651 				rect.y = (rect.y + oldy)/2;
1652 
1653 				mapPaintProjectile(&rect, sprite, surf, 15, currentframe, beam);
1654 				if (framecount > 1)
1655 					currentframe=(currentframe+1)%framecount;
1656 
1657 				rect.x = oldx = tempx;
1658 				rect.y = oldy = tempy;
1659 
1660 				mapPaintProjectile(&rect, sprite, surf, 15, currentframe, beam);
1661 				if (framecount > 1)
1662 					currentframe=(currentframe+1)%framecount;
1663 			}
1664 			else if (framecount > 1)
1665 			{
1666 				currentframe=(currentframe+2)%framecount;
1667 			}
1668 		}
1669 		paintloop--;
1670 
1671 		if (P > 0)
1672 		{
1673 			rect.x += Xincr;
1674 			rect.y += Yincr;
1675 			P += dPru;
1676 		}
1677 		else
1678 		{
1679 			rect.x += Xsubincr;
1680 			rect.y += Ysubincr;
1681 			P += dPr;
1682 		}
1683 	}
1684       done:
1685 	// erase the missile
1686 	// mapRepaintView(NULL, REPAINT_ACTIVE);
1687 	if (beam)
1688 		SDL_Delay(100);
1689 
1690 	mapUpdate(0);
1691 
1692 	// restore the missile sprite to the default facing
1693 	if (sprite)
1694 		sprite_set_facing(sprite, SPRITE_DEF_FACING);
1695 
1696   *Bx = Px;
1697   *By = Py;
1698 
1699 	t2 = SDL_GetTicks();
1700 
1701 	if (PROFILE_ANIMATE) {
1702 	  printf("mapAnimateProjectile: %d msec\n", t2 - t1);
1703 	}
1704 }
1705 
mapAttachCamera(class Object * subject)1706 void mapAttachCamera(class Object *subject)
1707 {
1708         Map.subject = subject;
1709 }
1710 
mapDetachCamera(class Object * subject)1711 void mapDetachCamera(class Object *subject)
1712 {
1713         Map.subject = NULL;
1714 }
1715 
mapUpdateTile(struct place * place,int x,int y)1716 void mapUpdateTile(struct place *place, int x, int y)
1717 {
1718         struct terrain *terrain;
1719         int index;
1720         char *vmask;
1721         SDL_Rect rect;
1722 
1723         //dbg("mapUpdateTile %d:%d:%s\n", x, y, place->name);
1724 
1725         if (NULL == Map.aview)
1726                 return;
1727 
1728 
1729         // ---------------------------------------------------------------------
1730         // Assume we want the active view as it was last rendered. Calculate
1731         // the screen coordinates of the given map location. Check if the
1732         // coordinates are in the map viewer and abort if not.
1733         // ---------------------------------------------------------------------
1734 
1735         if (place != Map.place)
1736                 return;
1737 
1738         rect.x = (x - (Map.aview->vrect.x + Map.aview->subrect.x))
1739                 * TILE_W/Map.aview->zoom + Map.srect.x;
1740         if (rect.x < Map.srect.x || rect.x > (Map.srect.x + Map.srect.w
1741                                               - TILE_W/Map.aview->zoom))
1742                 return;
1743 
1744         rect.y = (y - (Map.aview->vrect.y + Map.aview->subrect.y))
1745                 * TILE_H/Map.aview->zoom + Map.srect.y;
1746         if (rect.y < Map.srect.y || rect.y > (Map.srect.y + Map.srect.h
1747                                               - TILE_H/Map.aview->zoom))
1748                 return;
1749 
1750         // ---------------------------------------------------------------------
1751         // Erase the tile.
1752         // ---------------------------------------------------------------------
1753 
1754         rect.w = TILE_W/Map.aview->zoom;
1755         rect.h = TILE_H/Map.aview->zoom;
1756         screenErase(&rect);
1757 
1758         terrain = place_get_terrain(place, x, y);
1759 
1760         if (Map.aview->zoom > 1) {
1761 
1762                 // ------------------------------------------------------------
1763                 // When zoomed ignore LOS. The vmasks aren't big enough to
1764                 // cover the area viewed.
1765                 // ------------------------------------------------------------
1766 
1767                 sprite_zoom_out(Map.aview->zoom);
1768 		screenZoomOut(Map.aview->zoom);
1769                 sprite_paint(terrain->sprite, 0, rect.x, rect.y);
1770                 place_paint_objects(place, x, y, rect.x, rect.y);
1771                 sprite_zoom_in(Map.aview->zoom);
1772 		screenZoomIn(Map.aview->zoom);
1773 
1774         } else {
1775 
1776                 // ------------------------------------------------------------
1777                 // If the place is not in line-of-sight then don't paint the
1778                 // object(s) there. Paint the terrain iff ShowAllTerrain or
1779                 // XrayVision are in effect.
1780                 // ------------------------------------------------------------
1781 
1782                 vmask = Map.vmask;
1783                 index = ((y - Map.aview->vrect.y) * Map.aview->vrect.w)
1784                         + (x - Map.aview->vrect.x);
1785 
1786                 if (vmask[index] || ShowAllTerrain || XrayVision) {
1787                         sprite_paint(terrain->sprite, 0, rect.x, rect.y);
1788                 }
1789 
1790                 if (vmask[index]) {
1791                         place_paint_objects(place, x, y, rect.x, rect.y);
1792                 }
1793 
1794                 // If the selected object is not on this tile then shade it
1795                 if (! Map.selected
1796                     || Map.selected->getPlace() != Map.place
1797                     || Map.selected->getX() != x
1798                     || Map.selected->getY() != y)
1799                         screenShade(&rect, LIT - Map.lmap[index]);
1800 
1801         }
1802 
1803         if (x == Session->crosshair->getX() && y == Session->crosshair->getY())
1804                 map_paint_cursor();
1805 
1806 
1807 
1808 
1809         screenUpdate(&rect);
1810 
1811 }
1812 
mapSetSelected(class Object * obj)1813 void mapSetSelected(class Object *obj)
1814 {
1815         if (Map.selected == obj)
1816                 return;
1817 
1818         if (Map.selected) {
1819                 obj_dec_ref(Map.selected);
1820                 Map.selected = NULL;
1821         }
1822 
1823         Map.selected = obj;
1824         if (obj)
1825                 obj_inc_ref(obj);
1826 }
1827 
mapScreenToPlaceCoords(int * x,int * y)1828 int mapScreenToPlaceCoords(int *x, int *y)
1829 {
1830     if (! point_in_rect(*x, *y, &Map.srect)) {
1831         return -1;
1832     }
1833     int px = ((*x - Map.srect.x) * Map.aview->zoom) / TILE_W + Map.aview->vrect.x + Map.aview->subrect.x;
1834     int py = ((*y - Map.srect.y) * Map.aview->zoom) / TILE_H + Map.aview->vrect.y + Map.aview->subrect.y;
1835 
1836     if (place_off_map(Map.place, px, py)) {
1837         return -1;
1838     }
1839 
1840     *x = place_wrap_x(Map.place, px);
1841     *y = place_wrap_y(Map.place, py);
1842     return 0;
1843 }
1844 
mapSetImage(SDL_Surface * image)1845 void mapSetImage(SDL_Surface *image)
1846 {
1847         Map.is_image_mode = 1;
1848         if (image) {
1849                 /* center the image over the map */
1850                 SDL_Rect rect = Map.srect;
1851                 if (image->w < Map.srect.w) {
1852                         rect.x = Map.srect.x + (Map.srect.w - image->w) / 2;
1853                         rect.w = image->w;
1854                 }
1855                 if (image->h < Map.srect.h) {
1856                         rect.y = Map.srect.y + (Map.srect.h - image->h) / 2;
1857                         rect.h = image->h;
1858                 }
1859                 screenErase(&Map.srect);
1860                 screenBlit(image, NULL, &rect);
1861         } else {
1862                 screenErase(&Map.srect);
1863         }
1864         screenUpdate(&Map.srect);
1865 }
1866 
mapClearImage(void)1867 void mapClearImage(void)
1868 {
1869         Map.is_image_mode = 0;
1870 }
1871 
mapBlitImage(SDL_Surface * image,Uint32 x,Uint32 y)1872 void mapBlitImage(SDL_Surface *image, Uint32 x, Uint32 y)
1873 {
1874         SDL_Rect rect;
1875 
1876         rect.x = Map.srect.x + x;
1877         rect.y = Map.srect.y + y;
1878         rect.w = Map.srect.w - x;
1879         rect.h = Map.srect.h - y;
1880 
1881         screenBlit(image, NULL, &rect);
1882         screenUpdate(&Map.srect);
1883 }
1884