2 // nazghul - an old-school RPG engine
3 // Copyright (C) 2002, 2003 Gordon McNutt
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)
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
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
20 // gmcnutt@users.sourceforge.net
23 #include "sky.h" // For time/date functions
35 #include "nazghul.h" // for DeveloperMode
40 #define PROFILE_REPAINT 0
41 #define PROFILE_ANIMATE 0
43 #define LMAP_W (VMASK_W)
44 #define LMAP_H (VMASK_H)
46 #define MVIEW_SZ (sizeof(struct mview))
47 #define LMAP_SZ (LMAP_W * LMAP_H)
48 #define MAX_LIGHTS LMAP_SZ
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)
62 * Convert map coords to screen coords
65 (Map.srect.x+((x)-(Map.aview->vrect.x+Map.aview->subrect.x))*TILE_W)
67 (Map.srect.y+((y)-(Map.aview->vrect.y+Map.aview->subrect.y))*TILE_H)
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 */
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;
97 char *vmask; /* final mask used to render */
98 SDL_Surface *tile_scratch_surf;
100 class Object *selected; /* selected object -- don't shade the tile it's
103 /* FIXME: why is this dynamically allocated when we're using
105 struct light_source *lights;
107 unsigned char *tmp_lmap;
108 char is_image_mode : 1;
112 * The callback function prototype for rendering a tile.
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);
118 static void myRmView(struct mview *view, void *data)
120 list_remove(&view->list);
123 static void mapMergeRects(SDL_Rect *src_rect, unsigned char *src,
124 SDL_Rect *dst_rect, unsigned char *dst)
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;
130 // skip identical merges (yes, it happens)
134 if (src_rect->x < dst_rect->x) {
136 tmp = src_rect->x + src_rect->w - dst_rect->x;
139 c_src_start = dst_rect->x - src_rect->x;
140 c_end = c_src_start + tmp;
143 // Destination leftmost
144 tmp = dst_rect->x + dst_rect->w - src_rect->x;
149 c_dst_start = src_rect->x - dst_rect->x;
152 if (src_rect->y < dst_rect->y) {
154 tmp = src_rect->y + src_rect->h - dst_rect->y;
157 r_src_start = dst_rect->y - src_rect->y;
158 r_end = r_src_start + tmp;
161 // Destination topmost
162 tmp = dst_rect->y + dst_rect->h - src_rect->y;
167 r_dst_start = src_rect->y - dst_rect->y;
170 for (r_src = r_src_start, r_dst = r_dst_start; r_src < r_end;
172 for (c_src = c_src_start, c_dst = c_dst_start; c_src < c_end;
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);
183 static void mapMergeView(struct mview *view, void *data)
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;
190 /* Skip this view if it is the active view */
191 if (view == Map.aview)
194 // ---------------------------------------------------------------------
195 // Find the indices to merge from depending on the relationship between
196 // the map view rectangle and the mview being merged.
197 // ---------------------------------------------------------------------
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;
204 c_src_start = Map.aview->vrect.x - view->vrect.x;
205 c_end = c_src_start + tmp;
208 /* Active view leftmost (A) */
209 tmp = Map.aview->vrect.x + Map.aview->vrect.w - view->vrect.x;
214 c_dst_start = view->vrect.x - Map.aview->vrect.x;
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;
222 r_src_start = Map.aview->vrect.y - view->vrect.y;
223 r_end = r_src_start + tmp;
226 /* Active view topmost (A) */
227 tmp = Map.aview->vrect.y + Map.aview->vrect.h - view->vrect.y;
232 r_dst_start = view->vrect.y - Map.aview->vrect.y;
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 // ---------------------------------------------------------------------
241 vmask = vmask_get(Map.place, mview_center_x(view), mview_center_y(view));
246 // ---------------------------------------------------------------------
247 // Copy the contents of the view's vmask to the master vmask.
248 // ---------------------------------------------------------------------
250 for (r_src = r_src_start, r_dst = r_dst_start; r_src < r_end;
252 for (c_src = c_src_start, c_dst = c_dst_start; c_src < c_end;
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];
262 static void myMarkAsDirty(struct mview *view, void *data)
267 static void mySetViewLightRadius(struct mview *view, void *data)
269 int rad = *((int*)data);
273 static int mapCalcMaxLightRadius(int light)
275 // until something faster becomes necessary
276 return (int)sqrt((double)light);
281 static void mapDumpRect(char *name, SDL_Rect *rect, unsigned char *data)
285 printf("Rect %s (%d %d %d %d):\n", name, rect->x, rect->y, rect->w,
288 for (y = 0; y < rect->h; y++) {
289 for (x = 0; x < rect->w; x++, i++) {
290 printf(" %03d", data[i]);
299 * Given a light source, add its contribution to the light map (Map.lmap).
301 * @param light is the light source to add
302 * @param main_view is the view containing the light source
304 static void mapMergeLightSource(struct light_source *light, struct mview *main_view)
308 struct mview tmp_view;
316 // Initialize the temporary view to be centered on the light
317 // source. (Note: ignore the subrect, it shouldn't matter)
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;
327 radius = min(mapCalcMaxLightRadius(light->light), VMASK_W / 2);
329 // Fetch the vmask from the cache.
330 vmask = vmask_get(Map.place, light->x, light->y);
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.
342 //dbg("lightmap %d:%d:%s\n", light->x, light->y, Map.place->name);
344 for (y = min_y; y < max_y; y++) {
346 map_y = place_wrap_y(Map.place, tmp_view.vrect.y + y);
347 vmask_i = y * VMASK_W + min_x;
349 for (x = min_x; x < max_x; x++, vmask_i++) {
351 // skip non-visible tiles
352 if (vmask[vmask_i] == 0) {
353 Map.tmp_lmap[vmask_i] = 0;
357 map_x = place_wrap_x(Map.place, tmp_view.vrect.x + x);
359 D = place_flying_distance(Map.place, light->x,
360 light->y, map_x, map_y);
362 Map.tmp_lmap[vmask_i] = min(light->light / D, 255);
366 // Merge this source's lightmap (contained in the vmask we just built)
367 // with the main lightmap.
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,
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
382 * @param view specifies which part of the map to use
384 static void mapBuildLightMap(struct mview *view)
394 /* Initialize the main lightmap to ambient light levels. */
395 ambient_light = sky_get_ambient_light(&Session->sky);
397 (Map.place->underground ? UNLIT : ambient_light),
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) {
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. */
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++) {
418 map_x = place_wrap_x(Map.place, view->vrect.x + x);
419 light = place_get_light(Map.place, map_x, map_y);
423 Map.lights[lt_i].x = map_x;
424 Map.lights[lt_i].y = map_y;
425 Map.lights[lt_i].light = light;
430 /* Skip further processing if there are no light sources */
436 /* For each light source build a lightmap centered on that source and
437 * merge it into the main lightmap. */
439 mapMergeLightSource(&Map.lights[lt_i], view);
444 static void myShadeScene(SDL_Rect *subrect)
450 rect.x = Map.srect.x;
451 rect.y = Map.srect.y;
455 lmap_i = subrect->y * VMASK_W + subrect->x;
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) {
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
469 screenShade(&rect, LIT - Map.lmap[lmap_i + x]);
474 static inline void myAdjustCameraInBounds(void)
476 if (Map.place->wraps)
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);
485 void mapForEachView(void (*fx) (struct mview *, void *), void *data)
488 list = Map.views.next;
489 while (list != &Map.views) {
492 view = outcast(list, struct mview, list);
499 void mapSetLosStyle(const char *los)
502 los_destroy(LosEngine);
504 LosEngine = los_create(los, VMASK_W, VMASK_H, -1);
508 static void mapExit(void)
526 mapDestroyView(Map.cam_view);
539 memset(&Map, 0, sizeof(Map));
541 Map.lights = (struct light_source*)calloc(MAX_LIGHTS,
542 sizeof(Map.lights[0]));
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]));
554 /* This one is used during mapMergeLightSource */
555 Map.tmp_lmap = (unsigned char*)calloc(LMAP_SZ,
556 sizeof(Map.tmp_lmap[0]));
560 if (!(Map.cam_view = mapCreateView()))
563 Map.vmask = (char*)calloc(VMASK_SZ, sizeof(Map.vmask[0]));
567 list_init(&Map.views);
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;
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;
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;
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;
597 Map.tile_scratch_surf = screenCreateSurface(TILE_W, TILE_H);
598 assert(Map.tile_scratch_surf);
607 void mapFlash(int mdelay)
609 screenFlash(&Map.srect, mdelay, White);
612 void mapSetPlace(struct place *place)
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;
623 Map.cam_min_x = Map.cam_max_x = (place_w(place) + 1)/ 2 - 1;
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;
630 Map.cam_min_y = Map.cam_max_y = (place_h(place) + 1) / 2 - 1;
634 struct mview *mapCreateView(void)
638 /* Allocate a new view */
639 if (!(v = (struct mview *) malloc(MVIEW_SZ)))
642 /* Initialize the new view */
643 memset(v, 0, MVIEW_SZ);
645 v->vrect.w = VMASK_W;
646 v->vrect.h = VMASK_H;
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;
653 //dbg("mapCreateView: return %08lx\n", v);
659 void mapDestroyView(struct mview *view)
661 //dbg("mapDestroyView(%08lx)\n", view);
665 void mapAddView(struct mview *view)
667 //dbg("mapAddView(%08lx)\n", view);
668 list_add(&Map.views, &view->list);
671 void mapRmView(struct mview *view)
673 //dbg("mapRmView(%08lx)\n", view);
674 if (view == ALL_VIEWS)
675 mapForEachView(myRmView, 0);
680 void mapCenterView(struct mview *view, int x, int y)
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);
688 void mapRepaintClock(void)
690 char * date_time_str = time_HHMM_as_string();
695 // Show the clock time:
696 screenErase(&Map.clkRect);
697 screenPrint(&Map.clkRect, 0, "%s", date_time_str);
698 screenUpdate(&Map.clkRect);
699 } // mapRepaintClock()
702 * converts points so they appear correctly on wrapping maps
704 static void map_convert_point_to_vrect(int *x, int *y)
706 SDL_Rect *vrect = &Map.aview->vrect;
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) &&
712 *x += place_w(Map.place);
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) &&
720 *y += place_h(Map.place);
724 static void map_paint_cursor(void)
728 if (!Session->crosshair->is_active())
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)) {
740 sprite_paint(Session->crosshair->getSprite(), 0,
741 MX_TO_SX(x), MY_TO_SY(y));
746 * Paint the terrain sprite for a tile.
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.
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)
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);
765 } else if (ShowAllTerrain || XrayVision) {
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);
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);
785 * Paint the object sprites for a tile.
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.
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)
799 /* The tile is visible, so paint the objects normally. */
800 place_paint_objects(place, map_x, map_y, scr_x, scr_y);
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)) {
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);
818 * Loop over the map tiles shown in the map viewer, invoking a callback
819 * function for each tile.
821 * @param place The place viewed.
822 * @param region The part of the place covered by the visibility mask. Units
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
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.
832 static void map_render_loop(struct place *place,
839 map_tile_render_t tile_render)
843 int map_y; /* in rows */
844 int map_x; /* in cols */
845 int scr_x; /* in pixels */
846 int scr_y; /* in pixels */
851 region->x = place_wrap_x(place, region->x);
852 region->y = place_wrap_y(place, region->y);
856 +-----------------------------------------------------------------+
859 | +-------------------------+ |
872 | +-------------------------+ |
875 +-----------------------------------------------------------------+
878 use_mask = (mask != NULL);
879 map_y = region->y + subrect->y;
880 mask_i = (subrect->y * region->w) + subrect->x;
882 for (row = 0; row < subrect->h; row++, map_y++, mask_i += region->w) {
884 /* Test if the row is off-map */
886 map_y = place_wrap_y(place, map_y);
887 } else if (map_y < 0) {
889 } else if (map_y >= place->terrain_map->h) {
893 /* Set the screen pixel row */
894 scr_y = row * tile_h + dest->y;
896 /* Set the initial map column for this row */
897 map_x = region->x + subrect->x;
899 for (col = 0; col < subrect->w; col++, map_x++) {
903 /* Test if the column is off-map */
905 map_x = place_wrap_x(place, map_x);
906 } else if (map_x < 0) {
908 } else if (map_x >= place->terrain_map->w) {
912 /* Set the screen pixel column */
913 scr_x = col * tile_w + dest->x;
915 /* Set the LOS flag. */
916 in_los = (!use_mask || mask[mask_i + col]);
918 /* Invoke the callback function that does the rendering
920 tile_render(place, map_x, map_y, scr_x, scr_y, in_los);
926 static void mapPaintPlace(struct place *place,
927 SDL_Rect * region, /* portion of place covered by
929 SDL_Rect * dest, /* screen rectangle */
930 unsigned char *mask, /* visibility mask for entire
932 SDL_Rect * subrect, /* sub-rectangle within region
933 * that the map viewer sees */
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);
948 static void mapRepaintCoordinates(void)
950 if (! DeveloperMode) {
954 if (player_party->isOnMap()) {
955 screenPrint(&Map.locRect, 0, "[%d,%d]", player_party->getX(),
956 player_party->getY());
960 if (NULL != Map.subject)
961 screenPrint(&Map.locRect, 0, "[%d,%d]", Map.subject->getX(),
962 Map.subject->getY());
965 static void mapRepaintTurnaround(void)
967 extern int G_turnaround;
972 screenPrint(&Map.turnaroundRect, 0, "TA: %d", G_turnaround);
975 extern int G_latency_start;
976 static void mapRepaintLatency(void)
978 static int latency = 0;
983 latency = SDL_GetTicks() - G_latency_start;
985 //printf("repaint: %d\n", latency);
986 screenPrint(&Map.latencyRect, 0, "LAT: %d", latency);
987 screenUpdate(&Map.latencyRect);
991 * This is the main paint routine.
993 * @param view defines which part of the map to show
994 * @param flags controls controls policies of whether and what to paint
996 static void mapRepaintView(struct mview *view, int flags)
998 int t1, t2, t3, t4, t5, t6, t7, t8;
1002 if (flags & REPAINT_IF_DIRTY && !view->dirty)
1005 if (flags & REPAINT_IF_OLD
1006 && (SDL_GetTicks() - Map.last_repaint) < (Uint32)TickMilliseconds
1007 && (Map.last_repaint < SDL_GetTicks()))
1010 Map.last_repaint = SDL_GetTicks();
1013 G_latency_start = SDL_GetTicks();
1015 t1 = SDL_GetTicks();
1017 screenErase(&Map.srect);
1019 t2 = SDL_GetTicks();
1021 if (Map.aview->blackout) {
1022 // In blackout mode leave the screen erased
1023 goto done_painting_place;
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();
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
1052 // ------------------------------------------------------------
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,
1064 t6 = SDL_GetTicks();
1066 myShadeScene(&view->subrect);
1068 t7 = SDL_GetTicks();
1071 // After shading, repaint the tile with the selected object so
1072 // that it shows up brightly even in darkness.
1074 && Map.selected->getPlace() == Map.place)
1075 mapUpdateTile(Map.place,
1076 Map.selected->getX(),
1077 Map.selected->getY());
1080 done_painting_place:
1082 mapRepaintCoordinates();
1084 mapRepaintTurnaround();
1085 screenUpdate(&Map.srect);
1087 // ---------------------------------------------------------------------
1088 // Repaint the latency AFTER the screenUpdate because we want that to
1089 // be part of the time measurement.
1090 // ---------------------------------------------------------------------
1092 mapRepaintLatency();
1094 t8 = SDL_GetTicks();
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);
1108 static int mapXToViewX(int x)
1110 SDL_Rect *vrect = &Map.aview->vrect;
1111 int x2 = x - vrect->x;
1113 && place_is_wrapping(Map.place)
1114 && (vrect->x + vrect->w) > place_w(Map.place))
1115 x2 += place_w(Map.place);
1119 static int mapYToViewY(int y)
1121 SDL_Rect *vrect = &Map.aview->vrect;
1122 int y2 = y - vrect->y;
1124 && place_is_wrapping(Map.place)
1125 && (vrect->y + vrect->h) > place_h(Map.place))
1126 y2 += place_h(Map.place);
1130 int mapTileIsWithinViewport(int x, int y)
1132 SDL_Rect *vrect = &Map.aview->vrect;
1133 int vx = mapXToViewX(x);
1134 int vy = mapYToViewY(y);
1143 unsigned char mapTileLightLevel(int x, int y)
1145 int vx = mapXToViewX(x);
1146 int vy = mapYToViewY(y);
1147 return Map.lmap[vy * LMAP_W + vx];
1150 int mapTileIsVisible(int x, int y)
1152 SDL_Rect *vrect = &Map.aview->vrect;
1153 int vx = mapXToViewX(x);
1154 int vy = mapYToViewY(y);
1156 // check if coords in vrect
1163 // If zoomed out then don't bother checking the vmask.
1164 if (Map.aview->zoom > 1)
1167 // Return if the tile is marked as visible
1168 return Map.vmask[vy * vrect->w + vx];
1171 void mapMarkAsDirty(struct mview *view)
1173 if (view == ALL_VIEWS)
1174 mapForEachView(myMarkAsDirty, 0);
1176 myMarkAsDirty(view, 0);
1179 void mapSetRadius(struct mview *view, int rad)
1181 if (view == ALL_VIEWS)
1182 mapForEachView(mySetViewLightRadius, (void *) &rad);
1184 mySetViewLightRadius(view, (void *) &rad);
1187 int mapGetRadius(struct mview *view)
1192 void mapGetMapOrigin(int *x, int *y)
1195 *x = Map.aview->vrect.x + Map.aview->subrect.x;
1196 *y = Map.aview->vrect.y + Map.aview->subrect.y;
1199 void mapGetScreenOrigin(int *x, int *y)
1205 void mapGetTileDimensions(int *w, int *h)
1207 *w = TILE_W / Map.aview->zoom;
1208 *h = TILE_H / Map.aview->zoom;
1211 void mapSetActiveView(struct mview *view)
1214 //dbg("mapSetActiveView: aview=[%d %d]\n", Map.aview->vrect.x, Map.aview->vrect.y);
1217 void mapCenterCamera(int x, int y)
1222 Map.cam_x = place_wrap_x(Map.place, Map.cam_x);
1223 Map.cam_y = place_wrap_y(Map.place, Map.cam_y);
1225 myAdjustCameraInBounds();
1226 mapCenterView(Map.cam_view, Map.cam_x, Map.cam_y);
1229 void mapMoveCamera(int dx, int dy)
1231 mapCenterCamera(Map.cam_x + dx, Map.cam_y + dy);
1234 void mapUpdate(int flags)
1236 if (Map.is_image_mode)
1239 mapRepaintView(Map.cam_view, flags);
1242 void mapSetDirty(void)
1244 if (Map.cam_view != NULL)
1245 Map.cam_view->dirty = 1;
1248 void mapJitter(bool val)
1250 Map.srect.x = MAP_X;
1251 Map.srect.y = MAP_Y;
1252 Map.srect.w = MAP_W;
1253 Map.srect.h = MAP_H;
1256 Map.srect.x += (rand() % 5) - 2;
1257 Map.srect.y += (rand() % 5) - 2;
1261 void mapPeer(bool val)
1264 // Peering will apply to the camera view. Set the scale factor and
1265 // adjust the pertinent rectangle dimensions.
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;
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;
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;
1292 mapCenterCamera(Map.cam_x, Map.cam_y); // recenter
1295 void mapTogglePeering(void)
1297 mapPeer(!Map.peering);
1298 mapCenterCamera(Map.cam_x, Map.cam_y); // recenter
1302 void mapGetCameraFocus(struct place **place, int *x, int *y)
1309 int mapIsInCameraView(struct place *place, int x, int y)
1314 if (place != Map.place)
1317 min = Map.cam_view->subrect.x + Map.cam_view->vrect.x;
1318 max = min + Map.cam_view->subrect.w;
1320 if (x < min || x >= max)
1323 min = Map.cam_view->subrect.y + Map.cam_view->vrect.y;
1324 max = min + Map.cam_view->subrect.h;
1326 if (y < min || y >= max)
1332 void mapBlackout(int val)
1334 Map.cam_view->blackout = !!val;
1337 static void mapPaintProjectile(SDL_Rect *rect, struct sprite *sprite,
1338 SDL_Surface *surf, int dur, int currentframe, bool beam)
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
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)))
1348 // Save the backdrop of the new location
1350 screenCopy(rect, NULL, surf);
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);
1363 // Erase the missile by blitting the background
1366 screenBlit(surf, NULL, rect);
1371 void mapPaintDamage(int x, int y)
1376 if (!mapTileIsVisible(x, y))
1379 mapGetTileDimensions(&tile_w, &tile_h);
1382 rect.x = MX_TO_SX(x);
1383 rect.y = MY_TO_SY(y);
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);
1391 mapPaintProjectile(&rect, Session->damage_sprite,
1392 Map.tile_scratch_surf, 100, 0, false);
1395 //paint damage, but with custom sprite
1396 void mapFlashSprite(int x, int y, struct sprite *sprite)
1401 if (!mapTileIsVisible(x, y))
1404 mapGetTileDimensions(&tile_w, &tile_h);
1407 rect.x = MX_TO_SX(x);
1408 rect.y = MY_TO_SY(y);
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);
1416 mapPaintProjectile(&rect, sprite,
1417 Map.tile_scratch_surf, 100, 0, false);
1420 void mapAnimateProjectile(int Ax, int Ay, int *Bx, int *By,
1421 struct sprite *sprite, struct place *place,
1422 class Missile *missile, float range)
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)
1429 int framecount = sprite_num_frames(sprite);
1430 int currentframe = 0;
1433 SDL_Surface * surf; // for saving/restoring the background
1435 t1 = SDL_GetTicks();
1437 // Get tile dimensions
1440 mapGetTileDimensions(&tile_w, &tile_h);
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;
1446 MissileType *mistype = missile->getObjectType();
1447 bool canEnter = missile->canEnter();
1449 // Create a scratch surface for saving/restoring the background
1450 surf = Map.tile_scratch_surf;
1453 // Get the map coordinates of the view origin (upper left corner)
1455 mapGetMapOrigin(&Ox, &Oy);
1457 // Get the screen coordinates of the map viewer origin
1459 mapGetScreenOrigin(&Sx, &Sy);
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;
1469 // Convert to screen coordinates. (I need to keep the original
1470 // B-coordinates for field effects at the bottom of this routine).
1474 if (place_is_wrapping(place))
1477 Ax = (Ax - Ox) * tile_w + Sx;
1479 Ax = (place_w(place) - Ox + Ax) * tile_w + Sx;
1481 Ay = (Ay - Oy) * tile_h + Sy;
1483 Ay = (place_h(place) - Oy + Ay) * tile_h + Sy;
1486 sBx = (*Bx - Ox) * tile_w + Sx;
1488 sBx = (place_w(place) - Ox + *Bx) * tile_w + Sx;
1490 sBy = (*By - Oy) * tile_h + Sy;
1492 sBy = (place_h(place) - Oy + *By) * tile_h + Sy;
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;
1502 // Create the rect which bounds the missile's sprite (used to update
1503 // that portion of the screen after blitting the sprite).
1510 // Get the distance components
1511 int dX = sBx - rect.x;
1512 int dY = sBy - rect.y;
1516 // Select the sprite orientation based on direction of travel
1519 if ((sprite_facings_list(sprite) & 495) == 495) //nsew + diagonals
1521 sprite_set_facing(sprite, vector_to_8facing(dX, dY));
1523 else if ((sprite_facings_list(sprite) & 170) == 170) //nsew only
1525 sprite_set_facing(sprite, vector_to_dir(dX, dY));
1530 int Xincr = (rect.x > sBx) ? -1 : 1;
1531 // adjust for rounding errors
1538 int Yincr = (rect.y > sBy) ? -1 : 1;
1539 // adjust for rounding errors
1545 int dPr, dPru, P, i , Xsubincr, Ysubincr;
1546 int oldx, oldy, tempx, tempy;
1548 //number of steps between missile repaints
1549 int paintloopsize = 20;
1555 dPru = dPr - (AdX << 1);
1559 if (range > 0.5) // floating point hence error margins
1561 i = TILE_W * (place_w(place) + 2); // == "enough": its actually checked in the loop instead
1567 if (AdX==0 && AdY==0) {
1568 // Fix for 2364311: avoid divide-by-zero
1571 paintloopsize = paintloopsize * (AdX*AdX)/((AdX*AdX)+(AdY*AdY));
1577 dPru = dPr - (AdY << 1);
1582 if (range > 0.5) // floating point hence error margins
1584 i = TILE_H * (place_h(place) + 2); // == "enough": its actually checked in the loop instead
1590 paintloopsize = paintloopsize * (AdY*AdY)/((AdX*AdX)+(AdY*AdY));
1593 // firing past selected range, so work out the map edges if need be.
1594 bool checkEdge = ((range >= 1) && !(place_is_wrapping(place)));
1599 int paintloop = paintloopsize;
1601 bool beam = missile->getObjectType()->isBeam();
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));
1611 if (oPx != Px || oPy != Py)
1613 // check edge if required
1616 if (Px < 0 || Py < 0 || Px >= place_w(place) || Py >= place_h(place))
1620 // check range if required
1623 if (range < place_flying_distance(place, orx, ory, Px, Py))
1625 //need to back up one square, since we the missile shouldnt have gotten this far
1632 // check if blocked by terrain
1633 if (!missile->enterTile(place, Px, Py))
1637 //check if callback indicates blocked
1638 if (canEnter & !mistype->fireEnterTile(missile, place, Px, Py))
1644 // if have done paintloop steps, redraw sprite
1645 paintloop = paintloopsize;
1646 if (mapTileIsVisible(Px, Py) && sprite)
1650 rect.x = (rect.x + oldx)/2;
1651 rect.y = (rect.y + oldy)/2;
1653 mapPaintProjectile(&rect, sprite, surf, 15, currentframe, beam);
1655 currentframe=(currentframe+1)%framecount;
1657 rect.x = oldx = tempx;
1658 rect.y = oldy = tempy;
1660 mapPaintProjectile(&rect, sprite, surf, 15, currentframe, beam);
1662 currentframe=(currentframe+1)%framecount;
1664 else if (framecount > 1)
1666 currentframe=(currentframe+2)%framecount;
1685 // erase the missile
1686 // mapRepaintView(NULL, REPAINT_ACTIVE);
1692 // restore the missile sprite to the default facing
1694 sprite_set_facing(sprite, SPRITE_DEF_FACING);
1699 t2 = SDL_GetTicks();
1701 if (PROFILE_ANIMATE) {
1702 printf("mapAnimateProjectile: %d msec\n", t2 - t1);
1706 void mapAttachCamera(class Object *subject)
1708 Map.subject = subject;
1711 void mapDetachCamera(class Object *subject)
1716 void mapUpdateTile(struct place *place, int x, int y)
1718 struct terrain *terrain;
1723 //dbg("mapUpdateTile %d:%d:%s\n", x, y, place->name);
1725 if (NULL == Map.aview)
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 // ---------------------------------------------------------------------
1735 if (place != Map.place)
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))
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))
1750 // ---------------------------------------------------------------------
1752 // ---------------------------------------------------------------------
1754 rect.w = TILE_W/Map.aview->zoom;
1755 rect.h = TILE_H/Map.aview->zoom;
1758 terrain = place_get_terrain(place, x, y);
1760 if (Map.aview->zoom > 1) {
1762 // ------------------------------------------------------------
1763 // When zoomed ignore LOS. The vmasks aren't big enough to
1764 // cover the area viewed.
1765 // ------------------------------------------------------------
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);
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 // ------------------------------------------------------------
1783 index = ((y - Map.aview->vrect.y) * Map.aview->vrect.w)
1784 + (x - Map.aview->vrect.x);
1786 if (vmask[index] || ShowAllTerrain || XrayVision) {
1787 sprite_paint(terrain->sprite, 0, rect.x, rect.y);
1791 place_paint_objects(place, x, y, rect.x, rect.y);
1794 // If the selected object is not on this tile then shade it
1796 || Map.selected->getPlace() != Map.place
1797 || Map.selected->getX() != x
1798 || Map.selected->getY() != y)
1799 screenShade(&rect, LIT - Map.lmap[index]);
1803 if (x == Session->crosshair->getX() && y == Session->crosshair->getY())
1809 screenUpdate(&rect);
1813 void mapSetSelected(class Object *obj)
1815 if (Map.selected == obj)
1819 obj_dec_ref(Map.selected);
1820 Map.selected = NULL;
1828 int mapScreenToPlaceCoords(int *x, int *y)
1830 if (! point_in_rect(*x, *y, &Map.srect)) {
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;
1836 if (place_off_map(Map.place, px, py)) {
1840 *x = place_wrap_x(Map.place, px);
1841 *y = place_wrap_y(Map.place, py);
1845 void mapSetImage(SDL_Surface *image)
1847 Map.is_image_mode = 1;
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;
1855 if (image->h < Map.srect.h) {
1856 rect.y = Map.srect.y + (Map.srect.h - image->h) / 2;
1859 screenErase(&Map.srect);
1860 screenBlit(image, NULL, &rect);
1862 screenErase(&Map.srect);
1864 screenUpdate(&Map.srect);
1867 void mapClearImage(void)
1869 Map.is_image_mode = 0;
1872 void mapBlitImage(SDL_Surface *image, Uint32 x, Uint32 y)
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;
1881 screenBlit(image, NULL, &rect);
1882 screenUpdate(&Map.srect);