diff --git a/Make.mk b/Make.mk index 2508e1b7..c16d73b8 100644 --- a/Make.mk +++ b/Make.mk @@ -104,7 +104,7 @@ deps := $(obj:.o=.d) # Library archives: libs := $(addprefix lib/, libcommon.a libas.a libgen.a libglobal.a) # Programs: -util := $(addprefix src/util/, $(addsuffix $(EXEEXT), empsched fairland files pconfig)) +util := $(addprefix src/util/, $(addsuffix $(EXEEXT), empdump empsched fairland files pconfig)) client := src/client/empire$(EXEEXT) server := src/server/emp_server$(EXEEXT) # Info subjects: diff --git a/doc/xdump b/doc/xdump index 673a307e..7e82070f 100644 --- a/doc/xdump +++ b/doc/xdump @@ -174,9 +174,10 @@ precisely specified. We use EBNF (ISO 14977) for syntax, except we use '-' in meta-identifiers and omit the concatenation symbol ','. table = header { record } footer ; - header = "XDUMP" space [ "meta" space ] name space timestamp newline ; - name = name-chr { name-chr } ; - name-chr = ? ASCII characters 33..126 ? ; + header = "XDUMP" space [ "meta" space ] + identifier space timestamp newline ; + identifier = id-chr { id-chr } ; + id-char = ? ASCII characters 33..126 except '"#()<>=' ? ; timestamp = intnum ; footer = "/" number newline ; record = [ fields ] newline ; @@ -211,7 +212,7 @@ Notes: Semantics: -* The table name in the header is one of the names in xdump table. +* The table identifier in the header is one of the names in xdump table. * The timestamp increases monotonically. It has a noticeable granularity: game state may change between an xdump and the next @@ -524,7 +525,7 @@ EBNF changes: * Header and footer: - header = "config" name newline colhdr newline ; + header = "config" identifier newline colhdr newline ; colhdr = { identifier [ "(" ( intnum | identifier ) ")" ] } [ "..." ] ; footer = "/config" newline ; @@ -542,7 +543,7 @@ EBNF changes: update. - The column header is due to the self-containedness requirement. - It contains just the essential bit of meta-data: the column name. + It contains just the essential bit of meta-data: the column names. * Symbolic fields: @@ -662,7 +663,7 @@ and section `Objectives': enemy ships, nations). (4) Bandwidth will be minimized (i.e. the format will be as - concise as possible) while remaining human readable (i.e. no + concise as possible) while remaining human-readable (i.e. no binary messages). [Note that data compression may be added at a later date, but if it is added, it will be added on a separate port to maintain backwards compatability.] @@ -735,14 +736,14 @@ f. The data to sync is not readily available the server. limited view on certain parts of server game state on certain events. - To be complete, a machine readable protocol must disclose as much - information as the human readable output. Tracking server game + To be complete, a machine-readable protocol must disclose as much + information as the human-readable output. Tracking server game state changes cannot do that alone. For instance, lookout tells you ship#, owner and location. That event does not trigger any state change on the server! - To be correct, a machine readable protocol must disclose no more - information than the human readable output. When you observe a + To be correct, a machine-readable protocol must disclose no more + information than the human-readable output. When you observe a server game state change, you can only guess what event triggered it, and what it disclosed to which player. You're stuck with conservative assumptions. That's the death knell for completeness. @@ -761,7 +762,7 @@ a shortcut, and it didn't work. That doesn't mean there's no way at all. I believe the only way to get this done right is by tracking *events*. Whenever something is printed to a player, be it live connection or telegram, we need to transmit precisely the same -information in machine readable form. Much more work. +information in machine-readable form. Much more work. xdump shares valuable ideas with C_SYNC, e.g. using selector meta-data. It is, however, much more modest in scope. We're pretty diff --git a/include/commodity.h b/include/commodity.h index cba8f557..41573303 100644 --- a/include/commodity.h +++ b/include/commodity.h @@ -41,9 +41,11 @@ #include "item.h" struct comstr { + /* initial part must match struct empobj */ short ef_type; natid com_owner; short com_uid; + /* end of part matching struct empobj */ i_type com_type; int com_amount; float com_price; diff --git a/include/empobj.h b/include/empobj.h index 2d46022f..97dc00e5 100644 --- a/include/empobj.h +++ b/include/empobj.h @@ -56,7 +56,8 @@ struct empobj { short uid; coord x; /* is valid if EFF_XY is set in table def. */ coord y; /* is valid if EFF_XY is set in table def. */ - signed char type; + signed char type; /* is valid for sectors and units */ + /* remaining are valid for units */ signed char effic; signed char mobil; unsigned char off; @@ -88,9 +89,11 @@ union empobj_storage { struct empobj_chr; +#define get_empobj(type, n, p) ef_read((type), (n), (p)) +#define put_empobj(type, n, p) ef_write((type), (n), (p)) +#define get_empobjp(type, n) ((struct empobj *)ef_ptr((type), (n))) + extern char *obj_nameof(struct empobj *gp); -extern struct empobj *get_empobjp(int type, int id); -extern int put_empobj(struct empobj *gp); extern struct empobj_chr *get_empobj_chr(struct empobj *gp); extern char *emp_obj_chr_name(struct empobj *gp); extern int get_empobj_mob_max(int type); diff --git a/include/file.h b/include/file.h index 6748ba78..1242a660 100644 --- a/include/file.h +++ b/include/file.h @@ -57,7 +57,7 @@ struct empfile { int cids; /* # entries in cache */ int fids; /* # entries in table */ int fd; /* file descriptor, -1 if not open */ - /* flags bits EFF_RDONLY, EFF_CUSTOM also vary */ + /* flags bits EFF_PRIVATE, EFF_CUSTOM also vary */ /* User callbacks */ void (*init)(int, void *); /* called after entry creation, unless null */ @@ -86,8 +86,8 @@ struct empfile { /* Flags set when table contents is mapped */ /* Table is entirely in memory */ #define EFF_MEM bit(8) -/* Table is read-only */ -#define EFF_RDONLY bit(9) +/* Table is privately mapped: changes don't affect the underlying file */ +#define EFF_PRIVATE bit(9) /* Table is customized (configuration tables only) */ #define EFF_CUSTOM bit(10) /* Transient flags, only occur in argument of ef_open() */ @@ -97,7 +97,8 @@ struct empfile { /* * Empire `file types' * These are really table IDs. Some tables are backed by files, some - * are compiled into the server. + * are compiled into the server, some initialized from configuration + * files. */ enum { /* Error value */ diff --git a/include/prototypes.h b/include/prototypes.h index 080bebcc..623b509f 100644 --- a/include/prototypes.h +++ b/include/prototypes.h @@ -315,8 +315,8 @@ extern int sct_typematch(char *); extern int demand_update_want(int *, int *, int); extern int demand_check(void); extern int demandupdatecheck(void); -/* xundump.c */ -extern int xundump(FILE *, char *, int *, int); +/* xdump.c xundump.c */ +/* in xdump.h */ /* * src/lib/gen/ *.c @@ -480,7 +480,6 @@ extern int islist(char *); /* maps.c */ extern int do_map(int bmap, int unit_type, char *arg1, char *arg2); extern int draw_map(int, char, int, struct nstr_sect *); -extern int unit_map(int, int, struct nstr_sect *, char *); extern int display_region_map(int bmap, int unit_type, coord curx, coord cury, char *arg); extern int bmaps_intersect(natid, natid); diff --git a/include/sect.h b/include/sect.h index 18610e49..a960706e 100644 --- a/include/sect.h +++ b/include/sect.h @@ -48,8 +48,8 @@ struct sctstr { short sct_elev; /* elevation/depth */ coord sct_x; /* x coord of sector */ coord sct_y; /* y coord of sector */ + signed char sct_type; /* sector type */ /* end of part matching struct empobj */ - unsigned char sct_type; /* sector type */ unsigned char sct_effic; /* 0% to 100% */ short sct_mobil; /* mobility units */ unsigned char sct_loyal; /* updates until civilans "converted" */ @@ -65,7 +65,7 @@ struct sctstr { short sct_fill; /* gunk */ unsigned char sct_work; /* pct of civ actually working */ unsigned char sct_coastal; /* is this a coastal sector? */ - unsigned char sct_newtype; /* for changing designations */ + signed char sct_newtype; /* for changing designations */ unsigned char sct_min; /* ease of mining ore */ unsigned char sct_gmin; /* amount of gold ore */ unsigned char sct_fertil; /* fertility of soil */ diff --git a/include/trade.h b/include/trade.h index da1b5a8e..45b4dfe0 100644 --- a/include/trade.h +++ b/include/trade.h @@ -41,9 +41,11 @@ #include "types.h" struct trdstr { + /* initial part must match struct empobj */ short ef_type; natid trd_owner; short trd_uid; + /* end of part matching struct empobj */ short trd_type; short trd_unitid; long trd_price; diff --git a/include/xdump.h b/include/xdump.h new file mode 100644 index 00000000..513a71df --- /dev/null +++ b/include/xdump.h @@ -0,0 +1,59 @@ +/* + * Empire - A multi-player, client/server Internet based war game. + * Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak, + * Ken Stevens, Steve McClure + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * --- + * + * See files README, COPYING and CREDITS in the root of the source + * tree for related information and legal notices. It is expected + * that future projects/authors will amend these files as needed. + * + * --- + * + * xdump.h: Extended dumps + * + * Known contributors to this file: + * Markus Armbruster, 2008 + */ + +#ifndef XDUMP_H +#define XDUMP_H + +#include +#include "nsc.h" +#include "types.h" + +/* xdump descriptor */ +struct xdstr { + natid cnum; /* dump for this country */ + int divine; /* is this a deity dump? */ + int human; /* dump human-readable format */ + void (*pr)(char *fmt, ...); /* callback for printing dump */ +}; + +struct xdstr *xdinit(struct xdstr *, natid, int, void (*)(char *, ...)); +extern void xdhdr(struct xdstr *, char *, int); +extern void xdcolhdr(struct xdstr *, struct castr[]); +extern void xdflds(struct xdstr *, struct castr[], void *); +extern struct valstr *xdeval(struct valstr *, struct xdstr *, nsc_type, void *, ptrdiff_t, int, int); +extern char *xdprval(struct xdstr *, struct valstr *, char *); +extern void xdftr(struct xdstr *, int); +extern int xdmeta(struct xdstr *, int); +extern int xundump(FILE *, char *, int *, int); + +#endif diff --git a/man/empdump.6 b/man/empdump.6 new file mode 100644 index 00000000..3aac1b2c --- /dev/null +++ b/man/empdump.6 @@ -0,0 +1,57 @@ +.TH EMPDUMP 6 +.SH NAME +empdump \- Export/import Empire game state +.SH SYNOPSIS +.B empdump +[ +.B \-mtxhv +] +[ +.BI \-e " configfile" +] +[ +.I dump-file +] +.br +.SH DESCRIPTION +.B empdump +imports and exports game state as plain text. +.SH OPTIONS +.TP +.BI \-e " configfile" +Use game configuration in \fIconfigfile\fR. +.TP +.B \-h +Help. Print brief usage information and exit. +.TP +.TP +.B \-m +Use machine-readable format for export. Import always recognizes both +machine-readable and human-readable format. +.TP +.B \-t +Test import, don't update game state. +.TP +.B \-v +Print version information and exit. +.TP +.B \-x +Export game state to standard output. +.SH OPERANDS +.TP +.I dump-file +The file to import. +.SH "LIMITATIONS" +.B empdump +can't export player bmaps, power report, telegrams, announcements, +message of the day, no-login message and log files. Exported +floating-point values may be inexact. Importing an exported game +state may not result in identical data files; besides the loss of +floating-point precision just mentioned, coordinates are normalized, +and characters beyond a string's terminating zero in a character array +may be lost. +FIXME can't change file size, fix or document here +.SH "SEE ALSO" +\fIemp_server\fR(6). +.SH AUTHOR +Markus Armbruster diff --git a/src/lib/commands/expl.c b/src/lib/commands/expl.c index 44988339..4ed6590c 100644 --- a/src/lib/commands/expl.c +++ b/src/lib/commands/expl.c @@ -196,7 +196,7 @@ explore(void) putsect(§); return RET_FAIL; } - if (chksect.sct_type == '.') { + if (chksect.sct_type == SCT_WATER) { pr("Bridge disappeared!\n"); getsect(start.sct_x, start.sct_y, &start); start.sct_flags &= ~MOVE_IN_PROGRESS; diff --git a/src/lib/commands/miss.c b/src/lib/commands/miss.c index b60ff783..d859d564 100644 --- a/src/lib/commands/miss.c +++ b/src/lib/commands/miss.c @@ -321,7 +321,7 @@ mission(void) gp->mission = mission; gp->opx = x; gp->opy = y; - put_empobj(gp); + put_empobj(type, gp->uid, gp); } if (num == 0) { pr("No %s%s\n", ef_nameof(type), splur(num)); diff --git a/src/lib/commands/nati.c b/src/lib/commands/nati.c index 3bc9a7d2..4e42ead0 100644 --- a/src/lib/commands/nati.c +++ b/src/lib/commands/nati.c @@ -74,8 +74,7 @@ nati(void) mil = sect.sct_item[I_MILIT]; pr("%d%% eff %s at %s has %d civilian%s & %d military\n", sect.sct_effic, - (sect.sct_type == - SCT_CAPIT ? "capital" : "mountain capital"), + sect.sct_type == SCT_CAPIT ? "capital" : "mountain capital", xyas(sect.sct_x, sect.sct_y, cnum), civ, splur(civ), mil); } diff --git a/src/lib/commands/xdump.c b/src/lib/commands/xdump.c index 9e93a2f5..6dc1fe68 100644 --- a/src/lib/commands/xdump.c +++ b/src/lib/commands/xdump.c @@ -28,168 +28,17 @@ * xdump.c: Extended dump * * Known contributors to this file: - * Markus Armbruster, 2004-2007 + * Markus Armbruster, 2004-2008 */ -/* - * Dump everything under the sun - * - * Static game data (configuration): - * - Item characteristics: ichr[] - * - Land unit characteristics: lchr[] - * - Nuke characteristics: nchr[] - * - Plane characteristics: plchr[] - * - Product characteristics: pchr[] - * - Sector designation characteristics: dchr[] - * - Sector infrastructure characteristics: intrchr[] - * - Ship characteristics: mchr[] - * - News item characteristics: rpt[] - * - News page headings: page_headings[] - * - Commands: player_coms[] (TODO) - * - Update schedule: update_time[] (not really static) - * - Configuration: configkeys[] - * - * Dynamic game data: - * - Sectors: EF_SECTOR (superseding dump) - * - Land units: EF_LAND (superseding ldump) - * - Lost: EF_LOST (superseding lost) - * - Nukes: EF_NUKE (superseding ndump) - * - Planes: EF_PLANE (superseding pdump) - * - Ships: EF_SHIP (superseding sdump) - * - News: EF_NEWS - * - Treaties: EF_TREATY - * - Power: EF_POWER (TODO) - * - Nations: EF_NATION - * - Loans: EF_LOAN - * - Map: EF_MAP (TODO) - * - Bmap: EF_BMAP (TODO) - * - Market: EF_COMM - */ - -/* - * See doc/xdump for motivation, syntax, semantics, and rationale. - * Make sure to keep it up-to-date! - */ - -/* TODO don't dump stuff that's useless due to options */ - #include #include -#include #include "commands.h" #include "empobj.h" -#include "match.h" -#include "news.h" #include "optlist.h" -#include "treaty.h" #include "version.h" - -/* - * Evaluate a attribute of an object into VAL, return VAL. - * TYPE is the attribute's type. - * PTR points to the context object. - * The attribute is stored there at offset OFF + IDX * S, where S is - * its size. - * LEN is the #array elements if it is an array, else zero. - */ -static struct valstr * -xdeval(struct valstr *val, - nsc_type type, void *ptr, ptrdiff_t off, int idx, int len) -{ - val->val_type = type; - val->val_cat = NSC_OFF; - val->val_as.sym.off = off; - val->val_as.sym.len = len; - val->val_as.sym.idx = idx; - nstr_exec_val(val, player->cnum, ptr, NSC_NOTYPE); - return val; /* FIXME nstr_exec_val() should return VAL */ -} - -/* Dump VAL prefixed with SEP, return " ". */ -static char * -xdprval(struct valstr *val, char *sep) -{ - unsigned char *s, *e, *l; - - switch (val->val_type) { - case NSC_LONG: - pr("%s%ld", sep, val->val_as.lng); - break; - case NSC_DOUBLE: - pr("%s%#g", sep, val->val_as.dbl); - break; - case NSC_STRING: - s = (unsigned char *)val->val_as.str.base; - if (s) { - pr("%s\"", sep); - l = s + val->val_as.str.maxsz; - /* FIXME maxsz == INT_MAX ! */ - for (;;) { - for (e = s; - e < l && *e != '"' && *e != '\\' && isgraph(*e); - ++e) - ; - pr("%.*s", (int)(e-s), s); - if (e < l && *e) - pr("\\%03o", *e++); - else - break; - s = e; - } - pr("\""); - } else - pr("%snil", sep); - break; - default: - CANT_REACH(); - pr("0"); - } - return " "; -} - -/* - * Dump field values of a context object. - * CA[] describes fields. - * PTR points to context object. - */ -static void -xdflds(struct castr ca[], void *ptr) -{ - int i, j, n; - struct valstr val; - char *sep = ""; - - for (i = 0; ca[i].ca_name; ++i) { - if (ca[i].ca_flags & NSC_DEITY && !player->god) - continue; - if (ca[i].ca_flags & NSC_EXTRA) - continue; - n = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0; - j = 0; - do { - xdeval(&val, ca[i].ca_type, ptr, ca[i].ca_off, j, ca[i].ca_len); - sep = xdprval(&val, sep); - } while (++j < n); - } -} - -/* - * Dump header for dump NAME. - * If META, it's for the meta-data dump rather than the data dump. - */ -static void -xdhdr(char *name, int meta) -{ - pr("XDUMP %s%s %ld\n", meta ? "meta " : "", name, (long)time(NULL)); -} - -/* Dump footer for a dump that dumped N objects. */ -static void -xdftr(int n) -{ - pr("/%d\n", n); -} +#include "xdump.h" /* * Is object P of type TYPE visible to the player? @@ -265,11 +114,11 @@ xdvisible(int type, void *p) } /* - * Dump items of type TYPE selected by ARG. + * Dump items of type TYPE selected by ARG to XD. * Return RET_OK on success, RET_SYN on error. */ static int -xditem(int type, char *arg) +xditem(struct xdstr *xd, int type, char *arg) { struct castr *ca; struct nstr_item ni; @@ -283,59 +132,29 @@ xditem(int type, char *arg) if (!snxtitem(&ni, type, arg)) return RET_SYN; - xdhdr(ef_nameof(type), 0); + xdhdr(xd, ef_nameof(type), 0); n = 0; while (nxtitem(&ni, buf)) { if (!xdvisible(type, buf)) continue; ++n; - xdflds(ca, buf); - pr("\n"); + xdflds(xd, ca, buf); + xd->pr("\n"); } - xdftr(n); + xdftr(xd, n); return RET_OK; } /* - * Dump meta-data for items of type TYPE. + * Dump configkeys[] to XD. + * If META, dump meta-data rather than data. * Return RET_OK. */ static int -xdmeta(int type) -{ - struct castr *ca = ef_cadef(type); - int i; - int n = 0; - - if (!ca) - return RET_SYN; - - xdhdr(ef_nameof(type), 1); - - for (i = 0; ca[i].ca_name; i++) { - if (ca[i].ca_flags & NSC_DEITY && !player->god) - continue; - if (ca[i].ca_flags & NSC_EXTRA) - continue; - xdflds(mdchr_ca, &ca[i]); - pr("\n"); - n++; - } - - xdftr(n); - - return RET_OK; -} - -/* - * Dump configkeys[], return RET_OK. - * If META, dump meta-data rather than data. - */ -static int -xdver(int meta) +xdver(struct xdstr *xd, int meta) { static struct castr vers_ca = { NSC_STRINGY, 0, sizeof(PACKAGE_STRING), 0, "version", EF_BAD @@ -346,11 +165,11 @@ xdver(int meta) struct castr ca; struct valstr val; - xdhdr("version", meta); + xdhdr(xd, "version", meta); if (meta) { n = 0; - xdflds(mdchr_ca, &vers_ca); + xdflds(xd, mdchr_ca, &vers_ca); pr("\n"); n++; for (kp = configkeys; kp->km_key; ++kp) { @@ -361,39 +180,40 @@ xdver(int meta) ca.ca_off = 0; ca.ca_name = kp->km_key; ca.ca_table = EF_BAD; - xdflds(mdchr_ca, &ca); + xdflds(xd, mdchr_ca, &ca); pr("\n"); n++; } } - xdftr(n); + xdftr(xd, n); return RET_OK; } - xdeval(&val, vers_ca.ca_type, version, vers_ca.ca_off, 0, vers_ca.ca_len); - sep = xdprval(&val, ""); + xdeval(&val, xd, + vers_ca.ca_type, version, vers_ca.ca_off, 0, vers_ca.ca_len); + sep = xdprval(xd, &val, ""); for (kp = configkeys; kp->km_key; ++kp) { if (kp->km_type != NSC_NOTYPE && !(kp->km_flags & KM_INTERNAL)) { - xdeval(&val, kp->km_type, kp->km_data, 0, 0, 0); - sep = xdprval(&val, sep); + xdeval(&val, xd, kp->km_type, kp->km_data, 0, 0, 0); + sep = xdprval(xd, &val, sep); } } pr("\n"); - xdftr(1); + xdftr(xd, 1); return RET_OK; } -/* Experimental extended dump command */ int xdump(void) { char *p; char buf[1024]; + struct xdstr xd; + struct natstr *natp; int type; int meta = 0; - struct natstr *natp; p = getstarg(player->argp[1], "Table name, or meta? ", buf); if (p && strcmp(p, "meta") == 0) { @@ -403,19 +223,20 @@ xdump(void) if (!p || !*p) return RET_SYN; + xdinit(&xd, player->cnum, 0, pr); natp = getnatp(player->cnum); type = isdigit(p[0]) ? atoi(p) : ef_byname(p); if (type >= 0 && type < EF_MAX) { if (meta) - return xdmeta(type); + return xdmeta(&xd, type); else if ((EF_IS_GAME_STATE(type) || EF_IS_VIEW(type)) && !(natp->nat_stat == STAT_ACTIVE || player->god)) { pr("Access to table %s denied\n", ef_nameof(type)); return RET_FAIL; } else - return xditem(type, player->argp[2]); + return xditem(&xd, type, player->argp[2]); } else if (!strncmp(p, "ver", strlen(p))) { - return xdver(meta); + return xdver(&xd, meta); } return RET_SYN; diff --git a/src/lib/common/conftab.c b/src/lib/common/conftab.c index 980c710d..2848e23e 100644 --- a/src/lib/common/conftab.c +++ b/src/lib/common/conftab.c @@ -39,6 +39,7 @@ #include "optlist.h" #include "product.h" #include "prototypes.h" +#include "xdump.h" static int read_custom_table_file(char *); diff --git a/src/lib/common/file.c b/src/lib/common/file.c index ea29bb2a..f75ada44 100644 --- a/src/lib/common/file.c +++ b/src/lib/common/file.c @@ -46,8 +46,10 @@ #include "nsc.h" #include "prototypes.h" +static int ef_remap_cache(struct empfile *, int); static int fillcache(struct empfile *, int); static int do_write(struct empfile *, void *, int, int); +static void ef_blank(struct empfile *, void *, int, int); /* * Open the file-backed table TYPE (EF_SECTOR, ...). @@ -61,7 +63,7 @@ ef_open(int type, int how) { struct empfile *ep; struct flock lock; - int oflags, fd, fsiz, size; + int oflags, fd, fsiz, nslots; if (ef_check(type) < 0) return 0; @@ -73,7 +75,7 @@ ef_open(int type, int how) if (CANT_HAPPEN(ep->fd >= 0)) return 0; oflags = O_RDWR; - if (how & EFF_RDONLY) + if (how & EFF_PRIVATE) oflags = O_RDONLY; if (how & EFF_CREATE) oflags |= O_CREAT | O_TRUNC; @@ -85,7 +87,7 @@ ef_open(int type, int how) return 0; } - lock.l_type = how & EFF_RDONLY ? F_RDLCK : F_WRLCK; + lock.l_type = how & EFF_PRIVATE ? F_RDLCK : F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = lock.l_len = 0; if (fcntl(fd, F_SETLK, &lock) == -1) { @@ -116,18 +118,13 @@ ef_open(int type, int how) } } } else { - if (how & EFF_MEM) - ep->csize = ep->fids; - else - ep->csize = blksize(fd) / ep->size; - /* 0 could lead to null cache, which confuses assertions */ - if (!ep->csize) - ep->csize++; - size = ep->csize * ep->size; if (CANT_HAPPEN(ep->cache)) free(ep->cache); - ep->cache = malloc(size); - if (ep->cache == NULL) { + if (how & EFF_MEM) + nslots = ep->fids; + else + nslots = blksize(fd) / ep->size; + if (!ef_remap_cache(ep, nslots)) { logerror("Can't open %s: out of memory", ep->file); close(fd); return 0; @@ -148,11 +145,31 @@ ef_open(int type, int how) } } - /* - * Could close fd if both EFF_RDONLY and EFF_MEM, but that doesn't - * happen, so don't bother. - */ + return 1; +} +static int +ef_remap_cache(struct empfile *ep, int nslots) +{ + void *cache; + + if (CANT_HAPPEN(ep->flags & EFF_STATIC)) + return 0; + if (CANT_HAPPEN(nslots < 0)) + nslots = 0; + + /* + * Avoid zero slots, because that can lead to null cache, which + * would be interpreted as unmapped cache. + */ + if (nslots == 0) + nslots++; + cache = realloc(ep->cache, nslots * ep->size); + if (!cache) + return 0; + + ep->cache = cache; + ep->csize = nslots; return 1; } @@ -182,7 +199,8 @@ ef_close(int type) } /* - * Flush file-backed table TYPE (EF_SECTOR, ...) to disk. + * Flush table TYPE (EF_SECTOR, ...) to disk. + * Does nothing if the table is privately mapped. * Return non-zero on success, zero on failure. */ int @@ -193,6 +211,8 @@ ef_flush(int type) if (ef_check(type) < 0) return 0; ep = &empfile[type]; + if (ep->flags & EFF_PRIVATE) + return 1; /* nothing to do */ if (CANT_HAPPEN(ep->fd < 0)) return 0; /* @@ -201,7 +221,7 @@ ef_flush(int type) * allowed only with EFF_MEM. Assume the whole cash is dirty * then. */ - if (!(ep->flags & EFF_RDONLY) && (ep->flags & EFF_MEM)) + if (ep->flags & EFF_MEM) return do_write(ep, ep->cache, ep->baseid, ep->cids) >= 0; return 1; @@ -316,7 +336,8 @@ do_write(struct empfile *ep, void *buf, int id, int count) int n, ret; char *p; - if (CANT_HAPPEN(ep->fd < 0 || id < 0 || count < 0)) + if (CANT_HAPPEN(ep->fd < 0 || (ep->flags & EFF_PRIVATE) + || id < 0 || count < 0)) return -1; if (lseek(ep->fd, id * ep->size, SEEK_SET) == (off_t)-1) { @@ -344,9 +365,10 @@ do_write(struct empfile *ep, void *buf, int id, int count) } /* - * Write element ID into file-backed table TYPE from buffer FROM. + * Write element ID into table TYPE from buffer FROM. * FIXME pass buffer size! - * Write through cache straight to disk. + * If table is file-backed and not privately mapped, write through + * cache straight to disk. * Cannot write beyond the end of fully cached table (flags & EFF_MEM). * Can write at the end of partially cached table. * Return non-zero on success, zero on failure. @@ -360,12 +382,16 @@ ef_write(int type, int id, void *from) if (ef_check(type) < 0) return 0; ep = &empfile[type]; + if (CANT_HAPPEN((ep->flags & (EFF_MEM | EFF_PRIVATE)) == EFF_PRIVATE)) + return 0; if (ep->prewrite) ep->prewrite(id, from); if (CANT_HAPPEN((ep->flags & EFF_MEM) ? id >= ep->fids : id > ep->fids)) return 0; /* not implemented */ - if (do_write(ep, from, id, 1) < 0) - return 0; + if (!(ep->flags & EFF_PRIVATE)) { + if (do_write(ep, from, id, 1) < 0) + return 0; + } if (id >= ep->baseid && id < ep->baseid + ep->cids) { /* update the cache if necessary */ to = ep->cache + (id - ep->baseid) * ep->size; @@ -380,44 +406,66 @@ ef_write(int type, int id, void *from) } /* - * Extend the file-backed table TYPE by COUNT elements. + * Extend table TYPE by COUNT elements. + * Any pointers obtained from ef_ptr() become invalid. * Return non-zero on success, zero on failure. */ int ef_extend(int type, int count) { struct empfile *ep; - void *tmpobj; - int id, i, how; + char *p; + int i, id; if (ef_check(type) < 0) return 0; ep = &empfile[type]; - if (CANT_HAPPEN(ep->fd < 0 || count < 0)) + if (CANT_HAPPEN(count < 0)) return 0; - tmpobj = calloc(1, ep->size); id = ep->fids; - for (i = 0; i < count; i++) { - if (ep->init) - ep->init(id + i, tmpobj); - if (do_write(ep, tmpobj, id + i, 1) < 0) - break; - } - free(tmpobj); - if (ep->flags & EFF_MEM) { - /* FIXME lazy bastards... do this right */ - /* XXX this will cause problems if there are ef_ptrs (to the - * old allocated structure) active when we do the re-open */ - how = ep->flags & ~EFF_IMMUTABLE; - ef_close(type); - ef_open(type, how); + if (id + count > ep->csize) { + if (ep->flags & EFF_STATIC) + return 0; + if (!ef_remap_cache(ep, id + count)) + return 0; + } + p = ep->cache + id * ep->size; + ef_blank(ep, p, id, count); + if (!(ep->flags & EFF_PRIVATE)) { + if (do_write(ep, p, id, count) < 0) + return 0; + } + ep->cids += count; } else { - ep->fids += i; + /* need a buffer, steal last cache slot */ + if (ep->cids == ep->csize) + ep->cids--; + p = ep->cache + ep->cids * ep->size; + for (i = 0; i < count; i++) { + ef_blank(ep, p, id + i, 1); + if (do_write(ep, p, id + i, 1) < 0) + return 0; + } } + ep->fids += count; + return 1; +} - return i == count; +/* + * Initialize COUNT elements of EP in BUF, starting with element ID. + */ +static void +ef_blank(struct empfile *ep, void *buf, int id, int count) +{ + int i; + + memset(buf, 0, count * ep->size); + if (ep->init) { + for (i = 0; i < count; i++) + ep->init(id + i, (char *)buf + i * ep->size); + } } struct castr * diff --git a/src/lib/common/xdump.c b/src/lib/common/xdump.c new file mode 100644 index 00000000..6dc0662f --- /dev/null +++ b/src/lib/common/xdump.c @@ -0,0 +1,351 @@ +/* + * Empire - A multi-player, client/server Internet based war game. + * Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak, + * Ken Stevens, Steve McClure + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * --- + * + * See files README, COPYING and CREDITS in the root of the source + * tree for related information and legal notices. It is expected + * that future projects/authors will amend these files as needed. + * + * --- + * + * xdump.c: Extended dumps + * + * Known contributors to this file: + * Markus Armbruster, 2008 + */ + +/* + * Dump everything under the sun + * + * Static game data (configuration): + * - Item characteristics: ichr[] + * - Land unit characteristics: lchr[] + * - Nuke characteristics: nchr[] + * - Plane characteristics: plchr[] + * - Product characteristics: pchr[] + * - Sector designation characteristics: dchr[] + * - Sector infrastructure characteristics: intrchr[] + * - Ship characteristics: mchr[] + * - News item characteristics: rpt[] + * - News page headings: page_headings[] + * - Commands: player_coms[] (TODO) + * - Update schedule: update_time[] (not really static) + * - Configuration: configkeys[] + * + * Dynamic game data: + * - Sectors: EF_SECTOR (superseding dump) + * - Land units: EF_LAND (superseding ldump) + * - Lost: EF_LOST (superseding lost) + * - Nukes: EF_NUKE (superseding ndump) + * - Planes: EF_PLANE (superseding pdump) + * - Ships: EF_SHIP (superseding sdump) + * - News: EF_NEWS + * - Treaties: EF_TREATY + * - Power: EF_POWER (TODO) + * - Nations: EF_NATION + * - Loans: EF_LOAN + * - Map: EF_MAP (TODO) + * - Bmap: EF_BMAP (TODO) + * - Market: EF_COMM + */ + +/* + * See doc/xdump for motivation, syntax, semantics, and rationale. + * Make sure to keep it up-to-date! + */ + +/* TODO don't dump stuff that's useless due to options */ + +#include + +#include +#include +#include "file.h" +#include "nat.h" +#include "xdump.h" + +/* + * Initialize XD to dump for country CNUM. + * If HUMAN, dump in human-readable format. + * Dump is to be delivered through callback PR. + * Return XD. + */ +struct xdstr * +xdinit(struct xdstr *xd, natid cnum, int human, void (*pr)(char *fmt, ...)) +{ + xd->cnum = cnum; + xd->divine = getnatp(cnum)->nat_stat == STAT_GOD; + xd->human = human; + xd->pr = pr; + return xd; +} + +/* + * Evaluate a attribute of an object into VAL, return VAL. + * XD is the xdump descriptor. + * TYPE is the attribute's type. + * PTR points to the context object. + * The attribute is stored there at offset OFF + IDX * S, where S is + * its size. + * LEN is the #array elements if it is an array, else zero. + */ +struct valstr * +xdeval(struct valstr *val, struct xdstr *xd, + nsc_type type, void *ptr, ptrdiff_t off, int idx, int len) +{ + val->val_type = type; + val->val_cat = NSC_OFF; + val->val_as.sym.off = off; + val->val_as.sym.len = len; + val->val_as.sym.idx = idx; + nstr_exec_val(val, xd->cnum, ptr, NSC_NOTYPE); + return val; /* FIXME nstr_exec_val() should return VAL */ +} + +/* + * Dump string STR to XD with funny characters escaped. + * Dump at most N characters. + */ +static void +xdpresc(struct xdstr *xd, char *str, size_t n) +{ + unsigned char *s, *e, *l; + + s = (unsigned char *)str; + l = s + n; + for (;;) { + for (e = s; + e < l && *e != '"' && *e != '\\' && isgraph(*e); + ++e) + ; + xd->pr("%.*s", (int)(e-s), s); + if (e < l && *e) + xd->pr("\\%03o", *e++); + else + break; + s = e; + } +} + +/* Dump VAL in machine readable format, prefixed with SEP, return " ". */ +static char * +xdprval_nosym(struct xdstr *xd, struct valstr *val, char *sep) +{ + switch (val->val_type) { + case NSC_LONG: + xd->pr("%s%ld", sep, val->val_as.lng); + break; + case NSC_DOUBLE: +#if 1 /* TODO have %a */ + xd->pr("%s%a", sep, val->val_as.dbl); +#else + xd->pr("%s%#g", sep, val->val_as.dbl); +#endif + break; + case NSC_STRING: + if (val->val_as.str.base) { + xd->pr("%s\"", sep); + xdpresc(xd, val->val_as.str.base, val->val_as.str.maxsz); + xd->pr("\""); + } else + xd->pr("%snil", sep); + break; + default: + CANT_REACH(); + xd->pr("0"); + } + return " "; +} + +/* + * Dump symbol with value KEY from symbol table TYPE to XD. + * Prefix with SEP, return " ". + */ +static char * +xdprsym(struct xdstr *xd, int key, int type, char *sep) +{ + char *sym = symbol_by_value(key, ef_ptr(type, 0)); + + if (CANT_HAPPEN(!sym)) + xd->pr("%s%ld", sep, key); + else { + xd->pr("%s", sep); + xdpresc(xd, sym, INT_MAX); + } + return " "; +} + +/* + * Dump VAL prefixed with SEP, return " ". + * CA describes the field from which the value was fetched. + */ +static char * +xdprval_sym(struct xdstr *xd, struct valstr *val, struct castr *ca, char *sep) +{ + unsigned long bit; + struct castr *ca_sym; + char *sym; + + if (xd->human && val->val_type == NSC_LONG && ca->ca_table != EF_BAD) { + ca_sym = ef_cadef(ca->ca_table); + if (ca_sym != symbol_ca) + ; + else if (ca->ca_flags & NSC_BITS) { + xd->pr("%s(", sep); + sep = ""; + for (bit = 1; bit; bit <<= 1) { + if (bit & val->val_as.lng) + sep = xdprsym(xd, bit, ca->ca_table, sep); + } + xd->pr(")"); + return " "; + } else + return xdprsym(xd, val->val_as.lng, ca->ca_table, sep); + } + + return xdprval_nosym(xd, val, sep); +} + +/* + * Dump VAL prefixed with SEP, return " ". + * XD must not be human-readable. + */ +char * +xdprval(struct xdstr *xd, struct valstr *val, char *sep) +{ + CANT_HAPPEN(xd->human); + return xdprval_nosym(xd, val, sep); +} + +/* + * Dump field values of a context object to XD. + * CA[] describes fields. + * PTR points to context object. + */ +void +xdflds(struct xdstr *xd, struct castr ca[], void *ptr) +{ + int i, j, n; + struct valstr val; + char *sep = ""; + + for (i = 0; ca[i].ca_name; ++i) { + if (ca[i].ca_flags & NSC_DEITY && !xd->divine) + continue; + if (ca[i].ca_flags & NSC_EXTRA) + continue; + n = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0; + j = 0; + do { + xdeval(&val, xd, + ca[i].ca_type, ptr, ca[i].ca_off, j, ca[i].ca_len); + sep = xdprval_sym(xd, &val, &ca[i], sep); + } while (++j < n); + } +} + +/* + * Dump header for dump NAME to XD. + * If META, it's for the meta-data dump rather than the data dump. + */ +void +xdhdr(struct xdstr *xd, char *name, int meta) +{ + if (xd->human) { + xd->pr("config %s\n", name); + } else + xd->pr("XDUMP %s%s %ld\n", + meta ? "meta " : "", name, (long)time(NULL)); +} + +/* + * Dump column header to XD. + * CA[] describes fields. + * Does nothing unless XD is human-readable. + */ +void +xdcolhdr(struct xdstr *xd, struct castr ca[]) +{ + int i, j, n; + char *sep = ""; + + if (!xd->human) + return; + + for (i = 0; ca[i].ca_name; ++i) { + if (ca[i].ca_flags & NSC_DEITY && !xd->divine) + continue; + if (ca[i].ca_flags & NSC_EXTRA) + continue; + n = ca[i].ca_type != NSC_STRINGY ? ca[i].ca_len : 0; + if (n) { + for (j = 0; j < n; j++) { + xd->pr("%s%s(%d)",sep, ca[i].ca_name, j); + sep = " "; + } + } else { + xd->pr("%s%s", sep, ca[i].ca_name); + sep = " "; + } + } + xd->pr("\n"); +} + +/* Dump footer for a dump that dumped N objects to XD. */ +void +xdftr(struct xdstr *xd, int n) +{ + if (xd->human) + xd->pr("/config\n"); + else + xd->pr("/%d\n", n); +} + +/* + * Dump meta-data for items of type TYPE to XD. + * Return RET_OK. + */ +int +xdmeta(struct xdstr *xd, int type) +{ + struct castr *ca = ef_cadef(type); + int i; + int n = 0; + + if (!ca) + return RET_SYN; + + xdhdr(xd, ef_nameof(type), 1); + xdcolhdr(xd, ca); + + for (i = 0; ca[i].ca_name; i++) { + if (ca[i].ca_flags & NSC_DEITY && !xd->divine) + continue; + if (ca[i].ca_flags & NSC_EXTRA) + continue; + xdflds(xd, mdchr_ca, &ca[i]); + xd->pr("\n"); + n++; + } + + xdftr(xd, n); + + return RET_OK; +} diff --git a/src/lib/common/xundump.c b/src/lib/common/xundump.c index 739bddbc..76de98ca 100644 --- a/src/lib/common/xundump.c +++ b/src/lib/common/xundump.c @@ -56,12 +56,14 @@ #include #include #include +#include +#include #include #include "file.h" #include "match.h" #include "nsc.h" #include "optlist.h" -#include "prototypes.h" +#include "xdump.h" static char *fname; /* Name of file being read */ static int lineno; /* Current line number */ @@ -130,21 +132,6 @@ skipfs(FILE *fp) return ch; } -/* - * Read an identifier from FP into BUF. - * BUF must have space for 1024 characters. - * Return number of characters read on success, -1 on failure. - */ -static int -getid(FILE *fp, char *buf) -{ - int n; - if (fscanf(fp, "%1023[^#()<>=#\" \t\n]%n", buf, &n) != 1 - || !isalpha(buf[0])) - return -1; - return n; -} - /* * Decode escape sequences in BUF. * Return BUF on success, null pointer on failure. @@ -170,6 +157,22 @@ xuesc(char *buf) return buf; } +/* + * Read an identifier from FP into BUF. + * BUF must have space for 1024 characters. + * Return number of characters read on success, -1 on failure. + */ +static int +getid(FILE *fp, char *buf) +{ + int n; + if (fscanf(fp, "%1023[^\"#()<>= \t\n]%n", buf, &n) != 1 + || !isalpha(buf[0])) + return -1; + xuesc(buf); + return n; +} + /* * Try to read a field name from FP. * I is the field number, counting from zero. @@ -479,17 +482,20 @@ getobj(void) int need_sentinel = !EF_IS_GAME_STATE(cur_type); if (!cur_obj) { - cur_obj_is_blank = cur_id >= ep->fids; + cur_obj_is_blank = cur_id >= ep->fids - !!need_sentinel; if (cur_obj_is_blank) { - /* TODO grow cache (and possibly file) unless EFF_STATIC */ - if (cur_id < ep->csize - !!need_sentinel) - ep->cids = ep->fids = cur_id + 1; - /* else: ef_ptr() will fail */ - } - cur_obj = ef_ptr(cur_type, cur_id); - if (!cur_obj) - gripe("Can't put ID %d into table %s, it holds only 0..%d.", - cur_id, ep->name, ep->fids - 1); + if (ef_ensure_space(cur_type, cur_id + !!need_sentinel, 1)) + cur_obj = ef_ptr(cur_type, cur_id); + /* FIXME diagnose out of dynamic memory vs. static table full */ + if (!cur_obj) + gripe("Can't put ID %d into table %s, it holds only 0..%d.", + cur_id, ep->name, ep->fids - !!need_sentinel - 1); + } else + cur_obj = ef_ptr(cur_type, cur_id); + /* TODO find a less ugly solution */ + if (EF_IS_GAME_STATE(cur_type) && cur_type != EF_POWER && + cur_type != EF_MAP && cur_type != EF_BMAP) + *((short *)cur_obj) = cur_type; } return cur_obj; @@ -958,8 +964,12 @@ xubody(FILE *fp) { struct empfile *ep = &empfile[cur_type]; int need_sentinel = !EF_IS_GAME_STATE(cur_type); + int old_maxid = ep->fids; int i, maxid, ch; + if (old_maxid == 0 && need_sentinel) + ef_ensure_space(cur_type, 0, 1); + maxid = 0; for (i = 0;; ++i) { while ((ch = skipfs(fp)) == '\n') @@ -974,6 +984,12 @@ xubody(FILE *fp) maxid = MAX(maxid, cur_id + 1); } + if (maxid >= old_maxid && need_sentinel) { + /* appended a sentinel, strip it off */ + ep->fids--; + ep->cids--; + } + if (CANT_HAPPEN(maxid > ep->fids)) maxid = ep->fids; if (maxid < ep->fids) { @@ -988,11 +1004,5 @@ xubody(FILE *fp) ef_nameof(cur_type), ep->fids, maxid); } - if (need_sentinel) { - if (CANT_HAPPEN(maxid >= ep->csize)) - return gripe("No space for sentinel"); - memset(ep->cache + ep->size * maxid, 0, ep->size); - } - return i; } diff --git a/src/lib/global/file.c b/src/lib/global/file.c index 51e28dc1..0a0031a6 100644 --- a/src/lib/global/file.c +++ b/src/lib/global/file.c @@ -85,7 +85,7 @@ SZ((array)), 0, SZ((array)) - 1, SZ((array)) - 1, -1, NULL, NULL, NULL /* Common configuration table flags */ -#define EFF_CFG (EFF_RDONLY | EFF_MEM | EFF_STATIC) +#define EFF_CFG (EFF_PRIVATE | EFF_MEM | EFF_STATIC) struct empfile empfile[] = { /* @@ -95,7 +95,7 @@ struct empfile empfile[] = { * bits of flags get their final value. * If flags & EFF_STATIC, the cache is mapped here, and members * cache, csize get their final value. - * Members baseid, cids, fids and the EFF_MEM|EFF_RDONLY bits of + * Members baseid, cids, fids and the EFF_MEM|EFF_PRIVATE bits of * flags are initialized according the initial cache contents. * Member fd is initialized to -1. * Members init, postread, prewrite get initialized to NULL, but @@ -131,7 +131,7 @@ struct empfile empfile[] = { {EF_TREATY, "treaty", "treaty", treaty_ca, UNMAPPED_CACHE(struct trtstr, 0)}, {EF_TRADE, "trade", "trade", trade_ca, - UNMAPPED_CACHE(struct trdstr, 0)}, + UNMAPPED_CACHE(struct trdstr, EFF_OWNER)}, {EF_POWER, "pow", "power", NULL, UNMAPPED_CACHE(struct powstr, 0)}, {EF_NATION, "nat", "nation", nat_ca, @@ -143,7 +143,7 @@ struct empfile empfile[] = { {EF_BMAP, "bmap", "bmap", NULL, 0, 0, NULL, 0, 0, 0, 0, -1, NULL, NULL, NULL}, {EF_COMM, "commodity", "commodity", commodity_ca, - UNMAPPED_CACHE(struct comstr, 0)}, + UNMAPPED_CACHE(struct comstr, EFF_OWNER)}, {EF_LOST, "lost", "lostitems", lost_ca, UNMAPPED_CACHE(struct loststr, EFF_OWNER)}, {EF_REALM, "realm", "realms", realm_ca, @@ -214,7 +214,8 @@ struct empfile empfile[] = { SYMTAB(EF_TREATY_FLAGS, "treaty-flags", treaty_flags), /* Views */ - {EF_COUNTRY, "country", NULL, cou_ca, UNMAPPED_CACHE(struct natstr, 0)}, + {EF_COUNTRY, "country", NULL, cou_ca, + UNMAPPED_CACHE(struct natstr, EFF_OWNER)}, /* Sentinel */ {EF_BAD, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, 0, -1, NULL, NULL, NULL}, diff --git a/src/lib/global/nsc.c b/src/lib/global/nsc.c index a0889763..b23987e3 100644 --- a/src/lib/global/nsc.c +++ b/src/lib/global/nsc.c @@ -108,7 +108,7 @@ struct castr sect_ca[] = { {NSC_SHORT, NSC_DEITY, 0, fldoff(sctstr, sct_elev), "elev", EF_BAD}, {NSC_XCOORD, NSC_CONST, 0, fldoff(sctstr, sct_x), "xloc", EF_BAD}, {NSC_YCOORD, NSC_CONST, 0, fldoff(sctstr, sct_y), "yloc", EF_BAD}, - {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_type), "des", EF_SECTOR_CHR}, + {NSC_CHAR, 0, 0, fldoff(sctstr, sct_type), "des", EF_SECTOR_CHR}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_effic), "effic", EF_BAD}, {NSC_SHORT, 0, 0, fldoff(sctstr, sct_mobil), "mobil", EF_BAD}, {NSC_UCHAR, NSC_DEITY, 0, fldoff(sctstr, sct_loyal), "loyal", EF_BAD}, @@ -123,7 +123,7 @@ struct castr sect_ca[] = { {NSC_SHORT, 0, 0, fldoff(sctstr, sct_avail), "avail", EF_BAD}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_work), "work", EF_BAD}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_coastal), "coastal", EF_BAD}, - {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_newtype), "newdes", EF_SECTOR_CHR}, + {NSC_CHAR, 0, 0, fldoff(sctstr, sct_newtype), "newdes", EF_SECTOR_CHR}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_min), "min", EF_BAD}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_gmin), "gold", EF_BAD}, {NSC_UCHAR, 0, 0, fldoff(sctstr, sct_fertil), "fert", EF_BAD}, @@ -542,6 +542,8 @@ struct castr realm_ca[] = { {NSC_SHORT, 0, 0, fldoff(realmstr, r_xh), "xh", EF_BAD}, {NSC_SHORT, 0, 0, fldoff(realmstr, r_yl), "yl", EF_BAD}, {NSC_SHORT, 0, 0, fldoff(realmstr, r_yh), "yh", EF_BAD}, + {NSC_TIME, NSC_EXTRA, 0, fldoff(realmstr, r_timestamp), "timestamp", + EF_BAD}, {NSC_NOTYPE, 0, 0, 0, NULL, EF_BAD} }; diff --git a/src/lib/subs/empobj.c b/src/lib/subs/empobj.c index 35a18d07..3fb59adf 100644 --- a/src/lib/subs/empobj.c +++ b/src/lib/subs/empobj.c @@ -57,30 +57,6 @@ obj_nameof(struct empobj *gp) return "The Beast #666"; } -struct empobj * -get_empobjp(int type, int id) -{ - if (CANT_HAPPEN(type == EF_SECTOR || type == EF_BAD)) - return NULL; - return ef_ptr(type, id); -} - -int -put_empobj(struct empobj *gp) -{ - switch (gp->ef_type) - { - case EF_SECTOR: - return ef_write(gp->ef_type, sctoff(gp->x, gp->y), gp); - case EF_NATION: - case EF_BMAP: - case EF_MAP: - return ef_write(gp->ef_type, gp->own, gp); - default: - return ef_write(gp->ef_type, gp->uid, gp); - } -} - struct empobj_chr * get_empobj_chr(struct empobj *gp) { diff --git a/src/lib/subs/maps.c b/src/lib/subs/maps.c index b60d5d67..6b2c6e36 100644 --- a/src/lib/subs/maps.c +++ b/src/lib/subs/maps.c @@ -54,7 +54,8 @@ #include "xy.h" static int bmnxtsct(struct nstr_sect *); -static char map_char(unsigned char type, natid own, int owner_or_god); +static char map_char(int, natid, int); +static int unit_map(int, int, struct nstr_sect *, char *); int do_map(int bmap, int unit_type, char *arg, char *map_flags_arg) @@ -330,7 +331,7 @@ bmnxtsct(struct nstr_sect *np) * If OWNER_OR_GOD, the map is for the sector's owner or a deity. */ static char -map_char(unsigned char type, natid own, int owner_or_god) +map_char(int type, natid own, int owner_or_god) { if (CANT_HAPPEN(type > SCT_TYPE_MAX || !dchr[type].d_mnem)) return '?'; @@ -341,29 +342,30 @@ map_char(unsigned char type, natid own, int owner_or_god) return '?'; } -int +static int unit_map(int unit_type, int uid, struct nstr_sect *nsp, char *originp) { - struct empobj *gp; + union empobj_storage unit; struct range range; char *name; - gp = get_empobjp(unit_type, uid); - if (!gp || (gp->own != player->cnum && !player->god) || gp->own == 0) + if (!get_empobj(unit_type, uid, &unit)) + return RET_FAIL; + if (!player->owner || unit.gen.own == 0) return RET_FAIL; if (unit_type == EF_NUKE) *originp = 'n'; else { - if ((name = emp_obj_chr_name(gp)) == NULL) + if ((name = emp_obj_chr_name(&unit.gen)) == NULL) return RET_FAIL; *originp = *name; } - range.lx = xnorm(gp->x - 10); - range.hx = xnorm(gp->x + 11); - range.ly = ynorm(gp->y - 5); - range.hy = ynorm(gp->y + 6); + range.lx = xnorm(unit.gen.x - 10); + range.hx = xnorm(unit.gen.x + 11); + range.ly = ynorm(unit.gen.y - 5); + range.hy = ynorm(unit.gen.y + 6); xysize_range(&range); snxtsct_area(nsp, &range); return RET_OK; diff --git a/src/lib/subs/unitsub.c b/src/lib/subs/unitsub.c index ebc341c5..cc895d89 100644 --- a/src/lib/subs/unitsub.c +++ b/src/lib/subs/unitsub.c @@ -102,23 +102,24 @@ unit_put(struct emp_qelem *list, natid actor) struct emp_qelem *qp; struct emp_qelem *newqp; struct ulist *ulp; + struct empobj *unit; qp = list->q_back; while (qp != list) { ulp = (struct ulist *)qp; + unit = &ulp->unit.gen; if (actor) { - mpr(actor, "%s stopped at %s\n", obj_nameof(&ulp->unit.gen), - xyas(ulp->unit.gen.x, ulp->unit.gen.y, - ulp->unit.gen.own)); - if (ulp->unit.ef_type == EF_LAND) { + mpr(actor, "%s stopped at %s\n", obj_nameof(unit), + xyas(unit->x, unit->y, unit->own)); + if (unit->ef_type == EF_LAND) { if (ulp->mobil < -127) ulp->mobil = -127; - ulp->unit.land.lnd_mobil = ulp->mobil; + unit->mobil = ulp->mobil; } } - if (ulp->unit.ef_type == EF_SHIP) - ulp->unit.ship.shp_mobil = (int)ulp->mobil; - put_empobj(&ulp->unit.gen); + if (unit->ef_type == EF_SHIP) + unit->mobil = (int)ulp->mobil; + put_empobj(unit->ef_type, unit->uid, unit); newqp = qp->q_back; emp_remque(qp); free(qp); diff --git a/src/util/empdump.c b/src/util/empdump.c new file mode 100644 index 00000000..5f7e2441 --- /dev/null +++ b/src/util/empdump.c @@ -0,0 +1,444 @@ +/* + * Empire - A multi-player, client/server Internet based war game. + * Copyright (C) 1986-2008, Dave Pare, Jeff Bailey, Thomas Ruschak, + * Ken Stevens, Steve McClure + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * --- + * + * See files README, COPYING and CREDITS in the root of the source + * tree for related information and legal notices. It is expected + * that future projects/authors will amend these files as needed. + * + * --- + * + * empdump.c: Export/import Empire game state + * + * Known contributors to this file: + * Markus Armbruster, 2008 + */ + +#include + +#include +#include +#include +#include +#include +#include "file.h" +#include "optlist.h" +#include "prototypes.h" +#include "version.h" +#include "xdump.h" + +static void dump_table(int, int); +static void shp_fixup(void); +static void pln_fixup(void); +static void lnd_fixup(void); +static void nuk_fixup(void); + +int +main(int argc, char *argv[]) +{ + char *config_file = NULL; + char *import = NULL; + int export = 0; + int private = 0; + int human = 1; + int opt, i, lineno, type; + FILE *impf; + int dirty[EF_MAX]; + + while ((opt = getopt(argc, argv, "e:mtxhv")) != EOF) { + switch (opt) { + case 'e': + config_file = optarg; + break; + case 'h': + printf("Usage: %s [OPTION]... [DUMP-FILE]\n" + " -e CONFIG-FILE configuration file\n" + " (default %s)\n" + " -m use machine-readable format\n" + " -t test import, don't update game state\n" + " -x export to standard output\n" + " -h display this help and exit\n" + " -v display version information and exit\n", + argv[0], dflt_econfig); + exit(0); + case 'm': + human = 0; + break; + case 't': + private = EFF_PRIVATE; + break; + case 'x': + export = 1; + break; + case 'v': + printf("%s\n\n%s", version, legal); + exit(0); + default: + fprintf(stderr, "Try -h for help.\n"); + exit(1); + } + } + + if (argv[optind]) + import = argv[optind++]; + + if (import) { + impf = fopen(import, "r"); + if (!impf) { + fprintf(stderr, "Cant open %s for reading (%s)\n", + import, strerror(errno)); + exit(1); + } + } else + private = EFF_PRIVATE; + + /* read configuration & initialize */ + empfile_init(); + if (emp_config(config_file) < 0) + exit(1); + empfile_fixup(); + nsc_init(); + if (read_builtin_tables() < 0) + exit(1); + if (read_custom_tables() < 0) + exit(1); + if (chdir(gamedir)) { + fprintf(stderr, "Can't chdir to %s (%s)\n", + gamedir, strerror(errno)); + exit(1); + } + global_init(); + + for (i = 0; i < EF_MAX; i++) { + if (!EF_IS_GAME_STATE(i)) + continue; + if (!ef_open(i, EFF_MEM | private)) + exit(1); + } + + /* import from IMPORT */ + memset(dirty, 0, sizeof(dirty)); + if (import) { + lineno = 1; + while ((type = xundump(impf, import, &lineno, EF_BAD)) >= 0) + dirty[type] = 1; + if (type == EF_BAD) + exit(1); + shp_fixup(); + pln_fixup(); + lnd_fixup(); + nuk_fixup(); + } + + if (ef_verify() < 0) + exit(1); + + /* export to stdout */ + if (export) { + for (i = 0; i < EF_MAX; i++) { + if (!EF_IS_GAME_STATE(i)) + continue; + dump_table(i, human); + } + } + + /* write out imported data */ + for (i = 0; i < EF_MAX; i++) { + if (!EF_IS_GAME_STATE(i)) + continue; + if (!private && dirty[i]) { + if (!ef_close(i)) + exit(1); + } + } + + return 0; +} + +static void +printf_wrapper(char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +static void +dump_table(int type, int human) +{ + struct xdstr xd; + struct castr *ca; + int i; + void *p; + + ca = ef_cadef(type); + if (!ca) + return; + + xdinit(&xd, 0, human, printf_wrapper); + xdhdr(&xd, ef_nameof(type), 0); + xdcolhdr(&xd, ca); + for (i = 0; (p = ef_ptr(type, i)); i++) { + xdflds(&xd, ca, p); + printf("\n"); + } + xdftr(&xd, i); +} + + +/* FIXME remove need for this */ + +#include +#include "ship.h" +#include "plane.h" +#include "land.h" +#include "nuke.h" + +static int fit_plane_on_ship(struct plnstr *, struct shpstr *); +static int fit_plane_on_land(struct plnstr *, struct lndstr *); + +static void +shp_fixup(void) +{ + int i; + struct shpstr *sp; + + for (i = 0; (sp = ef_ptr(EF_SHIP, i)); i++) { + if (!sp->shp_own) + continue; + shp_set_tech(sp, sp->shp_tech); + } +} + +static void +pln_fixup(void) +{ + int i; + struct plnstr *pp; + struct shpstr *csp; + struct lndstr *clp; + + for (i = 0; (pp = ef_ptr(EF_PLANE, i)); i++) { + if (!pp->pln_own) + continue; + pln_set_tech(pp, pp->pln_tech); + csp = ef_ptr(EF_SHIP, pp->pln_ship); + clp = ef_ptr(EF_LAND, pp->pln_land); + if (csp) + fit_plane_on_ship(pp, csp); + else if (clp) + fit_plane_on_land(pp, clp); + } +} + +static void +lnd_fixup(void) +{ + int i; + struct lndstr *lp; + struct shpstr *csp; + struct lndstr *clp; + + for (i = 0; (lp = ef_ptr(EF_LAND, i)); i++) { + if (!lp->lnd_own) + continue; + lnd_set_tech(lp, lp->lnd_tech); + csp = ef_ptr(EF_SHIP, lp->lnd_ship); + clp = ef_ptr(EF_LAND, lp->lnd_land); + if (csp) + csp->shp_nland++; + else if (clp) + clp->lnd_nland++; + } +} + +static void +nuk_fixup(void) +{ + int i; + struct nukstr *np; + struct plnstr *cpp; + + for (i = 0; (np = ef_ptr(EF_NUKE, i)); i++) { + if (!np->nuk_own) + continue; + cpp = ef_ptr(EF_PLANE, np->nuk_plane); + if (cpp) + cpp->pln_nuketype = np->nuk_type; + } +} + +/* FIXME don't copy this from src/lib/subs/???sub.c */ + +/* + * Set SP's tech to TLEV along with everything else that depends on it. + */ +void +shp_set_tech(struct shpstr *sp, int tlev) +{ + struct mchrstr *mcp = mchr + sp->shp_type; + int tech_diff = tlev - mcp->m_tech; + + if (CANT_HAPPEN(tech_diff < 0)) { + tlev -= tech_diff; + tech_diff = 0; + } + + sp->shp_tech = tlev; + sp->shp_armor = (short)SHP_DEF(mcp->m_armor, tech_diff); + sp->shp_speed = (short)SHP_SPD(mcp->m_speed, tech_diff); + sp->shp_visib = (short)SHP_VIS(mcp->m_visib, tech_diff); + sp->shp_frnge = (short)SHP_RNG(mcp->m_frnge, tech_diff); + sp->shp_glim = (short)SHP_FIR(mcp->m_glim, tech_diff); +} + +/* + * Fit a plane of PP's type on ship SP. + * Adjust SP's plane counters. + * Updating the plane accordingly is the caller's job. + * Return whether it fits. + */ +static int +fit_plane_on_ship(struct plnstr *pp, struct shpstr *sp) +{ + struct plchrstr *pcp = plchr + pp->pln_type; + struct mchrstr *mcp = mchr + sp->shp_type; + int wanted; + + if (pcp->pl_flags & P_K) { + /* chopper, try chopper slot first */ + if (sp->shp_nchoppers < mcp->m_nchoppers) + return ++sp->shp_nchoppers; + /* else try plane slot */ + wanted = M_FLY; + } else if (pcp->pl_flags & P_E) { + /* x-light, try x-light slot first */ + if (sp->shp_nxlight < mcp->m_nxlight) + return ++sp->shp_nxlight; + /* else try plane slot */ + wanted = M_MSL | M_FLY; + } else if (!(pcp->pl_flags & P_L)) { + /* not light, no go */ + wanted = 0; + } else if (pcp->pl_flags & P_M) { + /* missile, use plane slot */ + wanted = M_MSL | M_FLY; + } else { + /* fixed-wing plane, use plane slot */ + wanted = M_FLY; + } + + if ((mcp->m_flags & wanted) == 0) + return 0; /* ship not capable */ + + if (sp->shp_nplane < mcp->m_nplanes) + return ++sp->shp_nplane; + + return 0; +} + +/* + * Fit a plane of PP's type on land unit LP. + * Adjust LP's plane counters. + * Updating the plane accordingly is the caller's job. + * Return whether it fits. + */ +static int +fit_plane_on_land(struct plnstr *pp, struct lndstr *lp) +{ + struct plchrstr *pcp = plchr + pp->pln_type; + struct lchrstr *lcp = lchr + lp->lnd_type; + + if ((pcp->pl_flags & P_E) && lp->lnd_nxlight < lcp->l_nxlight) + return ++lp->lnd_nxlight; + + return 0; +} + +/* + * Set PP's tech to TLEV along with everything else that depends on it. + */ +void +pln_set_tech(struct plnstr *pp, int tlev) +{ + struct plchrstr *pcp = plchr + pp->pln_type; + int tech_diff = tlev - pcp->pl_tech; + int limited_range = pp->pln_range < pp->pln_range_max; + + if (CANT_HAPPEN(tech_diff < 0)) { + tlev -= tech_diff; + tech_diff = 0; + } + + pp->pln_tech = tlev; + pp->pln_att = PLN_ATTDEF(pcp->pl_att, tech_diff); + pp->pln_def = PLN_ATTDEF(pcp->pl_def, tech_diff); + pp->pln_acc = PLN_ACC(pcp->pl_acc, tech_diff); + pp->pln_range_max = PLN_RAN(pcp->pl_range, tech_diff); + pp->pln_load = PLN_LOAD(pcp->pl_load, tech_diff); + + if (!limited_range || pp->pln_range > pp->pln_range_max) + pp->pln_range = pp->pln_range_max; +} + +/* + * Set LP's tech to TLEV along with everything else that depends on it. + */ +void +lnd_set_tech(struct lndstr *lp, int tlev) +{ + struct lchrstr *lcp = lchr + lp->lnd_type; + int tech_diff = tlev - lcp->l_tech; + + if (CANT_HAPPEN(tech_diff < 0)) { + tlev -= tech_diff; + tech_diff = 0; + } + + lp->lnd_tech = tlev; + lp->lnd_att = (float)LND_ATTDEF(lcp->l_att, tech_diff); + lp->lnd_def = (float)LND_ATTDEF(lcp->l_def, tech_diff); + lp->lnd_vul = (int)LND_VUL(lcp->l_vul, tech_diff); + lp->lnd_spd = (int)LND_SPD(lcp->l_spd, tech_diff); + lp->lnd_vis = (int)LND_VIS(lcp->l_vis, tech_diff); + lp->lnd_spy = (int)LND_SPY(lcp->l_spy, tech_diff); + lp->lnd_rad = (int)LND_RAD(lcp->l_rad, tech_diff); + lp->lnd_frg = (int)LND_FRG(lcp->l_frg, tech_diff); + lp->lnd_acc = (int)LND_ACC(lcp->l_acc, tech_diff); + lp->lnd_dam = (int)LND_DAM(lcp->l_dam, tech_diff); + lp->lnd_ammo = (int)LND_AMM(lcp->l_ammo, tech_diff); + lp->lnd_aaf = (int)LND_AAF(lcp->l_aaf, tech_diff); + lp->lnd_fuelc = (int)LND_FC(lcp->l_fuelc, tech_diff); + lp->lnd_fuelu = (int)LND_FU(lcp->l_fuelu, tech_diff); + lp->lnd_maxlight = (int)LND_XPL(lcp->l_nxlight, tech_diff); + lp->lnd_maxland = (int)LND_MXL(lcp->l_nland, tech_diff); +} + +/* FIXME don't copy this from src/lib/update/nat.c */ + +double +logx(double d, double base) +{ + if (base == 1.0) + return d; + return log10(d) / log10(base); +}