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