]> git.pond.sub.org Git - empserver/blob - src/lib/subs/maps.c
Clean up bmap commands' flags argument
[empserver] / src / lib / subs / maps.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2011, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                Ken Stevens, Steve McClure, Markus Armbruster
5  *
6  *  Empire is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  *
19  *  ---
20  *
21  *  See files README, COPYING and CREDITS in the root of the source
22  *  tree for related information and legal notices.  It is expected
23  *  that future projects/authors will amend these files as needed.
24  *
25  *  ---
26  *
27  *  maps.c: Map routines
28  *
29  *  Known contributors to this file:
30  *     Ken Stevens, 1995
31  *     Steve McClure, 1998
32  *     Markus Armbruster, 2004-2011
33  *     Ron Koenderink, 2006
34  */
35
36 #include <config.h>
37
38 #include <ctype.h>
39 #include "com.h"
40 #include "empobj.h"
41 #include "file.h"
42 #include "land.h"
43 #include "map.h"
44 #include "match.h"
45 #include "misc.h"
46 #include "nat.h"
47 #include "nsc.h"
48 #include "nuke.h"
49 #include "optlist.h"
50 #include "plane.h"
51 #include "player.h"
52 #include "prototypes.h"
53 #include "sect.h"
54 #include "ship.h"
55 #include "xy.h"
56
57 /* Flags for draw_map() */
58 /* whether to put ships, planes, land units or nukes on the map */
59 #define MAP_SHIP        bit(0)
60 #define MAP_PLANE       bit(1)
61 #define MAP_LAND        bit(2)
62 #define MAP_NUKE        bit(3)
63 #define MAP_ALL         (MAP_SHIP | MAP_PLANE | MAP_LAND | MAP_NUKE)
64 /* whether to highlight own sectors */
65 #define MAP_HIGH        bit(4)
66 /* whether to draw a map or a bmap */
67 #define MAP_BMAP        bit(5)
68 /* whether to draw an alternate map: newdes for map, true bmap for bmap */
69 #define MAP_ALT         bit(6)
70 /* whether to revert bmap, internal to do_map() */
71 #define MAP_BMAP_REVERT bit(7)
72
73 static int parse_map_flags(int, char *);
74 static int revert_bmap(struct nstr_sect *);
75 static int draw_map(char, int, struct nstr_sect *);
76 static int bmnxtsct(struct nstr_sect *);
77 static char map_char(int, natid, int);
78 static int unit_map(int, int, struct nstr_sect *, char *);
79
80 int
81 do_map(int bmap, int unit_type, char *arg, char *map_flags_arg)
82 {
83     struct nstr_sect ns;
84     char origin = '\0';
85     int map_flags;
86
87     switch (sarg_type(arg)) {
88     case NS_DIST:
89     case NS_AREA:
90     case NS_ALL:
91         if (!snxtsct(&ns, arg))
92             return RET_SYN;
93         break;
94     default:
95         if (unit_map(unit_type, atoi(arg), &ns, &origin))
96             return RET_FAIL;
97     }
98
99     map_flags = parse_map_flags(bmap, map_flags_arg);
100     if (map_flags < 0) 
101         return RET_SYN;
102
103     if (map_flags & MAP_BMAP_REVERT)
104         return revert_bmap(&ns);
105     return draw_map(origin, map_flags, &ns);
106 }
107
108 static void
109 warn_deprecated_arg(char *what, char *arg, char *use)
110 {
111     pr("%s '%s' is deprecated and will go away in a future release.\n"
112        "Use %s instead.\n",
113        what, arg, use);
114 }
115
116 static int
117 parse_map_flags(int bmap, char *str)
118 {
119     int map_flags;
120     char *p;
121     int tflags = 0;
122     char *tp = NULL;
123
124     switch (bmap) {
125     default: CANT_REACH();
126         /* fall through */
127     case 'b': map_flags = MAP_BMAP; break;
128     case 'n': map_flags = MAP_ALT; break;
129     case 0:   map_flags = 0;
130     }
131
132     if (!str)
133         return map_flags;
134
135     /* special case "revert" */
136     if (bmap == 'b' && mineq(str, "revert") != ME_MISMATCH)
137         return MAP_BMAP_REVERT;
138
139     for (p = str; *p; p++) {
140         switch (*p) {
141         case 's':
142         case 'S':
143             map_flags |= MAP_SHIP;
144             break;
145         case 'l':
146         case 'L':
147             map_flags |= MAP_LAND;
148             break;
149         case 'p':
150         case 'P':
151             map_flags |= MAP_PLANE;
152             break;
153         case 'n':
154         case 'N':
155             map_flags |= MAP_NUKE;
156             break;
157         case 'h':
158         case 'H':
159             map_flags |= MAP_HIGH;
160             break;
161         case '*':
162             map_flags |= MAP_ALL;
163             break;
164         case 't':
165             if (bmap != 'b')
166                 goto bad_flag;
167             map_flags |= MAP_ALT;
168             /*
169              * Flags following 't' used to be ignored.  That breaks
170              * perfectly sensible "ts".  Try to continue, but save
171              * state for when a bad flag is found.
172              */
173             if (!tflags) {
174                 tflags = map_flags;
175                 tp = p;
176             }
177             break;
178         case 'r':
179             if (bmap != 'b' || tflags)
180                 goto bad_flag;
181             warn_deprecated_arg("Map flag", "r", "argument 'revert'");
182             return MAP_BMAP_REVERT;
183         default:
184         bad_flag:
185             if (tflags) {
186                 /* ignore bad flags following 't' */
187                 warn_deprecated_arg("Argument", tp, "map flag 't'");
188                 return tflags;
189             }
190             pr("Bad flag %c!\n", *p);
191             return -1;
192         }
193     }
194
195     return map_flags;
196 }
197
198 static int
199 revert_bmap(struct nstr_sect *nsp)
200 {
201     if (!confirm("Are you sure you want to revert your bmap? "))
202         return RET_FAIL;
203     while (bmnxtsct(nsp))
204         player->bmap[nsp->id] = player->map[nsp->id];
205     ef_write(EF_BMAP, player->cnum, player->bmap);
206     return RET_OK;
207 }
208
209 static int
210 draw_map(char origin, int map_flags, struct nstr_sect *nsp)
211 {
212     struct natstr *np;
213     struct range range;
214     struct nstr_item ni;
215     union empobj_storage unit;
216     coord x, y;
217     int i;
218     /* Note this is not re-entrant anyway, so we keep the buffers
219        around */
220     static unsigned char *bitmap = NULL;
221     static char *wmapbuf = NULL;
222     static char **wmap = NULL;
223     static int ef_mappable[] = { EF_PLANE, EF_SHIP, EF_LAND, EF_NUKE, EF_BAD };
224     static int ef_unit_map[] = { MAP_PLANE, MAP_SHIP, MAP_LAND, MAP_NUKE };
225     char *name;
226
227     if (!wmapbuf)
228         wmapbuf = malloc(WORLD_Y * MAPWIDTH(1));
229     if (!wmap) {
230         wmap = malloc(WORLD_Y * sizeof(char *));
231         if (wmap && wmapbuf) {
232             for (i = 0; i < WORLD_Y; i++)
233                 wmap[i] = &wmapbuf[MAPWIDTH(1) * i];
234         } else if (wmap) {
235             free(wmap);
236             wmap = NULL;
237         }
238     }
239     if (!bitmap)
240         bitmap = malloc((WORLD_SZ() + 7) / 8);
241     if (!wmapbuf || !wmap || !bitmap) {
242         pr("Memory error, tell the deity.\n");
243         logerror("malloc failed in draw_map\n");
244         return RET_FAIL;
245     }
246
247     if (!(player->command->c_flags & C_MOD)) {
248         logerror("%s command needs C_MOD flag set",
249                  player->command->c_form);
250         player->command->c_flags |= C_MOD;
251     }
252     np = getnatp(player->cnum);
253     /* zap any conditionals */
254     nsp->ncond = 0;
255     xyrelrange(np, &nsp->range, &range);
256     border(&range, "     ", "");
257     blankfill(wmapbuf, &nsp->range, 1);
258
259     if (map_flags & MAP_BMAP) {
260         int c;
261
262         if (map_flags & MAP_ALT) {
263             while (bmnxtsct(nsp)) {
264                 if (0 != (c = player->map[nsp->id]))
265                     wmap[nsp->dy][nsp->dx] = c;
266             }
267         } else {
268             while (bmnxtsct(nsp)) {
269                 if (0 != (c = player->bmap[nsp->id]))
270                     wmap[nsp->dy][nsp->dx] = c;
271             }
272         }
273     } else {
274         struct sctstr sect;
275
276         if (!player->god) {
277             memset(bitmap, 0, (WORLD_SZ() + 7) / 8);
278             bitinit2(nsp, bitmap, player->cnum);
279         }
280         if (map_flags & MAP_ALT) {      
281             while (nxtsct(nsp, &sect)) {
282                 if (!player->god && !emp_getbit(nsp->x, nsp->y, bitmap))
283                     continue;
284                 wmap[nsp->dy][nsp->dx]
285                     = map_char(sect.sct_newtype, sect.sct_own,
286                                player->owner);
287             }
288         } else {
289             struct sctstr sect;
290             char mapch;
291             int changed = 0;
292
293             while (nxtsct(nsp, &sect)) {
294                 if (!player->god && !emp_getbit(nsp->x, nsp->y, bitmap))
295                     continue;
296                 mapch = map_char(sect.sct_type, sect.sct_own, player->owner);
297                 wmap[nsp->dy][nsp->dx] = mapch;
298                 changed |= map_set(player->cnum, nsp->x, nsp->y, mapch, 0);
299             }
300             if (changed)
301                 writemap(player->cnum);
302         }
303     }
304
305     i = 0;
306     while (ef_mappable[i] != EF_BAD) {
307         if (map_flags & ef_unit_map[i]) {
308             snxtitem_area(&ni, ef_mappable[i], &nsp->range);
309             while (nxtitem(&ni, &unit)) {
310                 if (unit.gen.own == 0)
311                     continue;
312                 if (unit.gen.own != player->cnum && !player->god)
313                     continue;
314
315                 x = deltx(&nsp->range, unit.gen.x);
316                 y = delty(&nsp->range, unit.gen.y);
317
318                 if (ef_mappable[i] == EF_NUKE)
319                     wmap[y][x] = 'N';
320                 else {
321                     name = empobj_chr_name(&unit.gen);
322                     wmap[y][x] = *name & ~0x20;
323                 }
324             }
325         }
326         i++;
327     }
328     if (map_flags & MAP_HIGH) {
329         struct sctstr sect;
330
331         snxtsct_rewind(nsp);
332         while (nxtsct(nsp, &sect)) {
333             if (sect.sct_own == player->cnum)
334                  wmap[nsp->dy][nsp->dx] |= 0x80;
335         }
336     }
337     if (origin)
338         wmap[5][10] = origin & ~0x20;
339     for (y = nsp->range.ly, i = 0; i < nsp->range.height; y++, i++) {
340         int yval;
341
342         yval = yrel(np, y);
343         wmap[i][nsp->range.width] = '\0';
344         pr("%4d %s %-4d\n", yval, wmap[i], yval);
345         if (y >= WORLD_Y)
346             y -= WORLD_Y;
347     }
348     border(&range, "     ", "");
349     return RET_OK;
350 }
351
352 /*
353  * get the next sector in the range
354  */
355 static int
356 bmnxtsct(struct nstr_sect *np)
357 {
358     while (1) {
359         np->dx++;
360         np->x++;
361         if (np->x >= WORLD_X)
362             np->x = 0;
363         if (np->dx >= np->range.width) {
364             np->dx = 0;
365             np->x = np->range.lx;
366             np->dy++;
367             if (np->dy >= np->range.height)
368                 return 0;
369             np->y++;
370             if (np->y >= WORLD_Y)
371                 np->y = 0;
372         }
373         if ((np->y + np->x) & 01)
374             continue;
375         if (np->type == NS_DIST) {
376             np->curdist = mapdist(np->x, np->y, np->cx, np->cy);
377             if (np->curdist > np->dist)
378                 continue;
379         }
380         np->id = sctoff(np->x, np->y);
381         return 1;
382     }
383     /*NOTREACHED*/
384 }
385
386 /*
387  * Return character to use in maps for sector type TYPE owned by OWN.
388  * If OWNER_OR_GOD, the map is for the sector's owner or a deity.
389  */
390 static char
391 map_char(int type, natid own, int owner_or_god)
392 {
393     if (CANT_HAPPEN(type > SCT_TYPE_MAX || !dchr[type].d_mnem))
394         return '?';
395     if (owner_or_god
396         || type == SCT_WATER || type == SCT_MOUNT || type == SCT_WASTE
397         || (!own && (type == SCT_RURAL || type == SCT_PLAINS)))
398         return dchr[type].d_mnem;
399     return '?';
400 }
401
402 static int
403 unit_map(int unit_type, int uid, struct nstr_sect *nsp, char *originp)
404 {
405     union empobj_storage unit;
406     struct range range;
407     char *name;
408
409     if (CANT_HAPPEN((ef_flags(unit_type) & (EFF_OWNER | EFF_XY))
410                     != (EFF_OWNER | EFF_XY)))
411         return RET_FAIL;
412
413     if (!get_empobj(unit_type, uid, &unit))
414         return RET_FAIL;
415     if (!player->owner || unit.gen.own == 0)
416         return RET_FAIL;
417
418     if (unit_type == EF_NUKE)
419         *originp = 'n';
420     else {
421         name = empobj_chr_name(&unit.gen);
422         *originp = *name;
423     }
424
425     range.lx = xnorm(unit.gen.x - 10);
426     range.hx = xnorm(unit.gen.x + 10);
427     range.ly = ynorm(unit.gen.y - 5);
428     range.hy = ynorm(unit.gen.y + 5);
429     xysize_range(&range);
430     snxtsct_area(nsp, &range);
431     return RET_OK;
432 }
433
434 int
435 display_region_map(int bmap, int unit_type, coord curx, coord cury,
436                    char *arg)
437 {
438     char coordinates[80];
439     char *map_flag_arg;
440
441     if (!arg || !*arg) {
442         struct natstr *np;
443
444         np = getnatp(player->cnum);
445         sprintf(coordinates, "%d:%d,%d:%d",
446             xrel(np, curx - 10), xrel(np, curx + 10),
447             yrel(np, cury - 5), yrel(np, cury + 5));
448         arg = coordinates;
449         map_flag_arg = NULL;
450     } else {
451         map_flag_arg = strchr(arg, ' ');
452         if (map_flag_arg != NULL) {
453             *map_flag_arg++  = '\0';
454             while (isspace(*map_flag_arg)) map_flag_arg++;
455         }
456     }
457     player->condarg = NULL;
458     return do_map(bmap, unit_type, arg, map_flag_arg);
459 }
460
461 int
462 bmaps_intersect(natid a, natid b)
463 {
464     char *mapa = ef_ptr(EF_MAP, a);
465     char *mapb = ef_ptr(EF_MAP, b);
466     int i;
467
468     for (i = 0; i < WORLD_SZ(); i++)
469         if (mapa[i] && mapa[i] != ' ' && mapb[i] && mapb[i] != ' ')
470             return 1;
471     return 0;
472 }
473
474 /* Note that this requires that the BMAP is mapped into memory */
475
476 int
477 share_bmap(natid from, natid to, struct nstr_sect *ns, char des,
478            char *from_name)
479 {
480     char *from_bmap = ef_ptr(EF_BMAP, from);
481     char *to_bmap = ef_ptr(EF_BMAP, to);
482     int n = 0;
483     struct sctstr sect;
484     char fromdes;
485     char todes;
486     char from_des = *from_name;
487
488     if (from == to)
489         return 0;
490
491     if (isalpha(from_des))
492         from_des &= ~0x20;
493
494     while (nxtsct(ns, &sect)) {
495         if (!(fromdes = from_bmap[sect.sct_uid]))
496             continue;
497         todes = to_bmap[sect.sct_uid];
498         if (todes &&
499             todes != '?' &&
500             todes != '.' && todes != ' ' && todes != from_des)
501             continue;
502         if (sect.sct_own == from) {
503             if (fromdes != '=' && fromdes != 'h' && fromdes != des)
504                 fromdes = from_des;
505         }
506         if (todes == fromdes)
507             continue;
508         n += map_set(to, ns->x, ns->y, fromdes, 1);
509     }
510
511     if (n)
512         writebmap(to);
513     return n;
514 }