/* Interface-independent natural language handling for Xconq. Copyright (C) 1987-1989, 1991-2000 Stanley T. Shebs. Xconq 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, or (at your option) any later version. See the file COPYING. */ /* This file should be entirely replaced for non-English Xconq. */ /* (One way to do this would be to call this file "nlang-en.c", then symlink this or nlang-fr.c, etc to nlang.c when configuring; similarly for help.c.) */ /* (A better way to would be to run all these through a dispatch vector, then each player could get messages and such in a different language.) */ #include "conq.h" #include "kernel.h" static void notify_combat(Unit *unit, Unit *atker, char *str); static int pattern_matches_combat(Obj *pattern, Unit *unit, Unit *unit2); static void combat_desc_from_list(Side *side, Obj *lis, Unit *unit, Unit *unit2, char *str, char *buf); static void init_calendar(void); static void parse_date_step_range(Obj *form); static void maybe_mention_date(Side *side); static int gain_count(Side *side, int u, int r); static int loss_count(Side *side, int u, int r); static int atkstats(Side *side, int a, int d); static int hitstats(Side *side, int a, int d); static void pad_out(char *buf, int n); static char *past_unit_handle(Side *side, PastUnit *past_unit); static char *short_side_title_with_adjective(Side *side, char *adjective); static char *tmpnbuf; static char *tmpdbuf; static char *pluralbuf; /* Short names of directions. */ char *dirnames[] = DIRNAMES; char *unitbuf = NULL; char *past_unitbuf = NULL; static char *side_short_title = NULL; static char *gain_reason_names[] = { "Ini", "Bld", "Cap", "Oth" }; static char *loss_reason_names[] = { "Cbt", "Cap", "Stv", "Acc", "Dis", "Oth" }; /* Calendar handling. */ typedef enum { cal_unknown, cal_number, cal_usual } CalendarType; static CalendarType calendar_type = cal_unknown; typedef enum { ds_second, ds_minute, ds_hour, ds_day, ds_week, ds_month, ds_season, ds_year } UsualDateStepType; typedef struct a_usualdate { int second; int minute; int hour; int day; int month; int year; } UsualDate; typedef struct a_usualdatesteprange { int turn_start; int turn_end; UsualDateStepType step_type; int step_size; } UsualDateStepRange; static char *usual_date_string(int date); static void parse_usual_date(char *datestr, int range, UsualDate *udate); static int turn_initial; static UsualDateStepRange date_step_ranges[20]; static int num_date_step_ranges; static char *months[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "???" }; static short monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0 }; static char *seasons[] = { "Win", "Spr", "Sum", "Aut" }; static UsualDate *usual_initial; char *datebuf; char *turn_name; char *featurebuf; /* This is the number of types to mention by name; any others will just be included in the count of missing images. */ #define NUMTOLIST 5 static char *missinglist; /* This array allows for counting up to 4 classes of missing images. */ static int missing[4]; static int totlisted = 0; /* Initialize things. Note that this happens before any game is loaded, so can't do game-specific init here. */ void init_nlang(void) { if (tmpnbuf == NULL) tmpnbuf = xmalloc(BUFSIZE); if (tmpdbuf == NULL) tmpdbuf = xmalloc(BUFSIZE); if (pluralbuf == NULL) pluralbuf = xmalloc(BUFSIZE); if (datebuf == NULL) datebuf = xmalloc(BUFSIZE); } /* Send a message to everybody who's got a screen. */ void notify_all(char *fmt, ...) { va_list ap; Side *side; for_all_sides(side) { if (active_display(side)) { maybe_mention_date(side); va_start(ap, fmt); vsprintf(tmpnbuf, fmt, ap); va_end(ap); /* Always capitalize first char of notice. */ capitalize(tmpnbuf); low_notify(side, tmpnbuf); } } } void notify(Side *side, char *fmt, ...) { va_list ap; if (!active_display(side)) return; maybe_mention_date(side); va_start(ap, fmt); vsprintf(tmpnbuf, fmt, ap); va_end(ap); /* Always capitalize first char of notice. */ capitalize(tmpnbuf); low_notify(side, tmpnbuf); } void vnotify(Side *side, char *fmt, va_list ap) { if (!active_display(side)) return; maybe_mention_date(side); vsprintf(tmpnbuf, fmt, ap); /* Always capitalize first char of notice. */ capitalize(tmpnbuf); low_notify(side, tmpnbuf); } static void maybe_mention_date(Side *side) { /* Note that last_notice_date defaults to 0, so this means that any turn 0 notices will not have a date prepended (which is good). */ if (g_turn() != side->last_notice_date) { sprintf(tmpdbuf, "%s:", absolute_date_string(g_turn())); low_notify(side, tmpdbuf); side->last_notice_date = g_turn(); } } /* Pad a given buffer with blanks out to the given position, and cut off any additional content. */ void pad_out(char *buf, int n) { int i, len = strlen(buf); if (n < 1) return; for (i = len; i < n; ++i) { buf[i] = ' '; } buf[n - 1] = '\0'; } /* Get a char string naming the side. Doesn't have to be pretty. */ /* (should synth complete name/adjective from other parts of speech) */ char * side_name(Side *side) { return (side->name ? side->name : (side->adjective ? side->adjective : (side->pluralnoun ? side->pluralnoun : (side->noun ? side->noun : "")))); } char * side_adjective(Side *side) { return (side->adjective ? side->adjective : (side->noun ? side->noun : (side->pluralnoun ? side->pluralnoun : (side->name ? side->name : "")))); } char * short_side_title(Side *side) { if (side_short_title == NULL) side_short_title = xmalloc(BUFSIZE); if (side->name) { return side->name; } else if (side->pluralnoun) { sprintf(side_short_title, "the %s", side->pluralnoun); } else if (side->noun) { sprintf(side_short_title, "the %s", plural_form(side->noun)); } else if (side->adjective) { sprintf(side_short_title, "the %s side", side->adjective); } else { return " - "; } return side_short_title; } char * short_side_title_with_adjective(Side *side, char *adjective) { if (side_short_title == NULL) side_short_title = xmalloc(BUFSIZE); side_short_title[0] = '\0'; if (side->name) { if (empty_string(adjective)) return side->name; else { strcat(side_short_title, adjective); strcat(side_short_title, " "); strcat(side_short_title, side->name); } } else if (side->pluralnoun) { strcat(side_short_title, "the "); if (!empty_string(adjective)) { strcat(side_short_title, adjective); strcat(side_short_title, " "); } strcat(side_short_title, side->pluralnoun); } else if (side->noun) { strcat(side_short_title, "the "); if (!empty_string(adjective)) { strcat(side_short_title, adjective); strcat(side_short_title, " "); } strcat(side_short_title, side->noun); } else if (side->adjective) { strcat(side_short_title, "the "); if (!empty_string(adjective)) { strcat(side_short_title, adjective); strcat(side_short_title, " "); } strcat(side_short_title, side->adjective); strcat(side_short_title, " side"); } else { return " - "; } return side_short_title; } /* This indicates whether the above routine returns a singular or plural form of title. */ int short_side_title_plural_p(Side *side) { if (side->name) { return FALSE; } else if (side->pluralnoun) { return TRUE; } else if (side->noun) { return TRUE; } else if (side->adjective) { sprintf(side_short_title, "the %s side", side->adjective); return FALSE; } else { return FALSE; } } char * shortest_side_title(Side *side2, char *buf) { if (side2->name) { return side2->name; } else if (side2->adjective) { return side2->adjective; } else if (side2->noun) { return side2->noun; } else if (side2->pluralnoun) { return side2->pluralnoun; } else { sprintf(buf, "(#%d)", side_number(side2)); } return buf; } char * sidemask_desc(char *buf, SideMask sidemask) { int first = TRUE; Side *side2; if (sidemask < 0 || sidemask >= ((1 << (numsides + 1)) - 2)) return "all"; buf[0] = '\0'; for_all_sides(side2) { if (side_in_set(side2, sidemask)) { if (first) first = FALSE; else strcat(buf, ", "); strcat(buf, short_side_title(side2)); } } return buf; } char * side_score_desc(char *buf, Side *side, Scorekeeper *sk) { if (symbolp(sk->body) && (match_keyword(sk->body, K_LAST_SIDE_WINS) || match_keyword(sk->body, K_LAST_ALLIANCE_WINS))) { sprintf(buf, "Point Value: %d", side_point_value(side)); } else { /* Compose the generic scorekeeper status display. */ if (sk->title != NULL) { strcpy(buf, sk->title); } else { sprintf(buf, "SK #%d", sk->id); } if (sk->scorenum >= 0) { tprintf(buf, ": %d", side->scores[sk->scorenum]); } } return buf; } char * long_player_title(char *buf, Player *player, char *thisdisplayname) { buf[0] = '\0'; if (player == NULL) { /* Do nothing */ } else if (player->displayname != NULL) { if (player->name != NULL) { strcat(buf, player->name); strcat(buf, "@"); } if (thisdisplayname != NULL && strcmp(player->displayname, thisdisplayname) == 0 && player->rid == my_rid) { strcat(buf, "You"); } else { strcat(buf, player->displayname); } if (player->aitypename != NULL) { strcat(buf, "(& AI "); strcat(buf, player->aitypename); strcat(buf, ")"); } } else if (player->aitypename != NULL) { strcat(buf, "AI "); strcat(buf, player->aitypename); } else { strcat(buf, "-"); } return buf; } char * short_player_title(char *buf, Player *player, char *thisdisplayname) { buf[0] = '\0'; if (player == NULL) return buf; if (player->name != NULL) { strcat(buf, player->name); } if (player->aitypename != NULL) { strcat(buf, ","); strcat(buf, player->aitypename); } if ((player->name != NULL || player->aitypename != NULL) && player->displayname != NULL) { strcat(buf, "@"); } if (thisdisplayname != NULL && player->displayname != NULL && strcmp(player->displayname, thisdisplayname) == 0) { strcat(buf, "You"); } else if (player->displayname != NULL) { strcat(buf, player->displayname); } if (strlen(buf) == 0) { strcat(buf, "-"); } return buf; } #if 0 /* Unused. */ void side_and_type_name(char *buf, Side *side, int u, Side *side2) { /* Decide how to identify the side. */ if (side2 == NULL) { sprintf(buf, "independent "); } else if (side == side2) { sprintf(buf, "your "); } else { sprintf(buf, "%s ", side_adjective(side2)); } /* Glue the pieces together and return it. */ strcat(buf, u_type_name(u)); } #endif /* Build a short phrase describing a given unit to a given side, basically consisting of indication of unit's side, then of unit itself. */ char * unit_handle(Side *side, Unit *unit) { Side *side2 = NULL; if (unit != NULL) side2 = unit->side; return apparent_unit_handle(side, unit, side2); } /* This version allows the caller to supply a side other than the unit's actual side, such as when describing an out-of-date image of a unit that may have been captured. */ char * apparent_unit_handle(Side *side, Unit *unit, Side *side2) { char *utypename, *fmtstr, smallbuf[40], sidebuf[100]; Side *side3; Obj *frest, *fmt1; /* This should be impossible, be really obvious if it happens. */ if (side2 == NULL) return "null side2 in apparent_unit_handle?"; /* Make sure our working space exists. */ if (unitbuf == NULL) unitbuf = xmalloc(BUFSIZE); /* Handle various weird situations. */ if (unit == NULL) return "???"; if (!alive(unit)) { sprintf(unitbuf, "dead #%d", unit->id); return unitbuf; } /* If this unit represents "yourself", say so. */ if (side != NULL && unit == side->self_unit) return "you"; unitbuf[0] = '\0'; /* Decide how to identify the side. If the unit's original side is not its current side, list both of them. */ side3 = NULL; if (unit->origside != NULL && side2 != unit->origside) side3 = unit->origside; sidebuf[0] = '\0'; if (side2 == side) { strcat(sidebuf, "your"); } else { /* If the side adjective is a genitive (ends in 's, s' or z') we should skip the definite article. */ int len = strlen(side_adjective(side2)); char *end = side_adjective(side2) + len - 2; if (strcmp(end, "'s") != 0 && strcmp(end, "s'") != 0 && strcmp(end, "z'") != 0) strcat(sidebuf, "the "); strcat(sidebuf, side_adjective(side2)); } if (side3 != NULL) { if (side3 == side) { strcat(sidebuf, " (formerly your)"); } else if (side3 == indepside) { /* Don't add anything for captured independents. While technically there's no reason not to do this, in practice a side will often have many captured indeps, and this keeps the text from getting too cluttered. */ } else { strcat(sidebuf, " (formerly "); strcat(sidebuf, side_adjective(side3)); strcat(sidebuf, ")"); } } /* Now add the unit's unique description. */ utypename = u_type_name(unit->type); /* If we have special formatting info, interpret it. */ if (u_desc_format(unit->type) != lispnil) { for_all_list(u_desc_format(unit->type), frest) { fmt1 = car(frest); if (stringp(fmt1)) { /* Append strings verbatim. */ strcat(unitbuf, c_string(fmt1)); } else if (symbolp(fmt1)) { /* Symbols indicate the types of data to format and output. */ fmtstr = c_string(fmt1); if (strcmp(fmtstr, "name") == 0) { strcat(unitbuf, (unit->name ? unit->name : "anon")); } else if (strcmp(fmtstr, "position") == 0) { sprintf(smallbuf, "%d,%d", unit->x, unit->y); strcat(unitbuf, smallbuf); } else if (strcmp(fmtstr, "side") == 0) { strcat(unitbuf, sidebuf); } else if (strcmp(fmtstr, "type") == 0) { strcat(unitbuf, utypename); } else if (strcmp(fmtstr, "side-name") == 0) { strcat(unitbuf, side_name(unit->side)); } else if (strcmp(fmtstr, "side-adjective") == 0) { strcat(unitbuf, side_adjective(unit->side)); } else { strcat(unitbuf, "??description-format??"); } } else { strcat(unitbuf, "??description-format??"); } } return unitbuf; } else { strcat(unitbuf, sidebuf); strcat(unitbuf, " "); } /* Default formats for units. */ if (unit->name) { tprintf(unitbuf, "%s %s", utypename, unit->name); } else if (unit->number > 0) { tprintf(unitbuf, "%d%s %s", unit->number, ordinal_suffix(unit->number), utypename); } else { strcat(unitbuf, utypename); } return unitbuf; } /* Shorter unit description omits side name, but uses same buffer. This is mainly useful for describing the transport of a unit and suchlike, where the player will likely already know the side of the unit. */ char * short_unit_handle(Unit *unit) { int u; if (unitbuf == NULL) unitbuf = xmalloc(BUFSIZE); if (unit == NULL) return "???"; if (!alive(unit)) { sprintf(unitbuf, "dead #%d", unit->id); return unitbuf; } /* If this unit represents "yourself", say so. */ if (unit->side != NULL && unit == unit->side->self_unit) return "you"; u = unit->type; /* Use the name alone if the unit is named, or else use optional ordinal and the shortest type name available. */ if (!empty_string(unit->name)) { strcpy(unitbuf, unit->name); } else { unitbuf[0] = '\0'; if (unit->number > 0) { sprintf(unitbuf, "%d%s ", unit->number, ordinal_suffix(unit->number)); } if (!empty_string(u_short_name(u))) strcat(unitbuf, u_short_name(u)); else strcat(unitbuf, u_type_name(u)); } return unitbuf; } /* This version lists the side but skips original side etc. */ char * medium_long_unit_handle(Unit *unit) { if (unitbuf == NULL) unitbuf = xmalloc(BUFSIZE); if (unit == NULL) return "???"; if (!alive(unit)) { sprintf(unitbuf, "dead #%d", unit->id); return unitbuf; } strcpy(unitbuf, side_adjective(unit->side)); /* If the unit has a name, write its type followed by the name. */ if (!empty_string(unit->name)) { strcat(unitbuf, " "); strcat(unitbuf, u_type_name(unit->type)); strcat(unitbuf, " "); strcat(unitbuf, unit->name); /* If the unit has a number, write it followed by the type. */ } else if (unit->number > 0) { tprintf(unitbuf, " %d%s %s", unit->number, ordinal_suffix(unit->number), u_type_name(unit->type)); /* Else just write the unit type. */ } else { strcat(unitbuf, " "); strcat(unitbuf, u_type_name(unit->type)); } return unitbuf; } /* Put either the unit's name or its number into the given buffer. */ void name_or_number(Unit *unit, char *buf) { if (unit->name) { strcpy(buf, unit->name); } else if (unit->number > 0) { sprintf(buf, "%d%s", unit->number, ordinal_suffix(unit->number)); } else { buf[0] = '\0'; } } /* Build a short phrase describing a given past unit to a given side, basically consisting of indication of unit's side, then of unit itself. */ char * past_unit_handle(Side *side, PastUnit *past_unit) { char *utypename; Side *side2; if (past_unitbuf == NULL) past_unitbuf = xmalloc(BUFSIZE); /* Handle various weird situations. */ if (past_unit == NULL) return "???"; /* Decide how to identify the side. */ side2 = past_unit->side; if (side2 == NULL) { sprintf(past_unitbuf, "the "); } else if (side2 == side) { sprintf(past_unitbuf, "your "); } else { /* If the side adjective is a genitive (ends in 's, s' or z') we should skip the definite article. */ int len = strlen(side_adjective(side2)); char *end = side_adjective(side2) + len - 2; if (strcmp(end, "'s") != 0 && strcmp(end, "s'") != 0 && strcmp(end, "z'") != 0) sprintf(past_unitbuf, "the "); sprintf(past_unitbuf, side_adjective(side2)); } /* Now add the past_unit's unique description. */ utypename = u_type_name(past_unit->type); if (past_unit->name) { tprintf(past_unitbuf, "%s %s", utypename, past_unit->name); } else if (past_unit->number > 0) { tprintf(past_unitbuf, "%d%s %s", past_unit->number, ordinal_suffix(past_unit->number), utypename); } else { strcat(past_unitbuf, utypename); } return past_unitbuf; } /* Given a unit and optional type u, summarize construction status and timing. */ void construction_desc(char *buf, Unit *unit, int u) { int est, u2; char ubuf[10], tmpbuf[100]; Task *task; Unit *unit2; if (u != NONUTYPE) { est = est_completion_time(unit, u); if (est >= 0) { sprintf(ubuf, "[%2d] ", est); } else { strcpy(ubuf, " -- "); } } else { ubuf[0] = '\0'; } name_or_number(unit, tmpbuf); sprintf(buf, "%s%s %s", ubuf, u_type_name(unit->type), tmpbuf); pad_out(buf, 25); if (unit->plan && unit->plan->tasks) { task = unit->plan->tasks; if (task->type == TASK_BUILD) { u2 = task->args[0]; tprintf(buf, " %s ", (is_unit_type(u2) ? u_type_name(u2) : "?")); unit2 = find_unit(task->args[1]); if (in_play(unit2) && unit2->type == u2) { tprintf(buf, "%d/%d done ", unit2->cp, u_cp(unit2->type)); } tprintf(buf, "(%d of %d)", task->args[2] + 1, task->args[3]); } else if (task->type == TASK_DEVELOP) { u2 = task->args[0]; if (is_unit_type(u2)) { tprintf(buf, " %s tech %d/%d", u_type_name(u2), unit->side->tech[u2], task->args[1]); } } } } /* Given a unit and optional advance a, summarize research status and timing. */ void research_desc(char *buf, Unit *unit, int a) { char abuf[10], tmpbuf[100]; if (a != NONATYPE) { if (u_can_research(unit->type)) { sprintf(abuf, "[%2d] ", a_rp(a)); } else { strcpy(abuf, " -- "); } } else { abuf[0] = '\0'; } name_or_number(unit, tmpbuf); sprintf(buf, "%s%s %s", abuf, u_type_name(unit->type), tmpbuf); pad_out(buf, 25); } void researchible_desc(char *buf, Unit *unit, int a) { char abuf[10]; if (a != NONATYPE && unit != NULL) { if (u_advanced(unit->type)) { sprintf(abuf, "[%2d] ", a_rp(a)); } else { strcpy(abuf, " -- "); } } else { abuf[0] = '\0'; } sprintf(buf, "%s%s", abuf, a_type_name(a)); pad_out(buf, 25); } /* This generates a textual description of a type's construction info, including estimated time for the given unit to build one, tooling & tech, plus number of that type in existence already. */ void constructible_desc(char *buf, Side *side, int u, Unit *unit) { char estbuf[20]; char techbuf[50]; char typenamebuf[50]; int est, tp, num; if (unit != NULL) { est = est_completion_time(unit, u); if (est >= 0) { sprintf(estbuf, "[%2d] ", est); if (uu_tp_to_build(unit->type, u) > 0) { tp = (unit->tooling ? unit->tooling[u] : 0); tprintf(estbuf, "(%2d) ", tp); } } else { strcpy(estbuf, " -- "); } } else { estbuf[0] = '\0'; } if (u_tech_max(u) > 0) { sprintf(techbuf, "[Tech %d/%d/%d] ", side->tech[u], u_tech_to_build(u), u_tech_max(u)); } else { techbuf[0] = '\0'; } strcpy(typenamebuf, u_type_name(u)); /* If the single char for the type is different from the first character of its type name, mention the char. */ if (!empty_string(u_uchar(u)) && (u_uchar(u))[0] != typenamebuf[0]) { tprintf(typenamebuf, "(%c)", (u_uchar(u))[0]); } sprintf(buf, "%s%s%-16.16s", estbuf, techbuf, typenamebuf); num = num_units_in_play(side, u); if (num > 0) { tprintf(buf, " %3d", num); } else { strcat(buf, " "); } num = num_units_incomplete(side, u); if (num > 0) { tprintf(buf, "(%d)", num); } } #if 0 /* Unused. */ void historical_event_date_desc(HistEvent *hevt, char *buf) { sprintf(buf, "%d: ", hevt->startdate); } #endif int find_event_type(Obj *sym) { int i; for (i = 0; hevtdefns[i].name != NULL; ++i) { if (strcmp(c_string(sym), hevtdefns[i].name) == 0) return i; } return -1; } /* (should abstract out evt arg -> unit/pastunit description code) */ void historical_event_desc(Side *side, HistEvent *hevt, char *buf) { int data0 = hevt->data[0]; int data1 = hevt->data[1]; Obj *rest, *head, *pattern, *text; Unit *unit; PastUnit *pastunit, *pastunit2; Side *side2; for_all_list(g_event_narratives(), rest) { head = car(rest); if (consp(head)) { pattern = car(head); if (symbolp(pattern) && find_event_type(pattern) == hevt->type) { text = cadr(head); if (stringp(text)) { sprintf(buf, c_string(text)); } else { sprintlisp(buf, text, 50); } return; } else if (consp(pattern) && symbolp(car(pattern)) && pattern_matches_event(pattern, hevt) ) { text = cadr(head); if (stringp(text)) { sprintf(buf, c_string(text)); } else { event_desc_from_list(side, text, hevt, buf); } return; } } } /* Generate a default description of the event. */ switch (hevt->type) { case H_LOG_STARTED: sprintf(buf, "we started recording events"); break; case H_LOG_ENDED: sprintf(buf, "we stopped recording events"); break; case H_GAME_STARTED: sprintf(buf, "we started the game"); break; case H_GAME_SAVED: sprintf(buf, "we saved the game"); break; case H_GAME_RESTARTED: sprintf(buf, "we restarted the game"); break; case H_GAME_ENDED: sprintf(buf, "we ended the game"); break; case H_SIDE_JOINED: side2 = side_n(data0); sprintf(buf, "%s joined the game", (side == side2 ? "you" : side_name(side2))); break; case H_SIDE_LOST: side2 = side_n(data0); sprintf(buf, "%s lost!", (side == side2 ? "you" : side_name(side2))); /* Include an explanation of the cause, if there is one. */ if (data1 == -1) { tprintf(buf, " (resigned)"); } else if (data1 == -2) { tprintf(buf, " (self-unit died)"); } else if (data1 > 0) { tprintf(buf, " (scorekeeper %d)", data1); } else { tprintf(buf, " (don't know why)"); } break; case H_SIDE_WITHDREW: side2 = side_n(data0); sprintf(buf, "%s withdrew!", (side == side2 ? "you" : side_name(side2))); break; case H_SIDE_WON: side2 = side_n(data0); sprintf(buf, "%s won!", (side == side2 ? "you" : side_name(side2))); /* Include an explanation of the cause, if there is one. */ if (data1 > 0) { tprintf(buf, " (scorekeeper %d)", data1); } else { tprintf(buf, " (don't know why)"); } break; case H_UNIT_CREATED: side2 = side_n(data0); sprintf(buf, "%s created ", (side == side2 ? "you" : side_name(side2))); unit = find_unit(data1); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data1); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data1); } } break; case H_UNIT_COMPLETED: side2 = side_n(data0); sprintf(buf, "%s completed ", (side == side2 ? "you" : side_name(side2))); unit = find_unit(data1); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data1); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data1); } } break; case H_UNIT_DAMAGED: unit = find_unit_dead_or_alive(data0); if (unit != NULL) { strcpy(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data0); if (pastunit != NULL) { strcpy(buf, past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } } tprintf(buf, " damaged (%d -> %d hp)", data1, hevt->data[2]); break; case H_UNIT_CAPTURED: buf[0] = '\0'; /* Note that the second optional value, if present, is the id of the unit that did the capturing. */ unit = find_unit_dead_or_alive(data1); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data1); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else if (data1 == 0) { tprintf(buf, "somebody"); } else { tprintf(buf, "%d??", data1); } } tprintf(buf, " captured "); /* Describe the unit that was captured. */ unit = find_unit_dead_or_alive(data0); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data0); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data0); } } break; case H_UNIT_SURRENDERED: buf[0] = '\0'; /* Describe the unit that surrendered. */ unit = find_unit_dead_or_alive(data0); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data0); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data0); } } tprintf(buf, " surrendered to "); /* The second optional value, if present, is the id of the unit that accepted the surrender. */ unit = find_unit_dead_or_alive(data1); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data1); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else if (data1 == 0) { tprintf(buf, "somebody"); } else { tprintf(buf, "%d??", data1); } } break; case H_UNIT_ACQUIRED: buf[0] = '\0'; if (data1 >= 0) { side2 = side_n(data1); strcat(buf, (side == side2 ? "you" : side_name(side2))); } else { tprintf(buf, "somebody"); } tprintf(buf, " acquired "); /* Describe the unit that was acquired. */ unit = find_unit_dead_or_alive(data0); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data0); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data0); } } break; case H_UNIT_REVOLTED: buf[0] = '\0'; /* Describe the unit that revolted. */ unit = find_unit_dead_or_alive(data0); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit = find_past_unit(data0); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d??", data0); } } tprintf(buf, " revolted"); if (data1 >= 0) { side2 = side_n(data1); tprintf(buf, ", went over to %s", (side == side2 ? "you" : side_name(side2))); } break; case H_UNIT_KILLED: case H_UNIT_DIED_IN_ACCIDENT: case H_UNIT_DIED_FROM_TEMPERATURE: /* Obviously, the unit mentioned here can only be a past unit. */ pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } if (hevt->type == H_UNIT_KILLED) tprintf(buf, " was destroyed"); else if (hevt->type == H_UNIT_DIED_IN_ACCIDENT) tprintf(buf, " died in an accident"); else tprintf(buf, " died from excessive temperature"); break; case H_UNIT_WRECKED: case H_UNIT_WRECKED_IN_ACCIDENT: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } if (hevt->type == H_UNIT_WRECKED) tprintf(buf, " was wrecked"); else tprintf(buf, " was wrecked in an accident"); break; case H_UNIT_VANISHED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " vanished"); break; case H_UNIT_DISBANDED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " was disbanded"); break; case H_UNIT_GARRISONED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " was used to garrison "); unit = find_unit(data1); if (unit != NULL) { strcat(buf, unit_handle(side, unit)); } else { pastunit2 = find_past_unit(data1); if (pastunit2 != NULL) { strcat(buf, past_unit_handle(side, pastunit2)); } else { /* Should never happen, but don't choke if it does. */ tprintf(buf, "?????"); } } break; case H_UNIT_STARVED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " starved to death"); break; case H_UNIT_MERGED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " merged into another"); break; case H_UNIT_LEFT_WORLD: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } tprintf(buf, " left the world"); break; case H_UNIT_NAME_CHANGED: pastunit = find_past_unit(data0); if (pastunit != NULL) { sprintf(buf, "%s", past_unit_handle(side, pastunit)); } else { sprintf(buf, "%d??", data0); } unit = find_unit(data1); if (unit != NULL) { if (unit->name != NULL) tprintf(buf, " changed name to \"%s\"", unit->name); else tprintf(buf, " became anonymous"); } else { pastunit2 = find_past_unit(data1); if (pastunit2 != NULL) { if (pastunit2->name != NULL) tprintf(buf, " changed name to \"%s\"", pastunit2->name); else tprintf(buf, " became anonymous"); } else tprintf(buf, " no name change???"); } break; default: /* Don't warn, will cause serious problems for windows that display lists of events, but make sure the non-understood event is obvious. */ sprintf(buf, "?????????? \"%s\" ??????????", hevtdefns[hevt->type].name); break; } } int pattern_matches_event(Obj *pattern, HistEvent *hevt) { int data0, u; Obj *rest, *subpat; PastUnit *pastunit; if (find_event_type(car(pattern)) != hevt->type) return FALSE; data0 = hevt->data[0]; for_all_list(cdr(pattern), rest) { subpat = car(rest); if (symbolp(subpat)) { switch (hevt->type) { case H_UNIT_STARVED: u = utype_from_name(c_string(subpat)); pastunit = find_past_unit(data0); if (pastunit != NULL && pastunit->type == u) return TRUE; else return FALSE; break; default: return FALSE; } } else { /* (should warn of bad pattern syntax?) */ return FALSE; } } return TRUE; } void event_desc_from_list(Side *side, Obj *lis, HistEvent *hevt, char *buf) { int n; Obj *rest, *item; PastUnit *pastunit; buf[0] = '\0'; for_all_list(lis, rest) { item = car(rest); if (stringp(item)) { strcat(buf, c_string(item)); } else if (numberp(item)) { n = c_number(item); if (between(0, n, 3)) { switch (hevt->type) { case H_UNIT_STARVED: pastunit = find_past_unit(hevt->data[0]); if (pastunit != NULL) { strcat(buf, past_unit_handle(side, pastunit)); } else { tprintf(buf, "%d?", hevt->data[0]); } break; /* (should add other event types) */ default: break; } } else { tprintf(buf, " ??%d?? ", n); } } else { strcat(buf, " ????? "); } } } /* Return a string describing the action's result. */ char * action_result_desc(int rslt) { char *str; switch (rslt) { case A_ANY_OK: str = "OK"; break; case A_ANY_DONE: str = "done"; break; case A_ANY_CANNOT_DO: str = "can never do"; break; case A_ANY_NO_ACP: str = "insufficient acp"; break; case A_ANY_NO_MATERIAL: str = "insufficient material"; break; case A_ANY_NO_AMMO: str = "insufficient ammo"; break; case A_ANY_TOO_FAR: str = "too far"; break; case A_ANY_TOO_NEAR: str = "too near"; break; case A_MOVE_NO_MP: str = "insufficient mp"; break; case A_MOVE_BLOCKING_ZOC: str = "blocking ZOC present"; break; case A_MOVE_CANNOT_LEAVE_WORLD: str = "cannot leave world"; break; case A_MOVE_DEST_FULL: str = "destination full"; break; case A_OVERRUN_FAILED: str = "overrun failed"; break; case A_OVERRUN_SUCCEEDED: str = "overrun succeeded"; break; case A_FIRE_BLOCKED: str = "fire blocked by terrain"; break; case A_FIRE_INTO_OUTSIDE_WORLD: str = "cannot fire outside world"; break; case A_CAPTURE_FAILED: str = "capture failed"; break; case A_CAPTURE_SUCCEEDED: str = "capture succeeded"; break; case A_ANY_ERROR: str = "misc error"; break; default: if (between(0, rslt, NUMHEVTTYPES - 1)) str = hevtdefns[rslt].name; else /* This should never happen. */ str = "???"; break; } return str; } /* Explain the reason for failure to move into a cell. */ void advance_failure_desc(char *buf, Unit *unit, HistEventType reason) { strcpy(buf, unit_handle(unit->side, unit)); if (reason == A_ANY_CANNOT_DO) strcat(buf, " can never do this!"); else if (reason == A_ANY_NO_ACP) strcat(buf, " does not have enough ACP!"); else if (reason == A_ANY_NO_MATERIAL) strcat(buf, " is out of some material!"); else if (reason == A_ANY_NO_AMMO) strcat(buf, " is out of ammo!"); else if (reason == A_MOVE_NO_MP) /* Player doesn't see mp calcs, so explain as ACP */ strcat(buf, " does not have enough ACP!"); else if (reason == A_MOVE_CANNOT_LEAVE_WORLD) strcat(buf, " can never leave the world!"); else if (reason == A_MOVE_BLOCKING_ZOC) strcat(buf, " cannot enter a blocking ZOC!"); else if (reason == A_MOVE_DEST_FULL) strcat(buf, " cannot fit into the destination!"); else if (reason != H_UNDEFINED) sprintf(buf, " cannot act, reason is %s", action_result_desc(reason)); else strcat(buf, " is unable to act, don't know why"); } /* Generate a description of the borders and connections in and around a location. */ void linear_desc(char *buf, int x, int y) { int t, first = TRUE; buf[0] = '\0'; if (any_aux_terrain_defined()) { for_all_terrain_types(t) { if (t_is_border(t) && aux_terrain_defined(t) && any_borders_at(x, y, t)) { #if 0 /* Confusing. */ if (first) { strcat(buf, " at "); first = FALSE; } else { strcat(buf, ","); } strcat(buf, t_type_name(t)); #endif #if 0 /* takes up more space, but not very useful */ for_all_directions(dir) { if (border_at(x, y, dir, t)) { tprintf(buf, "/%s", dirnames[dir]); } } #endif } else if (t_is_connection(t) && aux_terrain_defined(t) && any_connections_at(x, y, t)) { if (first) { strcat(buf, " + "); first = FALSE; } else { strcat(buf, ","); } strcat(buf, t_type_name(t)); #if 0 for_all_directions(dir) { if (connection_at(x, y, dir, t)) { tprintf(buf, "/%s", dirnames[dir]); } } #endif } } } } void elevation_desc(char *buf, int x, int y) { if (elevations_defined()) { sprintf(buf, "(Elev %d)", elev_at(x, y)); } } char * feature_desc(Feature *feature, char *buf) { int i, caps = FALSE; char *str; if (feature == NULL) return NULL; if (feature->name) { /* Does the name need any substitutions done? */ if (strchr(feature->name, '%')) { i = 0; for (str = feature->name; *str != '\0'; ++str) { if (*str == '%') { /* Interpret substitution directives. */ switch (*(str + 1)) { case 'T': caps = TRUE; case 't': if (feature->typename) { buf[i] = '\0'; strcat(buf, feature->typename); if (caps) capitalize(buf); i = strlen(buf); } ++str; break; default: break; } } else { buf[i++] = *str; } } /* Close off the string. */ buf[i] = '\0'; return buf; } else { /* Return the name alone. */ return feature->name; } } else { if (feature->typename) { strcpy(buf, "unnamed "); strcat(buf, feature->typename); return buf; } } /* No description of the location is available. */ return "anonymous feature"; } /* Generate a string describing what is at the given location. */ char * feature_name_at(int x, int y) { int fid = (features_defined() ? raw_feature_at(x, y) : 0); Feature *feature; if (fid == 0) return NULL; feature = find_feature(fid); if (feature != NULL) { if (featurebuf == NULL) featurebuf = xmalloc(BUFSIZE); return feature_desc(feature, featurebuf); } /* No description of the location is available. */ return NULL; } void temperature_desc(char *buf, int x, int y) { if (temperatures_defined()) { sprintf(buf, "(Temp %d)", temperature_at(x, y)); } } #if 0 int age, u; short view, prevview; Side *side2; /* Compose and display view history of this cell. */ Dprintf("Drawing previous view info\n"); age = side_view_age(side, curx, cury); prevview = side_prevview(side, curx, cury); if (age == 0) { if (prevview != view) { if (prevview == EMPTY) { /* misleading if prevview was set during init. */ sprintf(tmpbuf, "Up to date; had been empty."); } else if (prevview == UNSEEN) { sprintf(tmpbuf, "Up to date; had been unexplored."); } else { side2 = side_n(vside(prevview)); u = vtype(prevview); if (side2 != side) { sprintf(tmpbuf, "Up to date; had seen %s %s.", (side2 == NULL ? "independent" : side_name(side2)), u_type_name(u)); } else { sprintf(tmpbuf, "Up to date; had been occupied by your %s.", u_type_name(u)); } } } else { sprintf(tmpbuf, "Up to date."); } } else { if (prevview == EMPTY) { sprintf(tmpbuf, "Was empty %d turns ago.", age); } else if (prevview == UNSEEN) { sprintf(tmpbuf, "Terrain first seen %d turns ago.", age); } else { side2 = side_n(vside(prevview)); u = vtype(prevview); if (side2 != side) { sprintf(tmpbuf, "Saw %s %s, %d turns ago.", (side2 == NULL ? "independent" : side_name(side2)), u_type_name(u), age); } else { sprintf(tmpbuf, "Was occupied by your %s %d turns ago.", u_type_name(u), age); } } } #endif void size_desc(char *buf, Unit *unit, int label) { buf[0] = '\0'; if (!u_advanced(unit->type)) return; tprintf(buf, "(%d)", unit->size); } void hp_desc(char *buf, Unit *unit, int label) { if (label) { sprintf(buf, "HP "); } else { buf[0] = '\0'; } /* (print '-' or some such for zero hp case?) */ if (unit->hp == u_hp(unit->type)) { tprintf(buf, "%d", unit->hp); } else { tprintf(buf, "%d/%d", unit->hp, u_hp(unit->type)); } } void acp_desc(char *buf, Unit *unit, int label) { int u = unit->type; if (!completed(unit)) { sprintf(buf, "%d/%d done", unit->cp, u_cp(u)); } else if (unit->act && u_acp(u) > 0) { if (label) { strcpy(buf, "ACP "); } else { buf[0] = '\0'; } if (unit->act->acp == unit->act->initacp) { tprintf(buf, "%d", unit->act->acp); } else { tprintf(buf, "%d/%d", unit->act->acp, unit->act->initacp); } } else { buf[0] = '\0'; } } /* Describe a unit's current combat experience, if applicable. */ void cxp_desc(char *buf, Unit *unit, int label) { int cxpmax = u_cxp_max(unit->type); buf[0] = '\0'; if (cxpmax == 0) return; if (label) strcat(buf, " cXP "); if (unit->cxp == cxpmax) { tprintf(buf, "%d", unit->cxp); } else { tprintf(buf, "%d/%d", unit->cxp, cxpmax); } } /* Describe a unit's current morale, if applicable. */ void morale_desc(char *buf, Unit *unit, int label) { int moralemax = u_morale_max(unit->type); buf[0] = '\0'; if (moralemax == 0) return; if (label) strcat(buf, " Mor "); if (unit->morale == moralemax) { tprintf(buf, "%d", unit->morale); } else { tprintf(buf, "%d/%d", unit->morale, moralemax); } } /* Describe a unit's individual point value if it has one. */ void point_value_desc(char *buf, Unit *unit, int label) { buf[0] = '\0'; if (unit_point_value(unit) < 0) return; if (label) strcat(buf, " Value "); tprintf(buf, "%d", unit_point_value(unit)); } /* Describe one "row" (group of 3) of a unit's supply status. */ int supply_desc(char *buf, Unit *unit, int mrow) { int u = unit->type, m, mm, tmprow; tmprow = 0; buf[0] = '\0'; mm = 0; for_all_material_types(m) { if (um_storage_x(u, m) > 0) { if (mm > 0 && mm % 3 == 0) ++tmprow; if (tmprow == mrow) { tprintf(buf, "%s %d/%d ", m_type_name(m), unit->supply[m], um_storage_x(u, m)); } ++mm; } } return (strlen(buf) > 0); } /* Describe a builder's current tooling. */ int tooling_desc(char *buf, Unit *unit) { int u2, num; buf[0] = '\0'; if (unit->tooling == NULL) return FALSE; /* Use the number of nonzero tooling to govern the use of abbreviations. */ num = 0; for_all_unit_types(u2) if (unit->tooling[u2] > 0) ++num; if (num == 0) { strcat(buf, "No tooling"); return TRUE; } strcat(buf, "Tooling: "); for_all_unit_types(u2) { if (unit->tooling[u2] > 0) { tprintf(buf, "%s %d/%d ", (num > 2 ? shortest_unique_name(u2) : u_type_name(u2)), unit->tooling[u2], uu_tp_to_build(unit->type, u2)); } } return TRUE; } void location_desc(char *buf, Side *side, Unit *unit, int u, int x, int y) { int t = terrain_at(x, y); char *featurename; if (unit != NULL && unit->transport != NULL) { sprintf(buf, "In %s", short_unit_handle(unit->transport)); } else if (unit != NULL || u != NONUTYPE) { sprintf(buf, "In %s", t_type_name(t)); linear_desc(buf + strlen(buf), x, y); } else if (terrain_visible(side, x, y)) { sprintf(buf, "Empty %s", t_type_name(t)); linear_desc(buf + strlen(buf), x, y); } else { sprintf(buf, "Unknown"); } if (terrain_visible(side, x, y)) { featurename = feature_name_at(x, y); if (!empty_string(featurename)) tprintf(buf, " (%s)", featurename); if (elevations_defined()) tprintf(buf, " (El %d)", elev_at(x, y)); if (temperatures_defined()) tprintf(buf, " (T %d)", temperature_at(x, y)); if (winds_defined()) { int wforce = wind_force_at(x, y); if (wforce == 0) tprintf(buf, " (W calm)"); else tprintf(buf, " (W f%d %s)", wforce, dirnames[wind_dir_at(x, y)]); } /* (should optionally list other local weather also) */ } tprintf(buf, " at %d,%d", x, y); } /* Given a cell, describe where it is. */ void destination_desc(char *buf, Side *side, Unit *unit, int x, int y, int z) { int dir, x1, y1; Unit *unit2; if (!in_area(x, y)) { sprintf(buf, "?%d,%d?", x, y); return; } unit2 = unit_at(x, y); if (unit2 != NULL && unit2->side == side) { if (unit2 == unit) { sprintf(buf, "self (%d,%d)", x, y); } else if (!empty_string(unit2->name)) { sprintf(buf, "%s (%d,%d)", unit2->name, x, y); } else { sprintf(buf, "%s (%d,%d)", u_type_name(unit2->type), x, y); } return; } for_all_directions(dir) { if (interior_point_in_dir(x, y, dir, &x1, &y1)) { unit2 = unit_at(x1, y1); /* (should look at stack) */ if (unit2 != NULL && unit2->side == side) { if (unit2 == unit) { sprintf(buf, "%s (%d,%d)", dirnames[opposite_dir(dir)], x, y); } else if (!empty_string(unit2->name)) { sprintf(buf, "%s of %s (%d,%d)", dirnames[opposite_dir(dir)], unit2->name, x, y); } else { sprintf(buf, "%s of %s (%d,%d)", dirnames[opposite_dir(dir)], u_type_name(unit2->type), x, y); } return; } } } /* This is the old reliable case. */ sprintf(buf, "%d,%d", x, y); if (z != 0) tprintf(buf, ",%d", z); } void latlong_desc(char *buf, int x, int y, int xf, int yf, int which) { char minbuf[10]; int rawlat, latdeg, latmin, rawlon, londeg, lonmin; buf[0] = '\0'; if (world.circumference <= 0) return; xy_to_latlong(x, y, xf, yf, &rawlat, &rawlon); if (which & 2) { latdeg = abs(rawlat) / 60; latmin = abs(rawlat) % 60; minbuf[0] = '\0'; if (latmin != 0) sprintf(minbuf, "%dm", latmin); sprintf(buf, "%dd%s %c", latdeg, minbuf, (rawlat >= 0 ? 'N' : 'S')); } if (which & 1) { londeg = abs(rawlon) / 60; lonmin = abs(rawlon) % 60; minbuf[0] = '\0'; if (lonmin != 0) sprintf(minbuf, "%dm", lonmin); if (!empty_string(buf)) strcat(buf, " "); sprintf(buf + strlen(buf), "%dd%s %c", londeg, minbuf, (rawlon >= 0 ? 'E' : 'W')); } } void others_here_desc(char *buf, Unit *unit) { int u2, first = TRUE, nums[MAXUTYPES], incomplete[MAXUTYPES]; Unit *unit2, *top; buf[0] = '\0'; top = unit_at(unit->x, unit->y); if (top != NULL && top->nexthere != NULL) { for_all_unit_types(u2) nums[u2] = incomplete[u2] = 0; for_all_stack(unit->x, unit->y, unit2) if (completed(unit2)) ++nums[unit2->type]; else ++incomplete[unit2->type]; /* Don't count ourselves. */ if (completed(unit)) --nums[unit->type]; else --incomplete[unit->type]; for_all_unit_types(u2) { if (nums[u2] > 0 || incomplete[u2] > 0) { if (first) first = FALSE; else strcat(buf, " "); if (nums[u2] > 0) tprintf(buf, "%d", nums[u2]); if (incomplete[u2] > 0) tprintf(buf, "(%d)", incomplete[u2]); strcat(buf, " "); strcat(buf, shortest_generic_name(u2)); } } strcat(buf, " here also"); } } void occupants_desc(char *buf, Unit *unit) { int u2, first = TRUE, nums[MAXUTYPES], incomplete[MAXUTYPES]; Unit *occ; buf[0] = '\0'; if (unit->occupant != NULL) { strcat(buf, "Occs "); for_all_unit_types(u2) nums[u2] = incomplete[u2] = 0; for_all_occupants(unit, occ) if (completed(occ)) ++nums[occ->type]; else ++incomplete[occ->type]; for_all_unit_types(u2) { if (nums[u2] > 0 || incomplete[u2] > 0) { if (first) first = FALSE; else strcat(buf, " "); if (nums[u2] > 0) tprintf(buf, "%d", nums[u2]); if (incomplete[u2] > 0) tprintf(buf, "(%d)", incomplete[u2]); strcat(buf, " "); strcat(buf, shortest_generic_name(u2)); } } } } /* Fill the given buffer with a verbal description of the unit's current plan. */ void plan_desc(char *buf, Unit *unit) { char goalbuf[BUFSIZE]; int i; Plan *plan = unit->plan; Task *task; if (plan == NULL) { buf[0] = '\0'; return; } sprintf(buf, "Plan: %s", plantypenames[plan->type]); if (plan->waitingfortasks) strcat(buf, " Waiting"); if (plan->asleep) strcat(buf, " Asleep"); if (plan->reserve) strcat(buf, " Reserve"); if (plan->delayed) strcat(buf, " Delay"); /* The more usual case is to allow AI control of units, so we only mention it when it's off. */ if (!plan->aicontrol) strcat(buf, " NoAI"); if (plan->supply_is_low) strcat(buf, " SupplyLow"); if (plan->waitingfortransport) strcat(buf, " WaitingForTransport"); if (plan->maingoal) { strcat(buf, ". Goal: "); strcat(buf, goal_desc(goalbuf, plan->maingoal)); } if (plan->formation) { strcat(buf, ". Formation: "); strcat(buf, goal_desc(goalbuf, plan->formation)); } /* For tasks, just put out a count. */ if (plan->tasks) { i = 0; for_all_tasks(plan, task) ++i; tprintf(buf, ". %d task%s", i, (i == 1 ? "" : "s")); } strcat(buf, "."); } /* Describe a task in a brief but intelligible way. Note that the given side and/or unit may be NULL, such as when describing a task not yet assigned to a unit (standing orders for instance). */ void task_desc(char *buf, Side *side, Unit *unit, Task *task) { int i, slen, arg0, arg1, arg2, arg3; char *argtypes, *sidedesc, *utypedesc; Unit *unit2; if (task == NULL) { buf[0] = '\0'; return; } arg0 = task->args[0]; arg1 = task->args[1]; arg2 = task->args[2]; arg3 = task->args[3]; sprintf(buf, "%s ", taskdefns[task->type].display_name); switch (task->type) { case TASK_BUILD: tprintf(buf, "%s", u_type_name(arg0)); if (arg1 != 0) { unit2 = find_unit(arg1); if (unit2 != NULL) { tprintf(buf, ", %d/%d done", unit2->cp, u_cp(unit2->type)); } } if (arg3 > 1) tprintf(buf, " (%d%s of %d)", arg2 + 1, ordinal_suffix(arg2 + 1), arg3); break; case TASK_CAPTURE: sidedesc = (arg3 == ALLSIDES ? "any" : side_adjective(side_n(arg3))); utypedesc = (arg2 == NONUTYPE ? "unit" : u_type_name(arg2)); tprintf(buf, "%s %s ", sidedesc, utypedesc); unit2 = unit_at(arg0, arg1); /* (should scan stack, only list a visible unit) */ if (unit2 != NULL && unit2->side == side_n(arg3) && !empty_string(unit2->name)) { tprintf(buf, "%s ", unit2->name); tprintf(buf, "(%d,%d)", arg0, arg1); } else { tprintf(buf, "at %d,%d", arg0, arg1); } break; case TASK_COLLECT: tprintf(buf, "%s around ", m_type_name(arg0)); tprintf(buf, "%d,%d", arg0, arg1); break; case TASK_DEVELOP: tprintf(buf, "%s to tech %d", u_type_name(arg0), arg1); if (side != NULL) { tprintf(buf, " (currently at %d)", side->tech[arg0]); } break; case TASK_HIT_POSITION: tprintf(buf, "at %d,%d", arg0, arg1); break; case TASK_HIT_UNIT: sidedesc = (arg3 == ALLSIDES ? "any" : side_adjective(side_n(arg3))); utypedesc = (arg2 == NONUTYPE ? "unit" : u_type_name(arg2)); tprintf(buf, "%s %s ", sidedesc, utypedesc); unit2 = unit_at(arg0, arg1); if (unit2 != NULL && unit2->side == side_n(arg3) && !empty_string(unit2->name)) { tprintf(buf, "%s ", unit2->name); tprintf(buf, "(%d,%d)", arg0, arg1); } else { tprintf(buf, "at %d,%d", arg0, arg1); } break; case TASK_MOVE_DIR: tprintf(buf, "%s, %d times", dirnames[arg0], arg1); break; case TASK_MOVE_TO: if (unit != NULL && unit->x == arg0 && unit->y == arg1) { tprintf(buf, "here"); } else { if (arg3 == 0) { /* do nothing */ } else if (arg3 == 1) { tprintf(buf, "adj "); } else { tprintf(buf, "within %d of ", arg3); } tprintf(buf, "%d,%d", arg0, arg1); } break; case TASK_OCCUPY: unit2 = find_unit(arg0); if (unit2 != NULL) { tprintf(buf, "%s ", medium_long_unit_handle(unit2)); if (!empty_string(unit2->name)) { tprintf(buf, "(%d,%d)", unit2->x, unit2->y); } else { tprintf(buf, "at %d,%d", unit2->x, unit2->y); } } else { tprintf(buf, "unknown unit #%d", arg0); } break; case TASK_PICKUP: unit2 = find_unit(arg0); if (unit2 != NULL) { tprintf(buf, "%s", medium_long_unit_handle(unit2)); } else { tprintf(buf, "unknown unit #%d", arg0); } break; case TASK_RESUPPLY: if (arg0 != NONMTYPE) tprintf(buf, "%s", m_type_name(arg0)); else tprintf(buf, "all"); if (arg1 != 0) { unit2 = find_unit(arg1); if (unit2 != NULL) { tprintf(buf, " at %s", short_unit_handle(unit2)); } } break; case TASK_SENTRY: /* (should display as calendar time) */ tprintf(buf, "for %d turns", arg0); break; default: /* Default is just to dump out the raw data about the task. */ tprintf(buf, "raw"); argtypes = taskdefns[task->type].argtypes; slen = strlen(argtypes); for (i = 0; i < slen; ++i) { tprintf(buf, "%c%d", (i == 0 ? ' ' : ','), task->args[i]); } break; } if (Debug) { /* Include the number of executions and retries. */ tprintf(buf, " x %d", task->execnum); if (task->retrynum > 0) { tprintf(buf, " fail %d", task->retrynum); } } } /* Describe a goal in a human-intelligible way. */ char * goal_desc(char *buf, Goal *goal) { int numargs, i, arg; char *argtypes; Unit *unit; if (goal == NULL) return "null goal"; switch (goal->type) { case GOAL_KEEP_FORMATION: sprintf(buf, " %d,%d from %s (var %d)", goal->args[1], goal->args[2], unit_handle(NULL, find_unit(goal->args[0])), goal->args[3]); break; case GOAL_VICINITY_HELD: sprintf(buf, "Hold %dx%d area around %d,%d", goal->args[2], goal->args[3], goal->args[0], goal->args[1]); break; case GOAL_UNIT_OCCUPIED: unit = find_unit(goal->args[0]); sprintf(buf, "Occupy %s ", medium_long_unit_handle(unit)); if (!empty_string(unit->name)) { tprintf(buf, "(%d,%d)", unit->x, unit->y); } else { tprintf(buf, "at %d,%d", unit->x, unit->y); } break; /* (should add more cases specific to common types of goals) */ default: sprintf(buf, "%s%s", (goal->tf ? "" : "not "), goaldefns[goal->type].display_name); argtypes = goaldefns[goal->type].argtypes; numargs = strlen(argtypes); for (i = 0; i < numargs; ++i) { arg = goal->args[i]; switch (argtypes[i]) { case 'h': tprintf(buf, "%d", arg); break; case 'm': if (is_material_type(arg)) tprintf(buf, " %s", m_type_name(arg)); else tprintf(buf, " m%d?", arg); break; case 'S': tprintf(buf, " %s", short_side_title(side_n(arg))); break; case 'u': if (is_unit_type(arg)) tprintf(buf, " %s", u_type_name(arg)); else tprintf(buf, " u%d?", arg); break; case 'U': tprintf(buf, " %s", unit_handle(NULL, find_unit(arg))); break; case 'w': tprintf(buf, " %dx", arg); break; case 'x': tprintf(buf, " %d,", arg); break; case 'y': tprintf(buf, "%d", arg); break; default: tprintf(buf, " %d", arg); break; } } break; } return buf; } /* Format a (real, not game) clock time into a standard form. This routine will omit the hours part if it will be uninteresting. */ void time_desc(char *buf, int time, int maxtime) { int hour, minute, second; if (time >= 0) { hour = time / 3600; minute = (time / 60) % 60; second = time % 60; if (between(1, maxtime, 3600) && hour == 0) { sprintf(buf, "%.2d:%.2d", minute, second); } else { sprintf(buf, "%.2d:%.2d:%.2d", hour, minute, second); } } else { sprintf(buf, "??:??:??"); } } #if 0 /* Unused. */ /* General-purpose routine to take an array of anonymous unit types and summarize what's in it, using numbers and unit chars. */ char * summarize_units(char *buf, int *ucnts) { char tmp[BUFSIZE]; /* should be bigger? */ int u; buf[0] = '\0'; for_all_unit_types(u) { if (ucnts[u] > 0) { sprintf(tmp, " %d %s", ucnts[u], utype_name_n(u, 3)); strcat(buf, tmp); } } return buf; } #endif static void notify_doctrine_1(Side *side, Doctrine *doctrine); void notify_doctrine(Side *side, char *spec) { int u; char *arg, *rest, substr[BUFSIZE], outbuf[BUFSIZE]; Doctrine *doctrine; if (!empty_string(spec)) rest = get_next_arg(spec, substr, &arg); else arg = ""; if ((doctrine = find_doctrine_by_name(arg)) != NULL) { /* Found a specific named doctrine. */ /* (should say which of our own unit types use it) */ } else if ((u = utype_from_name(arg)) != NONUTYPE) { doctrine = side->udoctrine[u]; } else if (strcmp(arg, "default") == 0) { doctrine = side->default_doctrine; /* (should say which unit types use it) */ } else { if (!empty_string(arg)) notify(side, "\"%s\" not recognized as doctrine name or unit type.", arg); /* Note that although we list all doctrines, including those belonging to other sides, because we don't know if the other side is actually *using* any particular doctrine. While this may seem like an information leak, if a side has its own unique doctrine, curiously enough this corresponds well to the real world; for instance, present-day nations' militaries are all familiar with each other's doctrines. */ outbuf[0] = '\0'; for_all_doctrines(doctrine) { if (!empty_string(doctrine->name)) tprintf(outbuf, " %s", doctrine->name); else tprintf(outbuf, " #%d", doctrine->id); if (doctrine->next != NULL) tprintf(outbuf, ","); } notify(side, "Doctrines available:%s", outbuf); for_all_doctrines(doctrine) { notify_doctrine_1(side, doctrine); } return; } /* We now have a doctrine to display. */ notify_doctrine_1(side, doctrine); } static void notify_doctrine_1(Side *side, Doctrine *doctrine) { int u, i, num, dflt; char abuf[BUFSIZE]; if (!empty_string(doctrine->name)) notify(side, "Doctrine '%s' (%s):", doctrine->name, (doctrine->locked ? "fixed" : "adjustable")); else notify(side, "Doctrine #%d (%s):", doctrine->id, (doctrine->locked ? "fixed" : "adjustable")); notify(side, " Resupply at %d%% of storage", doctrine->resupply_percent); notify(side, " Rearm at %d%% of storage", doctrine->rearm_percent); notify(side, " Repair at %d%% of hp", doctrine->repair_percent); abuf[0] = '\0'; i = 0; num = 0; dflt = doctrine->construction_run[0]; #if 0 /* should use histo code in write_table */ for_all_unit_types(u) { if (dflt == doctrine->construction_run[u]) { ++num; } } #endif for_all_unit_types(u) { tprintf(abuf, " %s %d", u_type_name(u), doctrine->construction_run[u]); if (i > 0 && (i++) % 4 == 0 && !empty_string(abuf)) { notify(side, " Construction run:%s", abuf); abuf[0] = '\0'; } } if (!empty_string(abuf)) notify(side, " Construction run:%s", abuf); } #define first_person(unit) ((unit)->side && (unit)->side->self_unit == (unit)) static int report_combat_special(Unit *unit1, Unit *unit2, char *str); int type_matches_symbol(Obj *sym, int u); void report_combat(Unit *atker, Unit *other, char *str) { int rslt; if (g_action_notices() != lispnil) { rslt = report_combat_special(atker, other, str); if (rslt) return; } /* Default messages for each type of report. */ if (strcmp(str, "destroy") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s destroy %s!" : "%s destroys %s!")); } else if (strcmp(str, "destroy-occupant") == 0) { notify_combat(other, atker, (first_person(atker) ? " (and destroy occupant %s!)" : " (and destroys occupant %s!)")); } else if (strcmp(str, "hit") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s hit %s!" : "%s hits %s!")); } else if (strcmp(str, "hit-occupant") == 0) { notify_combat(other, atker, (first_person(atker) ? " (and hit occupant %s!)" : " (and hits occupant %s!)")); } else if (strcmp(str, "miss") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s miss %s." : "%s misses %s.")); } else if (strcmp(str, "miss-occupant") == 0) { /* (this case is too uninteresting to mention) */ } else if (strcmp(str, "resist") == 0) { notify_combat(other, atker, (first_person(other) ? "%s throw back %s!" : "%s throws back %s!")); } else if (strcmp(str, "resist/slaughter") == 0) { notify_combat(other, atker, (first_person(other) ? "%s resist capture; %s slaughtered!" : "%s resists capture; %s slaughtered!")); } else if (strcmp(str, "capture") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s capture %s!" : "%s captures %s!")); } else if (strcmp(str, "liberate") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s liberate %s!" : "%s liberates %s!")); } else if (strcmp(str, "escape") == 0) { notify_combat(other, atker, (first_person(other) ? "%s escape!" : "%s escapes!")); } else if (strcmp(str, "retreat") == 0) { notify_combat(other, atker, (first_person(other) ? "%s retreat!" : "%s retreats!")); } else if (strcmp(str, "detonate") == 0) { notify_combat(atker, other, (first_person(atker) ? "%s detonate!" : "%s detonates!")); } else { notify_combat(atker, other, str); } } static void notify_combat(Unit *unit1, Unit *unit2, char *str) { char buf1[BUFSIZE], buf2[BUFSIZE]; Side *side3; for_all_sides(side3) { if (active_display(side3) && (side3->see_all || (unit1 != NULL && side3 == unit1->side) || (unit2 != NULL && side3 == unit2->side))) { strcpy(buf1, unit_handle(side3, unit1)); strcpy(buf2, unit_handle(side3, unit2)); notify(side3, str, buf1, buf2); } } } static int report_combat_special(Unit *unit1, Unit *unit2, char *str) { int found = FALSE; char abuf[BUFSIZE]; Obj *rest, *head, *pat, *msgdesc; Side *side3; for_all_list(g_action_notices(), rest) { head = car(rest); if (!consp(head)) { run_warning("Non-list in action-notices"); continue; } pat = car(head); if (symbolp(pat) && strcmp(c_string(pat), str) == 0) { found = TRUE; break; } if (consp(pat) && symbolp(car(pat)) && strcmp(c_string(car(pat)), str) == 0 && pattern_matches_combat(cdr(pat), unit1, unit2)) { found = TRUE; break; } } /* If we have a match, do something with it. */ if (found) { msgdesc = cadr(head); if (stringp(msgdesc)) { strcpy(abuf, c_string(msgdesc)); } else { /* Notify all sides that could have seen the combat. */ for_all_sides(side3) { if (active_display(side3) && (side3->see_all || (unit1 != NULL && side3 == unit1->side) || (unit2 != NULL && side3 == unit2->side))) { combat_desc_from_list(side3, msgdesc, unit1, unit2, str, abuf); notify(side3, abuf); } } } } return found; } static int pattern_matches_combat(Obj *parms, Unit *unit, Unit *unit2) { Obj *head; head = car(parms); if (!type_matches_symbol(head, unit->type)) return FALSE; parms = cdr(parms); head = car(parms); if (!type_matches_symbol(head, unit2->type)) return FALSE; return TRUE; } int type_matches_symbol(Obj *sym, int u) { char *typename; Obj *val, *rest, *head; if (!symbolp(sym)) return FALSE; typename = u_type_name(u); if (strcmp(c_string(sym), typename) == 0) return TRUE; if (match_keyword(sym, K_USTAR)) return TRUE; if (boundp(sym)) { val = eval(sym); if (symbolp(val) && strcmp(c_string(val), typename) == 0) return TRUE; if (utypep(val) && c_number(val) == u) return TRUE; if (consp(val)) { for_all_list(val, rest) { head = car(rest); if (symbolp(head) && strcmp(c_string(head), typename) == 0) return TRUE; if (utypep(head) && c_number(head) == u) return TRUE; } } } return FALSE; } static void combat_desc_from_list(Side *side, Obj *lis, Unit *unit, Unit *unit2, char *str, char *buf) { int n; char *symname; Obj *rest, *item; buf[0] = '\0'; for_all_list(lis, rest) { item = car(rest); if (stringp(item)) { strcat(buf, c_string(item)); } else if (symbolp(item)) { symname = c_string(item); if (strcmp(symname, "actor") == 0) { strcat(buf, unit_handle(side, unit)); } else if (strcmp(symname, "actee") == 0) { strcat(buf, unit_handle(side, unit2)); } else { tprintf(buf, " ??%s?? ", symname); } } else if (numberp(item)) { n = c_number(item); if (0 /* special processing */) { } else { tprintf(buf, "%d", n); } } else { strcat(buf, " ????? "); } } } void report_give(Side *side, Unit *unit, Unit *unit2, short *rslts) { char buf[BUFSIZE]; int m, something = FALSE; sprintf(buf, "%s gave", unit_handle(side, unit)); for_all_material_types(m) { if (rslts[m] > 0) { tprintf(buf, " %d %s", rslts[m], m_type_name(m)); something = TRUE; } } if (!something) { strcat(buf, " nothing"); } if (unit2 != NULL) { tprintf(buf, " to %s", unit_handle(side, unit2)); } notify(side, "%s.", buf); } void report_take(Side *side, Unit *unit, int needed, short *rslts) { char buf[BUFSIZE]; int m, something = FALSE; if (!needed) { notify(side, "%s needed nothing.", unit_handle(side, unit)); return; } buf[0] = '\0'; for_all_material_types(m) { if (rslts[m] > 0) { tprintf(buf, " %d %s", rslts[m], m_type_name(m)); something = TRUE; } } if (something) { notify(side, "%s got%s.", unit_handle(side, unit), buf); } else { notify(side, "%s got nothing.", unit_handle(side, unit)); } } void notify_all_of_resignation(Side *side, Side *side2) { Side *side3; for_all_sides(side3) { if (side3 != side) { notify(side3, "%s %s giving up!", short_side_title_with_adjective(side, #ifdef RUDE (flip_coin() ? "cowardly" : "wimpy") #else NULL #endif /* RUDE */ ), (short_side_title_plural_p(side) ? "are" : "is")); if (side2 != NULL) { notify(side3, "... and donating everything to %s!", short_side_title(side2)); } } } } /* Given a number, figure out what suffix should go with it. */ char * ordinal_suffix(int n) { if (n % 100 == 11 || n % 100 == 12 || n % 100 == 13) { return "th"; } else { switch (n % 10) { case 1: return "st"; case 2: return "nd"; case 3: return "rd"; default: return "th"; } } } /* Pluralize a word, attempting to be smart about various possibilities that don't have a different plural form (such as "Chinese" and "Swiss"). */ /* There should probably be a test for when to add "es" instead of "s". */ char * plural_form(char *word) { char endch = ' ', nextend = ' '; int len; if (word == NULL) { run_warning("plural_form given NULL string"); pluralbuf[0] = '\0'; return pluralbuf; } len = strlen(word); if (len > 0) endch = word[len - 1]; if (len > 1) nextend = word[len - 2]; if (endch == 'h' || endch == 's' || (endch == 'e' && nextend == 's')) { sprintf(pluralbuf, "%s", word); } else { sprintf(pluralbuf, "%ss", word); } return pluralbuf; } /* Make the first letter of the buffer upper-case. */ char * capitalize(char *buf) { if (islower(buf[0])) buf[0] = toupper(buf[0]); return buf; } /* Compose a readable form of the given date. */ char * absolute_date_string(int date) { /* The first time we ask for a date, interpret the calendar. */ if (calendar_type == cal_unknown) init_calendar(); switch (calendar_type) { case cal_number: sprintf(datebuf, "%s%4d", turn_name, date); return datebuf; case cal_usual: return usual_date_string(date); default: case_panic("calendar type", calendar_type); } return "!?!"; } /* Interpret the calendar definition. */ static void init_calendar(void) { Obj *cal, *caltype, *stepname, *steprest; cal = g_calendar(); turn_name = "Turn"; if (cal == lispnil) { /* The default is to have numbered turns named "Turn". */ calendar_type = cal_number; } else if (consp(cal)) { caltype = car(cal); if (match_keyword(caltype, K_NUMBER)) { calendar_type = cal_number; if (stringp(cadr(cal))) turn_name = c_string(cadr(cal)); } else if (match_keyword(caltype, K_USUAL)) { calendar_type = cal_usual; stepname = cadr(cal); if (symbolp(stepname)) { parse_date_step_range(cdr(cal)); } else if (consp(stepname)) { for_all_list(cdr(cal), steprest) { parse_date_step_range(car(steprest)); } } else { init_warning("No name for date step type, substituting `day'"); date_step_ranges[0].step_type = ds_day; date_step_ranges[0].step_size = 1; num_date_step_ranges = 1; } } } if (calendar_type == cal_unknown) init_error("Bad calendar type"); if (!empty_string(g_initial_date())) set_initial_date(g_initial_date()); } static void parse_date_step_range(Obj *form) { int n; char *stepnamestr; UsualDateStepType steptype; Obj *step; n = num_date_step_ranges; /* Peel off a turn range if supplied. */ if (numberp(car(form))) { date_step_ranges[n].turn_start = c_number(car(form)); form = cdr(form); } if (numberp(car(form))) { date_step_ranges[n].turn_end = c_number(car(form)); form = cdr(form); } if (symbolp(car(form))) { stepnamestr = c_string(car(form)); if (strcmp(stepnamestr, "second") == 0) { steptype = ds_second; } else if (strcmp(stepnamestr, "minute") == 0) { steptype = ds_minute; } else if (strcmp(stepnamestr, "hour") == 0) { steptype = ds_hour; } else if (strcmp(stepnamestr, "day") == 0) { steptype = ds_day; } else if (strcmp(stepnamestr, "week") == 0) { steptype = ds_week; } else if (strcmp(stepnamestr, "month") == 0) { steptype = ds_month; } else if (strcmp(stepnamestr, "season") == 0) { steptype = ds_season; } else if (strcmp(stepnamestr, "year") == 0) { steptype = ds_year; } else { init_warning("\"%s\" not a known date step name", stepnamestr); steptype = ds_day; } } else { /* (should warn) */ steptype = ds_day; } date_step_ranges[n].step_type = steptype; /* Collect an optional multiple. */ step = cadr(form); date_step_ranges[n].step_size = (numberp(step) ? c_number(step) : 1); ++num_date_step_ranges; } /* Given two dates, figure out how many turns encompassed by them. */ int turns_between(char *datestr1, char *datestr2) { int rslt, turn1, turn2; UsualDate date1, date2; if (calendar_type == cal_unknown) init_calendar(); switch (calendar_type) { case cal_number: sscanf("%d", datestr1, &turn1); sscanf("%d", datestr2, &turn2); return (turn2 - turn1); case cal_usual: parse_usual_date(datestr1, 0, &date1); parse_usual_date(datestr2, 0, &date2); rslt = date2.year - date1.year; if (num_date_step_ranges == 1) { if (date_step_ranges[0].step_type == ds_year) return (rslt / date_step_ranges[0].step_size); if (date_step_ranges[0].step_type < ds_year) { rslt = (12 * rslt) - (date2.month - date1.month); } if (date_step_ranges[0].step_type == ds_month) return (rslt / date_step_ranges[0].step_size); if (date_step_ranges[0].step_type < ds_month) { rslt = (30 * rslt) - (date2.day - date1.day); } if (date_step_ranges[0].step_type == ds_week) return (((rslt + 6) / 7) / date_step_ranges[0].step_size); if (date_step_ranges[0].step_type == ds_day) return (rslt / date_step_ranges[0].step_size); if (date_step_ranges[0].step_type < ds_day) { rslt = (24 * rslt) - (date2.hour - date1.hour); } if (date_step_ranges[0].step_type == ds_hour) return (rslt / date_step_ranges[0].step_size); return (rslt / date_step_ranges[0].step_size); /* semi-bogus */ } else { /* Too complicated for now, give up. */ return 1; } default: case_panic("calendar type", calendar_type); break; } return 1; /* appease the compiler */ } /* Given a date string, make it be the date of the first turn. */ void set_initial_date(char *str) { if (calendar_type == cal_unknown) init_calendar(); switch (calendar_type) { case cal_number: sscanf("%d", str, &turn_initial); break; case cal_usual: if (usual_initial == NULL) usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate)); parse_usual_date(str, 0, usual_initial); break; default: case_panic("calendar type", calendar_type); break; } } /* Pick a date out of the given string. Note that this implementation does not detect extra garbage in the string, should fix someday. */ /* (should use strtol etc instead of sscanf) */ static void parse_usual_date(char *datestr, int range, UsualDate *udate) { char aname[BUFSIZE]; int i, cnt; udate->second = udate->minute = udate->hour = 0; udate->day = udate->month = udate->year = 0; aname[0] = '\0'; if (!empty_string(datestr)) { /* Assume it's in a standard date format. */ switch (date_step_ranges[0].step_type) { case ds_second: cnt = sscanf(datestr, "%d:%d:%d %d %s %d", &(udate->second), &(udate->minute), &(udate->hour), &(udate->day), aname, &(udate->year)); if (cnt != 6) { cnt = sscanf(datestr, "%d:%d:%d", &(udate->second), &(udate->minute), &(udate->hour)); if (cnt != 3) goto bad_format; return; } --(udate->day); break; case ds_minute: cnt = sscanf(datestr, "%d:%d %d %s %d", &(udate->minute), &(udate->hour), &(udate->day), aname, &(udate->year)); if (cnt != 5) { cnt = sscanf(datestr, "%d:%d", &(udate->minute), &(udate->hour)); if (cnt != 2) goto bad_format; return; } --(udate->day); break; case ds_hour: cnt = sscanf(datestr, "%d:00 %d %s %d", &(udate->hour), &(udate->day), aname, &(udate->year)); if (cnt != 4) cnt = sscanf(datestr, "%d %d %s %d", &(udate->hour), &(udate->day), aname, &(udate->year)); if (cnt != 4) goto bad_format; --(udate->day); break; case ds_day: case ds_week: cnt = sscanf(datestr, "%d %s %d", &(udate->day), aname, &(udate->year)); if (cnt != 3) goto bad_format; --(udate->day); break; case ds_month: cnt = sscanf(datestr, "%s %d", aname, &(udate->year)); if (cnt != 2) goto bad_format; break; case ds_season: cnt = sscanf(datestr, "%s %d", aname, &(udate->year)); if (cnt != 2) goto bad_format; for (i = 0; i < 4; ++i) { if (strcmp(aname, seasons[i]) == 0) { udate->month = i * 3; return; } } init_warning("\"%s\" not a recognized season name", aname); return; case ds_year: cnt = sscanf(datestr, "%d", &(udate->year)); if (cnt != 1) goto bad_format; return; default: init_warning("%d not an allowed date step type", date_step_ranges[0].step_type); break; } for (i = 0; i < 12; ++i) { /* (should make case-insensitive) */ if (strcmp(aname, months[i]) == 0) { udate->month = i; return; } } init_warning("\"%s\" not a recognized month name", aname); } return; bad_format: init_warning("\"%s\" is a badly formatted date", datestr); } /* Given a numeric date, convert it into something understandable. */ static int ever_mentioned_bc; static char * usual_date_string(int date) { int year = 0, season = 0, month = 0, day = 0; int hour = 0, second = 0, minute = 0; int i, r, range; /* The date, which is a turn number, should be 1 or more, but this routine may be called before the game really starts, so return something that will be distinctive if it's ever displayed. */ if (date <= 0) return "pregame"; /* First displayed date is normally turn 1; be zero-based for the benefit of calculation. */ --date; /* Find the date step type and size. */ for (range = 0; range < num_date_step_ranges; ++range) { if (between(date_step_ranges[range].turn_start, date, date_step_ranges[range].turn_end)) break; } /* If no matches, just use the last range in the array. */ if (range >= num_date_step_ranges) range = num_date_step_ranges - 1; /* If multiples of basic step, convert to basic step by multiplying. */ date *= date_step_ranges[range].step_size; if (usual_initial == NULL) { usual_initial = (UsualDate *) xmalloc(sizeof(UsualDate)); if (!empty_string(g_initial_date())) parse_usual_date(g_initial_date(), 0, usual_initial); } switch (date_step_ranges[range].step_type) { case ds_second: second = date % 60; minute = date / 60; sprintf(datebuf, "%d:%d", minute, second); /* (should add day/month/year if available?) */ break; case ds_minute: minute = date % 60; hour = date / 60 + usual_initial->hour; sprintf(datebuf, "%d:%d", hour, minute); /* (should add day/month/year if available?) */ break; case ds_hour: date += usual_initial->hour; hour = date % 24; date /= 24; date += usual_initial->day; for (i = 0; i < usual_initial->month; ++i) date += monthdays[i]; day = date % 365; month = 0; for (i = 0; i < 12; ++i) { if (day < monthdays[i]) break; day -= monthdays[i]; ++month; } ++day; year = date / 365 + usual_initial->year; sprintf(datebuf, "%d:00 %2d %s %d", hour, day, months[month], ABS(year)); break; case ds_week: /* Convert to days, then proceed as for days. */ date *= 7; /* Fall through. */ case ds_day: date += usual_initial->day; for (i = 0; i < usual_initial->month; ++i) date += monthdays[i]; day = date % 365; month = 0; for (i = 0; i < 12; ++i) { if (day < monthdays[i]) break; day -= monthdays[i]; ++month; } ++day; year = date / 365 + usual_initial->year; sprintf(datebuf, "%2d %s %d", day, months[month], ABS(year)); break; case ds_month: date += usual_initial->month; month = date % 12; year = date / 12 + usual_initial->year; sprintf(datebuf, "%s %d", months[month], ABS(year)); break; case ds_season: season = date % 4; year = date / 4 + usual_initial->year; sprintf(datebuf, "%s %d", seasons[season], ABS(year)); break; case ds_year: year = usual_initial->year; /* (should do something similar for other step types) */ for (r = 0; r < range; ++r) year += ((date_step_ranges[r].turn_end - date_step_ranges[r].turn_start) * date_step_ranges[r].step_size); year += (date - (date_step_ranges[range].turn_start * date_step_ranges[range].step_size)); sprintf(datebuf, "%d", ABS(year)); break; default: sprintf(datebuf, "%d is unknown date step type", date_step_ranges[0].step_type); break; } if (year < 0) { strcat(datebuf, " BC"); ever_mentioned_bc = TRUE; } else if (ever_mentioned_bc) { /* If any date was displayed as "BC", use "AD" with all positive year numbers. */ strcat(datebuf, " AD"); } return datebuf; } /* Show some overall numbers on performance of a side. */ void write_side_results(FILE *fp, Side *side) { int i; if (side == NULL) { fprintf(fp, "Results for game as a whole:\n\n"); } else { fprintf(fp, "Results for %s%s", short_side_title(side), (side_won(side) ? " (WINNER)" : (side_lost(side) ? " (LOSER)" : ""))); for (i = 0; i < numscores; ++i) { fprintf(fp, " %d", side->scores[i]); } fprintf(fp, ", played by %s:\n\n", long_player_title(spbuf, side->player, NULL)); } } /* Display what is essentially a double-column bookkeeping of unit gains and losses. */ void write_unit_record(FILE *fp, Side *side) { int u, gainreason, lossreason, totgain, totloss, val; fprintf(fp, "Unit Record (gains and losses by cause and unit type)\n"); fprintf(fp, " Unit Type "); for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) { fprintf(fp, " %3s", gain_reason_names[gainreason]); } fprintf(fp, " Gain |"); for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) { fprintf(fp, " %3s", loss_reason_names[lossreason]); } fprintf(fp, " Loss |"); fprintf(fp, " Total\n"); for_all_unit_types(u) { if (u_possible[u]) { totgain = 0; fprintf(fp, " %9s ", utype_name_n(u, 9)); for (gainreason = 0; gainreason < num_gain_reasons; ++gainreason) { val = gain_count(side, u, gainreason); if (val > 0) { fprintf(fp, " %3d", val); totgain += val; } else { fprintf(fp, " "); } } fprintf(fp, " %3d |", totgain); totloss = 0; for (lossreason = 0; lossreason < num_loss_reasons; ++lossreason) { val = loss_count(side, u, lossreason); if (val > 0) { fprintf(fp, " %3d", val); totloss += val; } else { fprintf(fp, " "); } } fprintf(fp, " %3d |", totloss); fprintf(fp, " %3d\n", totgain - totloss); } } fprintf(fp, "\n"); } static int gain_count(Side *side, int u, int r) { int sum; if (side != NULL) return side_gain_count(side, u, r); sum = 0; for_all_sides(side) { sum += side_gain_count(side, u, r); } return sum; } static int loss_count(Side *side, int u, int r) { int sum; if (side != NULL) return side_loss_count(side, u, r); sum = 0; for_all_sides(side) { sum += side_loss_count(side, u, r); } return sum; } /* Nearly-raw combat statistics; hard to interpret, but they provide a useful check against subjective evaluation of performance. */ void write_combat_results(FILE *fp, Side *side) { int a, d, atk; fprintf(fp, "Unit Combat Results (average damage over # attacks against enemy, by type)\n"); fprintf(fp, " A D->"); for_all_unit_types(d) { if (u_possible[d]) { fprintf(fp, " %4s ", utype_name_n(d, 4)); } } fprintf(fp, "\n"); for_all_unit_types(a) { if (u_possible[a]) { fprintf(fp, " %4s ", utype_name_n(a, 4)); for_all_unit_types(d) { if (u_possible[d]) { atk = atkstats(side, a, d); if (atk > 0) { fprintf(fp, " %5.2f", ((float) hitstats(side, a, d)) / atk); } else { fprintf(fp, " "); } } } fprintf(fp, "\n "); for_all_unit_types(d) { if (u_possible[d]) { atk = atkstats(side, a, d); if (atk > 0) { fprintf(fp, " %4d ", atk); } else { fprintf(fp, " "); } } } fprintf(fp, "\n"); } } fprintf(fp, "\n"); } static int atkstats(Side *side, int a, int d) { int sum; if (side != NULL) return side_atkstats(side, a, d); sum = 0; for_all_sides(side) { sum += side_atkstats(side, a, d); } return sum; } static int hitstats(Side *side, int a, int d) { int sum; if (side != NULL) return side_hitstats(side, a, d); sum = 0; for_all_sides(side) { sum += side_hitstats(side, a, d); } return sum; } void dice_desc(char *buf, int dice) { int numdice, die, offset; if (dice >> 14 == 0 || dice >> 14 == 3) { sprintf(buf, "%d", dice); } else { numdice = (dice >> 11) & 0x07; die = (dice >> 7) & 0x0f; offset = dice & 0x7f; if (offset == 0) { sprintf(buf, "%dd%d", numdice, die); } else { sprintf(buf, "%dd%d+%d", numdice, die, offset); } } } /* The following code formats a list of types that are missing images. */ void record_missing_image(int typtyp, char *str) { if (missinglist == NULL) { missinglist = xmalloc(BUFSIZE); missinglist[0] = '\0'; } ++missing[typtyp]; /* Add the name of the image-less type, but only if one of the first few. */ if (between(1, totlisted, NUMTOLIST)) strcat(missinglist, ","); if (totlisted < NUMTOLIST) { strcat(missinglist, str); } else if (totlisted == NUMTOLIST) { strcat(missinglist, "..."); } ++totlisted; } /* Return true if any images could not be found, and provide some helpful info into the supplied buffer. */ int missing_images(char *buf) { if (missinglist == NULL) return FALSE; buf[0] = '\0'; if (missing[UTYP] > 0) tprintf(buf, " %d unit images", missing[UTYP]); if (missing[TTYP] > 0) tprintf(buf, " %d terrain images", missing[TTYP]); if (missing[3] > 0) tprintf(buf, " %d emblems", missing[3]); tprintf(buf, " - %s", missinglist); return TRUE; }