OSDN Git Service

Nazghul-0.7.1
[nazghul-jp/nazghul-jp.git] / src / map.c
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
118 static void myRmView(struct mview *view, void *data)
119 {
120         list_remove(&view->list);
121 }
122
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
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
262 static void myMarkAsDirty(struct mview *view, void *data)
263 {
264         view->dirty = 1;
265 }
266
267 static void mySetViewLightRadius(struct mview *view, void *data)
268 {
269         int rad = *((int*)data);
270         view->rad = rad;
271 }
272
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  */
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  */
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
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
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
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
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
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
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
607 void mapFlash(int mdelay)
608 {
609         screenFlash(&Map.srect, mdelay, White);
610 }
611
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
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
659 void mapDestroyView(struct mview *view)
660 {
661         //dbg("mapDestroyView(%08lx)\n", view);
662         free(view);
663 }
664
665 void mapAddView(struct mview *view)
666 {
667         //dbg("mapAddView(%08lx)\n", view);
668         list_add(&Map.views, &view->list);
669 }
670
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
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
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  */
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
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  */
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  */
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  */
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
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
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
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;
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  */
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
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
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
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
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
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 }
1171 void mapMarkAsDirty(struct mview *view)
1172 {
1173         if (view == ALL_VIEWS)
1174                 mapForEachView(myMarkAsDirty, 0);
1175         else
1176                 myMarkAsDirty(view, 0);
1177 }
1178
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
1187 int mapGetRadius(struct mview *view)
1188 {
1189         return view->rad;
1190 }
1191
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
1199 void mapGetScreenOrigin(int *x, int *y)
1200 {
1201         *x = Map.srect.x;
1202         *y = Map.srect.y;
1203 }
1204
1205 void mapGetTileDimensions(int *w, int *h)
1206 {
1207         *w = TILE_W / Map.aview->zoom;
1208         *h = TILE_H / Map.aview->zoom;
1209 }
1210
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
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
1229 void mapMoveCamera(int dx, int dy)
1230 {
1231         mapCenterCamera(Map.cam_x + dx, Map.cam_y + dy);
1232 }
1233
1234 void mapUpdate(int flags)
1235 {
1236         if (Map.is_image_mode)
1237                 return;
1238
1239         mapRepaintView(Map.cam_view, flags);
1240 }
1241
1242 void mapSetDirty(void)
1243 {
1244         if (Map.cam_view != NULL)
1245                 Map.cam_view->dirty = 1;
1246 }
1247
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
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
1295 void mapTogglePeering(void)
1296 {
1297         mapPeer(!Map.peering);
1298         mapCenterCamera(Map.cam_x, Map.cam_y); // recenter
1299         mapUpdate(0);
1300 }
1301
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
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
1332 void mapBlackout(int val)
1333 {
1334         Map.cam_view->blackout = !!val;
1335 }
1336
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
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
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
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
1706 void mapAttachCamera(class Object *subject)
1707 {
1708         Map.subject = subject;
1709 }
1710
1711 void mapDetachCamera(class Object *subject)
1712 {
1713         Map.subject = NULL;
1714 }
1715
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
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
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
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
1867 void mapClearImage(void)
1868 {
1869         Map.is_image_mode = 0;
1870 }
1871
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 }