forked from github/server
2434 lines
70 KiB
C
2434 lines
70 KiB
C
#ifdef _MSC_VER
|
|
#include <platform.h>
|
|
#endif
|
|
#include "reports.h"
|
|
|
|
#include "battle.h"
|
|
#include "guard.h"
|
|
#include "laws.h"
|
|
#include "spells.h"
|
|
#include "travelthru.h"
|
|
#include "lighthouse.h"
|
|
#include "donations.h"
|
|
|
|
/* attributes includes */
|
|
#include "attributes/attributes.h"
|
|
#include "attributes/follow.h"
|
|
#include "attributes/otherfaction.h"
|
|
#include "attributes/racename.h"
|
|
#include "attributes/stealth.h"
|
|
|
|
#include "spells/unitcurse.h"
|
|
|
|
/* kernel includes */
|
|
#include "kernel/config.h"
|
|
#include "kernel/calendar.h"
|
|
#include "kernel/ally.h"
|
|
#include "kernel/alliance.h"
|
|
#include "kernel/connection.h"
|
|
#include "kernel/building.h"
|
|
#include "kernel/curse.h"
|
|
#include "kernel/faction.h"
|
|
#include "kernel/group.h"
|
|
#include "kernel/item.h"
|
|
#include "kernel/messages.h"
|
|
#include "kernel/order.h"
|
|
#include "kernel/plane.h"
|
|
#include "kernel/race.h"
|
|
#include "kernel/region.h"
|
|
#include "kernel/resources.h"
|
|
#include "kernel/ship.h"
|
|
#include "kernel/spell.h"
|
|
#include "kernel/spellbook.h"
|
|
#include "kernel/terrain.h"
|
|
#include "kernel/unit.h"
|
|
|
|
/* util includes */
|
|
#include "kernel/attrib.h"
|
|
#include "util/base36.h"
|
|
#include "util/functions.h"
|
|
#include "util/goodies.h"
|
|
#include "util/language.h"
|
|
#include "util/lists.h"
|
|
#include "util/log.h"
|
|
#include "util/macros.h"
|
|
#include "util/path.h"
|
|
#include "util/password.h"
|
|
#include "util/strings.h"
|
|
#include "util/translation.h"
|
|
#include <stream.h>
|
|
#include <selist.h>
|
|
|
|
/* libc includes */
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
#include "move.h"
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1900
|
|
# pragma warning(disable: 4774) /* TODO: remove this */
|
|
#endif
|
|
|
|
#define SCALEWEIGHT 100 /* Faktor, um den die Anzeige von Gewichten skaliert wird */
|
|
|
|
bool nocr = false;
|
|
bool nonr = false;
|
|
bool noreports = false;
|
|
|
|
const char *visibility[] = {
|
|
"none",
|
|
"neighbour",
|
|
"lighthouse",
|
|
"lighthouse",
|
|
"travel",
|
|
"far",
|
|
"unit",
|
|
"spell",
|
|
"battle"
|
|
};
|
|
|
|
const char *coasts[MAXDIRECTIONS] = {
|
|
"coast::nw",
|
|
"coast::ne",
|
|
"coast::e",
|
|
"coast::se",
|
|
"coast::sw",
|
|
"coast::w"
|
|
};
|
|
|
|
const char *options[MAXOPTIONS] = {
|
|
"AUSWERTUNG",
|
|
"COMPUTER",
|
|
"ZUGVORLAGE",
|
|
NULL,
|
|
"STATISTIK",
|
|
"DEBUG",
|
|
"ZIPPED",
|
|
"ZEITUNG", /* Option hat Sonderbehandlung! */
|
|
NULL,
|
|
"ADRESSEN",
|
|
"BZIP2",
|
|
"PUNKTE",
|
|
"SHOWSKCHANGE"
|
|
};
|
|
|
|
bool omniscient(const faction *f)
|
|
{
|
|
static const race *rc_template;
|
|
static int cache;
|
|
if (rc_changed(&cache)) {
|
|
rc_template = get_race(RC_TEMPLATE);
|
|
}
|
|
return (f->race == rc_template);
|
|
}
|
|
|
|
static char *groupid(const struct group *g, const struct faction *f)
|
|
{
|
|
typedef char name[OBJECTIDSIZE + 1];
|
|
static name idbuf[8];
|
|
static int nextbuf = 0;
|
|
char *buf = idbuf[(++nextbuf) % 8];
|
|
sprintf(buf, "%s (%s)", g->name, itoa36(f->no));
|
|
return buf;
|
|
}
|
|
|
|
const char *combatstatus[] = {
|
|
"status_aggressive", "status_front",
|
|
"status_rear", "status_defensive",
|
|
"status_avoid", "status_flee"
|
|
};
|
|
|
|
void report_status(const unit * u, const struct locale *lang, struct sbstring *sbp)
|
|
{
|
|
const char * status = LOC(lang, combatstatus[u->status]);
|
|
|
|
if (!status) {
|
|
const char *lname = locale_name(lang);
|
|
struct locale *wloc = get_locale(lname);
|
|
|
|
log_warning("no translation for combat status %s in %s", combatstatus[u->status], lname);
|
|
locale_setstring(wloc, combatstatus[u->status], combatstatus[u->status] + 7);
|
|
sbs_strcat(sbp, combatstatus[u->status] + 7);
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, status);
|
|
}
|
|
if (fval(u, UFL_NOAID)) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "status_noaid"));
|
|
}
|
|
}
|
|
|
|
size_t report_status_depr(const unit * u, const struct locale *lang, char *fsbuf, size_t buflen)
|
|
{
|
|
sbstring sbs;
|
|
|
|
sbs_init(&sbs, fsbuf, buflen);
|
|
report_status(u, lang, &sbs);
|
|
return sbs_length(&sbs);
|
|
}
|
|
|
|
|
|
const char *hp_status(const unit * u)
|
|
{
|
|
double p;
|
|
int max_hp = u->number * unit_max_hp(u);
|
|
|
|
if (u->hp == max_hp)
|
|
return NULL;
|
|
|
|
p = (double)((double)u->hp / (double)(max_hp));
|
|
|
|
if (p < 0.50)
|
|
return mkname("damage", "badly");
|
|
if (p < 0.75)
|
|
return mkname("damage", "wounded");
|
|
if (p < 0.99)
|
|
return mkname("damage", "exhausted");
|
|
if (p > 2.00)
|
|
return mkname("damage", "plusstrong");
|
|
if (p > 1.50)
|
|
return mkname("damage", "strong");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void
|
|
report_item(const unit * owner, const item * i, const faction * viewer,
|
|
const char **name, const char **basename, int *number, bool singular)
|
|
{
|
|
const resource_type *rsilver = get_resourcetype(R_SILVER);
|
|
|
|
assert(!owner || owner->number);
|
|
if (owner && owner->faction == viewer) {
|
|
if (name)
|
|
*name =
|
|
LOC(viewer->locale, resourcename(i->type->rtype,
|
|
((i->number != 1 && !singular) ? GR_PLURAL : 0)));
|
|
if (basename)
|
|
*basename = resourcename(i->type->rtype, 0);
|
|
if (number)
|
|
*number = i->number;
|
|
}
|
|
else if (owner && i->type->rtype == rsilver) {
|
|
int pp = i->number / owner->number;
|
|
if (number)
|
|
*number = 1;
|
|
if (pp > 50000 && dragonrace(u_race(owner))) {
|
|
if (name)
|
|
*name = LOC(viewer->locale, "dragonhoard");
|
|
if (basename)
|
|
*basename = "dragonhoard";
|
|
}
|
|
else if (pp > 5000) {
|
|
if (name)
|
|
*name = LOC(viewer->locale, "moneychest");
|
|
if (basename)
|
|
*basename = "moneychest";
|
|
}
|
|
else if (pp > 500) {
|
|
if (name)
|
|
*name = LOC(viewer->locale, "moneybag");
|
|
if (basename)
|
|
*basename = "moneybag";
|
|
}
|
|
else {
|
|
if (number)
|
|
*number = 0;
|
|
if (name)
|
|
*name = NULL;
|
|
if (basename)
|
|
*basename = NULL;
|
|
}
|
|
}
|
|
else {
|
|
if (name)
|
|
*name =
|
|
LOC(viewer->locale, resourcename(i->type->rtype,
|
|
NMF_APPEARANCE | ((i->number != 1 && !singular) ? GR_PLURAL : 0)));
|
|
if (basename)
|
|
*basename = resourcename(i->type->rtype, NMF_APPEARANCE);
|
|
if (number) {
|
|
if (fval(i->type, ITF_HERB))
|
|
*number = 1;
|
|
else
|
|
*number = i->number;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define ORDERS_IN_NR 1
|
|
static void buforder(sbstring *sbp, const order * ord, const struct locale *lang, int mode)
|
|
{
|
|
sbs_strcat(sbp, ", \"");
|
|
if (mode < ORDERS_IN_NR) {
|
|
char cmd[ORDERSIZE];
|
|
get_command(ord, lang, cmd, sizeof(cmd));
|
|
sbs_strcat(sbp, cmd);
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, "...");
|
|
}
|
|
|
|
sbs_strcat(sbp, "\"");
|
|
}
|
|
|
|
/** create a report of a list of items to a non-owner.
|
|
* \param result: an array of size items.
|
|
* \param size: maximum number of items to return
|
|
* \param owner: the owner of the items, or NULL for faction::items etc.
|
|
* \param viewer: the faction looking at the items
|
|
*/
|
|
int
|
|
report_items(const unit *u, item * result, int size, const unit * owner,
|
|
const faction * viewer)
|
|
{
|
|
const item *itm, *items = u->items;
|
|
int n = 0; /* number of results */
|
|
|
|
assert(owner == NULL || viewer != owner->faction);
|
|
assert(size);
|
|
|
|
if (u->attribs) {
|
|
curse * cu = get_curse(u->attribs, &ct_itemcloak);
|
|
if (cu && curse_active(cu)) {
|
|
return 0;
|
|
}
|
|
}
|
|
for (itm = items; itm; itm = itm->next) {
|
|
const char *ic;
|
|
|
|
report_item(owner, itm, viewer, NULL, &ic, NULL, false);
|
|
if (ic && *ic) {
|
|
item *ishow;
|
|
for (ishow = result; ishow != result + n; ++ishow) {
|
|
const char *sc;
|
|
|
|
if (ishow->type == itm->type)
|
|
sc = ic;
|
|
else
|
|
report_item(owner, ishow, viewer, NULL, &sc, NULL, false);
|
|
if (sc == ic || strcmp(sc, ic) == 0) {
|
|
ishow->number += itm->number;
|
|
break;
|
|
}
|
|
}
|
|
if (ishow == result + n) {
|
|
if (n == size) {
|
|
log_error("too many items to report, increase buffer size.\n");
|
|
return -1;
|
|
}
|
|
result[n].number = itm->number;
|
|
result[n].type = itm->type;
|
|
result[n].next = (n + 1 == size) ? NULL : result + n + 1;
|
|
++n;
|
|
}
|
|
}
|
|
}
|
|
if (n > 0)
|
|
result[n - 1].next = NULL;
|
|
return n;
|
|
}
|
|
|
|
static void report_resource(resource_report * result, const resource_type *rtype,
|
|
int number, int level)
|
|
{
|
|
assert(rtype);
|
|
result->rtype = rtype;
|
|
result->number = number;
|
|
result->level = level;
|
|
}
|
|
|
|
static void bufattack(struct sbstring *sbp, const struct locale *lang, const char *name, const char *dmg) {
|
|
sbs_strcat(sbp, LOC(lang, name));
|
|
if (dmg) {
|
|
sbs_strcat(sbp, " (");
|
|
sbs_strcat(sbp, dmg);
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
|
|
void report_raceinfo(const struct race *rc, const struct locale *lang, struct sbstring *sbp)
|
|
{
|
|
const char *info;
|
|
int a, at_count;
|
|
const char *name, *key;
|
|
|
|
name = rc_name_s(rc, NAME_SINGULAR);
|
|
sbs_strcat(sbp, LOC(lang, name));
|
|
sbs_strcat(sbp, ": ");
|
|
|
|
key = mkname("raceinfo", rc->_name);
|
|
info = locale_getstring(lang, key);
|
|
if (info == NULL) {
|
|
info = LOC(lang, mkname("raceinfo", "no_info"));
|
|
}
|
|
|
|
if (info) {
|
|
sbs_strcat(sbp, info);
|
|
}
|
|
|
|
/* hp_p : Trefferpunkte */
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, str_itoa(rc->hitpoints));
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_hitpoints"));
|
|
|
|
/* b_attacke : Angriff */
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_attack"));
|
|
sbs_strcat(sbp, ": ");
|
|
sbs_strcat(sbp, str_itoa(rc->at_default + rc->at_bonus));
|
|
|
|
/* b_defense : Verteidigung */
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_defense"));
|
|
sbs_strcat(sbp, ": ");
|
|
sbs_strcat(sbp, str_itoa(rc->df_default + rc->df_bonus));
|
|
|
|
/* b_armor : Ruestung */
|
|
if (rc->armor > 0) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_armor"));
|
|
sbs_strcat(sbp, ": ");
|
|
sbs_strcat(sbp, str_itoa(rc->armor));
|
|
}
|
|
|
|
sbs_strcat(sbp, ".");
|
|
|
|
/* b_damage : Schaden */
|
|
at_count = 0;
|
|
for (a = 0; a < RACE_ATTACKS; a++) {
|
|
if (rc->attack[a].type != AT_NONE) {
|
|
at_count++;
|
|
}
|
|
}
|
|
if (rc->battle_flags & BF_EQUIPMENT) {
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_equipment"));
|
|
}
|
|
if (rc->battle_flags & BF_RES_PIERCE) {
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_pierce"));
|
|
}
|
|
if (rc->battle_flags & BF_RES_CUT) {
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_cut"));
|
|
}
|
|
if (rc->battle_flags & BF_RES_BASH) {
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, "stat_bash"));
|
|
}
|
|
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, str_itoa(at_count));
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, LOC(lang, (at_count == 1) ? "stat_attack" : "stat_attacks"));
|
|
|
|
for (a = 0; a < RACE_ATTACKS; a++) {
|
|
if (rc->attack[a].type != AT_NONE) {
|
|
sbs_strcat(sbp, (a == 0) ? ": " : ", ");
|
|
|
|
switch (rc->attack[a].type) {
|
|
case AT_STANDARD:
|
|
bufattack(sbp, lang, "attack_standard", rc->def_damage);
|
|
break;
|
|
case AT_NATURAL:
|
|
bufattack(sbp, lang, "attack_natural", rc->attack[a].data.dice);
|
|
break;
|
|
case AT_SPELL:
|
|
case AT_COMBATSPELL:
|
|
case AT_DRAIN_ST:
|
|
case AT_DRAIN_EXP:
|
|
case AT_DAZZLE:
|
|
bufattack(sbp, lang, "attack_magical", NULL);
|
|
break;
|
|
case AT_STRUCTURAL:
|
|
bufattack(sbp, lang, "attack_structural", rc->attack[a].data.dice);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sbs_strcat(sbp, ".");
|
|
}
|
|
|
|
void
|
|
report_building(const struct building *b, const char **name,
|
|
const char **illusion)
|
|
{
|
|
if (name) {
|
|
*name = buildingtype(b->type, b, b->size);
|
|
}
|
|
if (illusion) {
|
|
*illusion = NULL;
|
|
|
|
if (b->attribs && is_building_type(b->type, "illusioncastle")) {
|
|
const attrib *a = a_find(b->attribs, &at_icastle);
|
|
if (a != NULL) {
|
|
*illusion = buildingtype(icastle_type(a), b, b->size);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
report_resources(const region * r, resource_report * result, int size,
|
|
const faction * viewer, bool see_unit)
|
|
{
|
|
int n = 0;
|
|
|
|
if (r->land) {
|
|
int peasants = rpeasants(r);
|
|
int money = rmoney(r);
|
|
int horses = rhorses(r);
|
|
int trees = rtrees(r, 2);
|
|
int saplings = rtrees(r, 1);
|
|
bool mallorn = fval(r, RF_MALLORN) != 0;
|
|
const resource_type *rtype;
|
|
|
|
if (saplings) {
|
|
if (n >= size)
|
|
return -1;
|
|
rtype = get_resourcetype(mallorn ? R_MALLORN_SAPLING : R_SAPLING);
|
|
report_resource(result + n, rtype, saplings, -1);
|
|
++n;
|
|
}
|
|
if (trees) {
|
|
if (n >= size)
|
|
return -1;
|
|
rtype = get_resourcetype(mallorn ? R_MALLORN_TREE : R_TREE);
|
|
report_resource(result + n, rtype, trees, -1);
|
|
++n;
|
|
}
|
|
if (money) {
|
|
if (n >= size)
|
|
return -1;
|
|
report_resource(result + n, get_resourcetype(R_SILVER), money, -1);
|
|
++n;
|
|
}
|
|
if (peasants) {
|
|
if (n >= size)
|
|
return -1;
|
|
report_resource(result + n, get_resourcetype(R_PEASANT), peasants, -1);
|
|
++n;
|
|
}
|
|
if (horses) {
|
|
if (n >= size)
|
|
return -1;
|
|
report_resource(result + n, get_resourcetype(R_HORSE), horses, -1);
|
|
++n;
|
|
}
|
|
}
|
|
|
|
if (see_unit) {
|
|
rawmaterial *res = r->resources;
|
|
while (res) {
|
|
const item_type *itype = resource2item(res->rtype);
|
|
int minskill = itype->construction->minskill;
|
|
skill_t skill = itype->construction->skill;
|
|
int level = res->level + minskill - 1;
|
|
int visible = -1;
|
|
rawmaterial_type *raw = rmt_get(res->rtype);
|
|
if (raw->visible == NULL) {
|
|
visible = res->amount;
|
|
level = res->level + minskill - 1;
|
|
}
|
|
else {
|
|
const unit *u;
|
|
int maxskill = 0;
|
|
for (u = r->units; visible != res->amount && u != NULL; u = u->next) {
|
|
if (u->faction == viewer) {
|
|
int s = effskill(u, skill, NULL);
|
|
if (s > maxskill) {
|
|
maxskill = s;
|
|
visible = raw->visible(res, maxskill);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (level >= 0 && visible >= 0) {
|
|
if (n >= size)
|
|
return -1;
|
|
report_resource(result + n, res->rtype, visible, level);
|
|
n++;
|
|
}
|
|
res = res->next;
|
|
}
|
|
}
|
|
return n;
|
|
}
|
|
|
|
static void spskill(sbstring *sbp, const struct locale * lang,
|
|
const struct unit * u, struct skill * sv, int *dh)
|
|
{
|
|
int effsk;
|
|
|
|
if (!u->number) {
|
|
return;
|
|
}
|
|
if (sv->level <= 0) {
|
|
if (sv->old <= 0 || (u->faction->options & WANT_OPTION(O_SHOWSKCHANGE)) == 0) {
|
|
return;
|
|
}
|
|
}
|
|
sbs_strcat(sbp, ", ");
|
|
|
|
if (!*dh) {
|
|
sbs_strcat(sbp, LOC(lang, "nr_skills"));
|
|
sbs_strcat(sbp, ": ");
|
|
*dh = 1;
|
|
}
|
|
sbs_strcat(sbp, skillname(sv->id, lang));
|
|
sbs_strcat(sbp, " ");
|
|
|
|
if (sv->id == SK_MAGIC) {
|
|
magic_t mtype = unit_get_magic(u);
|
|
if (mtype != M_GRAY) {
|
|
sbs_strcat(sbp, magic_name(mtype, lang));
|
|
sbs_strcat(sbp, " ");
|
|
}
|
|
}
|
|
|
|
if (sv->id == SK_STEALTH && fval(u, UFL_STEALTH)) {
|
|
int i = u_geteffstealth(u);
|
|
if (i >= 0) {
|
|
sbs_strcat(sbp, str_itoa(i));
|
|
sbs_strcat(sbp, "/");
|
|
}
|
|
}
|
|
|
|
effsk = eff_skill(u, sv, NULL);
|
|
sbs_strcat(sbp, str_itoa(effsk));
|
|
|
|
if (u->faction->options & WANT_OPTION(O_SHOWSKCHANGE)) {
|
|
int oldeff = 0;
|
|
int diff;
|
|
|
|
if (sv->old > 0) {
|
|
oldeff = sv->old + get_modifier(u, sv->id, sv->old, u->region, false);
|
|
if (oldeff < 0) oldeff = 0;
|
|
}
|
|
|
|
diff = effsk - oldeff;
|
|
|
|
if (diff != 0) {
|
|
sbs_strcat(sbp, " (");
|
|
sbs_strcat(sbp, (diff > 0) ? "+" : "");
|
|
sbs_strcat(sbp, str_itoa(diff));
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
}
|
|
|
|
void bufunit(const faction * f, const unit * u, const faction *fv,
|
|
seen_mode mode, int getarnt, struct sbstring *sbp)
|
|
{
|
|
int i, dh;
|
|
const char *pzTmp, *str;
|
|
bool isbattle = (mode == seen_battle);
|
|
item *itm, *show = NULL;
|
|
item results[MAX_INVENTORY];
|
|
const struct locale *lang = f->locale;
|
|
|
|
if (!fv) {
|
|
fv = visible_faction(f, u);
|
|
}
|
|
assert(f);
|
|
sbs_strcat(sbp, unitname(u));
|
|
if (!isbattle) {
|
|
if (u->faction == f) {
|
|
if (fval(u, UFL_GROUP)) {
|
|
group *g = get_group(u);
|
|
if (g) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, groupid(g, f));
|
|
}
|
|
}
|
|
if (getarnt) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "anonymous"));
|
|
}
|
|
else if (u->attribs) {
|
|
faction *otherf = get_otherfaction(u);
|
|
if (otherf) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, factionname(otherf));
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (getarnt) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "anonymous"));
|
|
}
|
|
else {
|
|
if (u->attribs && alliedunit(u, f, HELP_FSTEALTH)) {
|
|
faction *otherf = get_otherfaction(u);
|
|
if (otherf) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, factionname(otherf));
|
|
sbs_strcat(sbp, " (");
|
|
sbs_strcat(sbp, factionname(u->faction));
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, factionname(fv));
|
|
}
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, factionname(fv));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, str_itoa(u->number));
|
|
sbs_strcat(sbp, " ");
|
|
|
|
pzTmp = get_racename(u->attribs);
|
|
if (pzTmp) {
|
|
const char *name = locale_string(lang, mkname("race", pzTmp), false);
|
|
sbs_strcat(sbp, name ? name : pzTmp);
|
|
if (u->faction == f && fval(u_race(u), RCF_SHAPESHIFTANY)) {
|
|
sbs_strcat(sbp, " (");
|
|
sbs_strcat(sbp, racename(lang, u, u_race(u)));
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
else {
|
|
const race *irace = u_irace(u);
|
|
const race *urace = u_race(u);
|
|
sbs_strcat(sbp, racename(lang, u, irace));
|
|
if (u->faction == f && irace != urace) {
|
|
sbs_strcat(sbp, " (");
|
|
sbs_strcat(sbp, racename(lang, u, urace));
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
|
|
if (fval(u, UFL_HERO) && (u->faction == f || omniscient(f))) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "hero"));
|
|
}
|
|
/* status */
|
|
|
|
if (u->number && (u->faction == f || isbattle)) {
|
|
const char *c = hp_status(u);
|
|
c = c ? LOC(lang, c) : 0;
|
|
sbs_strcat(sbp, ", ");
|
|
report_status(u, lang, sbp);
|
|
if (c || fval(u, UFL_HUNGER)) {
|
|
sbs_strcat(sbp, " (");
|
|
if (c) {
|
|
sbs_strcat(sbp, c);
|
|
}
|
|
if (fval(u, UFL_HUNGER)) {
|
|
if (c) {
|
|
sbs_strcat(sbp, ", ");
|
|
}
|
|
sbs_strcat(sbp, LOC(lang, "unit_hungers"));
|
|
}
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
if (is_guard(u)) {
|
|
sbs_strcat(sbp, ", ");
|
|
sbs_strcat(sbp, LOC(lang, "unit_guards"));
|
|
}
|
|
|
|
dh = 0;
|
|
if (u->faction == f) {
|
|
skill *sv;
|
|
for (sv = u->skills; sv != u->skills + u->skill_size; ++sv) {
|
|
spskill(sbp, lang, u, sv, &dh);
|
|
}
|
|
}
|
|
|
|
dh = 0;
|
|
if (f == u->faction || omniscient(f)) {
|
|
show = u->items;
|
|
}
|
|
else if (mode >= seen_unit) {
|
|
int n = report_items(u, results, MAX_INVENTORY, u, f);
|
|
assert(n >= 0);
|
|
if (n > 0) {
|
|
show = results;
|
|
}
|
|
}
|
|
for (itm = show; itm; itm = itm->next) {
|
|
const char *ic;
|
|
int in;
|
|
report_item(u, itm, f, &ic, NULL, &in, false);
|
|
if (in == 0 || ic == NULL) {
|
|
continue;
|
|
}
|
|
sbs_strcat(sbp, ", ");
|
|
|
|
if (!dh) {
|
|
sbs_strcat(sbp, LOC(lang, "nr_inventory"));
|
|
sbs_strcat(sbp, ": ");
|
|
dh = 1;
|
|
}
|
|
if (in == 1) {
|
|
sbs_strcat(sbp, ic);
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, str_itoa(in));
|
|
sbs_strcat(sbp, " ");
|
|
sbs_strcat(sbp, ic);
|
|
}
|
|
}
|
|
|
|
if (u->faction == f) {
|
|
spellbook *book = unit_get_spellbook(u);
|
|
|
|
if (book) {
|
|
selist *ql = book->spells;
|
|
int qi, header, maxlevel = effskill(u, SK_MAGIC, NULL);
|
|
sbs_printf(sbp, ". Aura %d/%d", get_spellpoints(u), max_spellpoints(u, NULL));
|
|
|
|
for (header = 0, qi = 0; ql; selist_advance(&ql, &qi, 1)) {
|
|
spellbook_entry * sbe = (spellbook_entry *)selist_get(ql, qi);
|
|
const spell *sp = spellref_get(&sbe->spref);
|
|
if (sbe->level <= maxlevel) {
|
|
if (!header) {
|
|
sbs_printf(sbp, ", %s: ", LOC(lang, "nr_spells"));
|
|
header = 1;
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, ", ");
|
|
}
|
|
/* TODO: no need to deref the spellref here (spref->name is good) */
|
|
sbs_strcat(sbp, spell_name(sp, lang));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i != MAXCOMBATSPELLS; ++i) {
|
|
if (get_combatspell(u, i))
|
|
break;
|
|
}
|
|
if (i != MAXCOMBATSPELLS) {
|
|
sbs_printf(sbp, ", %s: ", LOC(lang, "nr_combatspells"));
|
|
dh = 0;
|
|
for (i = 0; i < MAXCOMBATSPELLS; i++) {
|
|
const spell *sp;
|
|
if (!dh) {
|
|
dh = 1;
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, ", ");
|
|
}
|
|
sp = get_combatspell(u, i);
|
|
if (sp) {
|
|
int sl = get_combatspelllevel(u, i);
|
|
sbs_strcat(sbp, spell_name(sp, lang));
|
|
if (sl > 0) {
|
|
sbs_printf(sbp, "(%d)", sl);
|
|
}
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, LOC(lang, "nr_nospells"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!isbattle) {
|
|
int printed = 0;
|
|
order *ord;
|
|
for (ord = u->old_orders; ord; ord = ord->next) {
|
|
keyword_t kwd = getkeyword(ord);
|
|
if (is_repeated(kwd)) {
|
|
if (printed >= ORDERS_IN_NR) {
|
|
break;
|
|
}
|
|
buforder(sbp, ord, u->faction->locale, printed++);
|
|
}
|
|
}
|
|
if (printed < ORDERS_IN_NR) {
|
|
for (ord = u->orders; ord; ord = ord->next) {
|
|
keyword_t kwd = getkeyword(ord);
|
|
if (is_repeated(kwd)) {
|
|
if (printed >= ORDERS_IN_NR) {
|
|
break;
|
|
}
|
|
buforder(sbp, ord, lang, printed++);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
i = 0;
|
|
|
|
str = u_description(u, lang);
|
|
if (str) {
|
|
sbs_strcat(sbp, "; ");
|
|
sbs_strcat(sbp, str);
|
|
i = str[strlen(str) - 1];
|
|
}
|
|
if (i != '!' && i != '?' && i != '.') {
|
|
sbs_strcat(sbp, ".");
|
|
}
|
|
pzTmp = uprivate(u);
|
|
if (u->faction == f && pzTmp) {
|
|
sbs_strcat(sbp, " (Bem: ");
|
|
sbs_strcat(sbp, pzTmp);
|
|
sbs_strcat(sbp, ")");
|
|
}
|
|
}
|
|
|
|
int bufunit_depr(const faction * f, const unit * u, seen_mode mode,
|
|
char *buf, size_t size)
|
|
{
|
|
int getarnt = fval(u, UFL_ANON_FACTION);
|
|
const faction * fv = visible_faction(f, u);
|
|
sbstring sbs;
|
|
|
|
sbs_init(&sbs, buf, size);
|
|
bufunit(f, u, fv, mode, getarnt, &sbs);
|
|
if (!getarnt) {
|
|
if (alliedfaction(f, fv, HELP_ALL)) {
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void split_paragraph(strlist ** SP, const char *s, unsigned int indent, unsigned int width, char mark)
|
|
{
|
|
bool firstline;
|
|
static char buf[REPORTWIDTH + 1]; /* FIXME: static buffer, artificial limit */
|
|
size_t len = strlen(s);
|
|
|
|
assert(width <= REPORTWIDTH);
|
|
width -= indent;
|
|
firstline = (mark != 0 && indent > 2);
|
|
*SP = 0;
|
|
|
|
while (len > 0) {
|
|
unsigned int j;
|
|
const char *cut = 0, *space = strchr(s, ' ');
|
|
while (space && *space && (space - s) <= (ptrdiff_t)width) {
|
|
cut = space;
|
|
space = strchr(space + 1, ' ');
|
|
if (!space && len < width) {
|
|
cut = space = s + len;
|
|
}
|
|
}
|
|
|
|
for (j = 0; j != indent; j++)
|
|
buf[j] = ' ';
|
|
|
|
if (firstline) {
|
|
buf[indent - 2] = mark;
|
|
firstline = false;
|
|
}
|
|
if (!cut) {
|
|
cut = s + ((len < REPORTWIDTH) ? len : REPORTWIDTH);
|
|
}
|
|
memcpy(buf + indent, s, cut - s);
|
|
buf[indent + (cut - s)] = 0;
|
|
addstrlist(SP, buf); /* TODO: too much string copying, cut out this function */
|
|
while (*cut == ' ') {
|
|
++cut;
|
|
}
|
|
len -= (cut - s);
|
|
s = cut;
|
|
}
|
|
}
|
|
|
|
void sparagraph(strlist ** SP, const char *s, unsigned int indent, char mark)
|
|
{
|
|
split_paragraph(SP, s, indent, REPORTWIDTH, mark);
|
|
}
|
|
|
|
void lparagraph(struct strlist **SP, char *s, unsigned int indent, char mark)
|
|
{
|
|
/* Die Liste SP wird mit dem String s aufgefuellt, mit indent und einer
|
|
* mark, falls angegeben. SP wurde also auf 0 gesetzt vor dem Aufruf.
|
|
* Vgl. spunit (). */
|
|
|
|
char *buflocal = calloc(strlen(s) + indent + 1, sizeof(char));
|
|
if (!buflocal) abort();
|
|
|
|
if (indent) {
|
|
memset(buflocal, ' ', indent);
|
|
if (mark)
|
|
buflocal[indent - 2] = mark;
|
|
}
|
|
strcpy(buflocal + indent, s);
|
|
addstrlist(SP, buflocal);
|
|
free(buflocal);
|
|
}
|
|
|
|
void
|
|
spunit(struct strlist **SP, const struct faction *f, const unit * u, unsigned int indent,
|
|
seen_mode mode)
|
|
{
|
|
char buf[DISPLAYSIZE];
|
|
int dh = bufunit_depr(f, u, mode, buf, sizeof(buf));
|
|
lparagraph(SP, buf, indent,
|
|
(char)((u->faction == f) ? '*' : (dh ? '+' : '-')));
|
|
}
|
|
|
|
struct message *msg_curse(const struct curse *c, const void *obj, objtype_t typ,
|
|
int self)
|
|
{
|
|
if (c->type->curseinfo) {
|
|
/* if curseinfo returns NULL, then we don't want to tell the viewer anything. */
|
|
return c->type->curseinfo(obj, typ, c, self);
|
|
}
|
|
else {
|
|
message *msg = cinfo_simple(obj, typ, c, self);
|
|
if (msg == NULL) {
|
|
const char *unknown[] =
|
|
{ "unit_unknown", "region_unknown", "building_unknown",
|
|
"ship_unknown" };
|
|
msg = msg_message(mkname("curseinfo", unknown[typ]), "id", c->no);
|
|
log_warning("no curseinfo function for %s and no fallback either.\n", c->type->cname);
|
|
}
|
|
else {
|
|
log_debug("no curseinfo function for %s, using cinfo_simple fallback.\n", c->type->cname);
|
|
}
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
int stealth_modifier(const region *r, const faction *f, seen_mode mode)
|
|
{
|
|
switch (mode) {
|
|
case seen_spell:
|
|
return get_observer(r, f);
|
|
case seen_unit:
|
|
return 0;
|
|
case seen_lighthouse:
|
|
return -2;
|
|
case seen_travel:
|
|
return -1;
|
|
default:
|
|
return INT_MIN;
|
|
}
|
|
}
|
|
|
|
static void transfer_seen(selist ** dst, selist ** src) {
|
|
assert(!*dst);
|
|
*dst = *src;
|
|
*src = NULL;
|
|
}
|
|
|
|
int cmp_faction(const void *lhs, const void *rhs) {
|
|
const faction *lhf = (const faction *)lhs;
|
|
const faction *rhf = (const faction *)rhs;
|
|
if (lhf->no == rhf->no) return 0;
|
|
if (lhf->no > rhf->no) return 1;
|
|
return -1;
|
|
}
|
|
|
|
static void add_seen_faction_i(struct selist **flist, faction *f) {
|
|
selist_set_insert(flist, f, cmp_faction);
|
|
}
|
|
|
|
void add_seen_faction(faction *self, faction *seen) {
|
|
add_seen_faction_i(&self->seen_factions, seen);
|
|
}
|
|
|
|
typedef struct address_data {
|
|
faction *f, *lastf;
|
|
selist **flist;
|
|
int stealthmod;
|
|
} address_data;
|
|
|
|
static void cb_add_address(region *r, unit *ut, void *cbdata) {
|
|
address_data *data = (address_data *)cbdata;
|
|
faction *f = data->f;
|
|
|
|
if (ut->faction == f) {
|
|
unit *u;
|
|
for (u = r->units; u; u = u->next) {
|
|
faction *sf = visible_faction(f, u);
|
|
assert(u->faction != f); /* if this is see_travel only, then I shouldn't be here. */
|
|
if (data->lastf != sf && cansee_unit(ut, u, data->stealthmod)) {
|
|
add_seen_faction_i(data->flist, sf);
|
|
data->lastf = sf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void add_travelthru_addresses(region *r, faction *f, selist **flist, int stealthmod) {
|
|
/* for each traveling unit: add the faction of any unit is can see */
|
|
address_data cbdata = { 0 };
|
|
cbdata.f = f;
|
|
cbdata.flist = flist;
|
|
cbdata.stealthmod = stealthmod;
|
|
travelthru_map(r, cb_add_address, &cbdata);
|
|
}
|
|
|
|
void get_addresses(report_context * ctx)
|
|
{
|
|
/* "TODO: travelthru" */
|
|
region *r;
|
|
const faction *lastf = NULL;
|
|
selist *flist = 0;
|
|
|
|
transfer_seen(&flist, &ctx->f->seen_factions);
|
|
|
|
ctx->f->seen_factions = NULL; /* do not delete it twice */
|
|
selist_push(&flist, ctx->f);
|
|
|
|
if (f_get_alliance(ctx->f)) {
|
|
selist *ql = ctx->f->alliance->members;
|
|
int qi;
|
|
for (qi = 0; ql; selist_advance(&ql, &qi, 1)) {
|
|
add_seen_faction_i(&flist, (faction *)selist_get(ql, qi));
|
|
}
|
|
}
|
|
|
|
/* find the first region that this faction can see */
|
|
for (r = ctx->first; r != ctx->last; r = r->next) {
|
|
if (r->seen.mode > seen_none) break;
|
|
}
|
|
|
|
for (; r != NULL; r = r->next) {
|
|
if (r->seen.mode >= seen_lighthouse) {
|
|
int stealthmod = stealth_modifier(r, ctx->f, r->seen.mode);
|
|
if (r->seen.mode == seen_lighthouse) {
|
|
unit *u = r->units;
|
|
for (; u; u = u->next) {
|
|
faction *sf = visible_faction(ctx->f, u);
|
|
if (lastf != sf) {
|
|
if (u->building || u->ship || (stealthmod > INT_MIN
|
|
&& cansee(ctx->f, r, u, stealthmod))) {
|
|
add_seen_faction_i(&flist, sf);
|
|
lastf = sf;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (r->seen.mode == seen_travel) {
|
|
/* when we travel through a region, then we must add
|
|
* the factions of any units we saw */
|
|
add_travelthru_addresses(r, ctx->f, &flist, stealthmod);
|
|
}
|
|
else if (r->seen.mode > seen_travel) {
|
|
const unit *u = r->units;
|
|
while (u != NULL) {
|
|
if (u->faction != ctx->f) {
|
|
faction *sf = visible_faction(ctx->f, u);
|
|
bool ballied = sf && sf != ctx->f && sf != lastf
|
|
&& !fval(u, UFL_ANON_FACTION) && cansee(ctx->f, r, u, stealthmod);
|
|
if (ballied || is_allied(ctx->f, sf)) {
|
|
add_seen_faction_i(&flist, sf);
|
|
lastf = sf;
|
|
}
|
|
}
|
|
u = u->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (f_get_alliance(ctx->f)) {
|
|
faction *f2;
|
|
for (f2 = factions; f2; f2 = f2->next) {
|
|
if (f2->alliance == ctx->f->alliance) {
|
|
add_seen_faction_i(&flist, f2);
|
|
}
|
|
}
|
|
}
|
|
ctx->addresses = flist;
|
|
}
|
|
|
|
typedef struct report_type {
|
|
struct report_type *next;
|
|
report_fun write;
|
|
const char *extension;
|
|
int flag;
|
|
} report_type;
|
|
|
|
static report_type *report_types;
|
|
|
|
void register_reporttype(const char *extension, report_fun write, int flag)
|
|
{
|
|
report_type *type = (report_type *)malloc(sizeof(report_type));
|
|
if (!type) abort();
|
|
type->extension = extension;
|
|
type->write = write;
|
|
type->flag = flag;
|
|
type->next = report_types;
|
|
report_types = type;
|
|
}
|
|
|
|
void reports_done(void) {
|
|
report_type **rtp = &report_types;
|
|
while (*rtp) {
|
|
report_type *rt = *rtp;
|
|
*rtp = rt->next;
|
|
free(rt);
|
|
}
|
|
}
|
|
|
|
int get_regions_distance_arr(region *rc, int radius, region *result[], int size)
|
|
{
|
|
int n = 0, i;
|
|
|
|
if (size > n) {
|
|
result[n++] = rc;
|
|
fset(rc, RF_MARK);
|
|
}
|
|
for (i = 0; i != n; ++i) {
|
|
region *r;
|
|
int dist;
|
|
|
|
r = result[i];
|
|
dist = distance(rc, r);
|
|
if (dist < radius) {
|
|
region *adj[MAXDIRECTIONS];
|
|
int d;
|
|
|
|
get_neighbours(r, adj);
|
|
for (d = 0; d != MAXDIRECTIONS; ++d) {
|
|
r = adj[d];
|
|
if (r) {
|
|
if (size > n) {
|
|
if (!fval(r, RF_MARK) && dist < distance(rc, r)) {
|
|
result[n++] = r;
|
|
fset(r, RF_MARK);
|
|
}
|
|
}
|
|
else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (i = 0; i != n; ++i) {
|
|
freset(result[i], RF_MARK);
|
|
}
|
|
return n;
|
|
}
|
|
|
|
selist *get_regions_distance(region * root, int radius)
|
|
{
|
|
selist *ql, *rlist = NULL;
|
|
int qi = 0;
|
|
|
|
selist_push(&rlist, root);
|
|
fset(root, RF_MARK);
|
|
ql = rlist;
|
|
|
|
while (ql) {
|
|
region *r = (region *)selist_get(ql, qi);
|
|
region * next[MAXDIRECTIONS];
|
|
int d;
|
|
get_neighbours(r, next);
|
|
|
|
for (d = 0; d != MAXDIRECTIONS; ++d) {
|
|
if (next[d] && !fval(next[d], RF_MARK) && distance(next[d], root) <= radius) {
|
|
selist_push(&rlist, next[d]);
|
|
fset(next[d], RF_MARK);
|
|
}
|
|
}
|
|
selist_advance(&ql, &qi, 1);
|
|
}
|
|
for (ql = rlist, qi = 0; ql; selist_advance(&ql, &qi, 1)) {
|
|
region *r = (region *)selist_get(ql, qi);
|
|
freset(r, RF_MARK);
|
|
}
|
|
return rlist;
|
|
}
|
|
|
|
static void add_seen(region *r, seen_mode mode) {
|
|
if (r->seen.mode < mode) {
|
|
r->seen.mode = mode;
|
|
}
|
|
}
|
|
|
|
static void add_seen_nb(faction *f, region *r, seen_mode mode) {
|
|
region *first = r, *last = r;
|
|
add_seen(r, mode);
|
|
if (mode > seen_neighbour) {
|
|
region *next[MAXDIRECTIONS];
|
|
int d;
|
|
get_neighbours(r, next);
|
|
for (d = 0; d != MAXDIRECTIONS; ++d) {
|
|
region *rn = next[d];
|
|
if (rn && rn->seen.mode < seen_neighbour) {
|
|
rn->seen.mode = seen_neighbour;
|
|
if (first->index > rn->index) first = rn;
|
|
if (last->index < rn->index) last = rn;
|
|
}
|
|
}
|
|
}
|
|
update_interval(f, first);
|
|
update_interval(f, last);
|
|
}
|
|
|
|
static void add_seen_lighthouse(region *r, faction *f)
|
|
{
|
|
if (r->terrain->flags & SEA_REGION) {
|
|
add_seen_nb(f, r, seen_lighthouse);
|
|
}
|
|
else {
|
|
add_seen_nb(f, r, seen_lighthouse_land);
|
|
}
|
|
}
|
|
|
|
/** mark all regions seen by the lighthouse.
|
|
*/
|
|
static void prepare_lighthouse_ql(faction *f, selist *rlist) {
|
|
selist *ql;
|
|
int qi;
|
|
|
|
for (ql = rlist, qi = 0; ql; selist_advance(&ql, &qi, 1)) {
|
|
region *rl = (region *)selist_get(ql, qi);
|
|
add_seen_lighthouse(rl, f);
|
|
}
|
|
}
|
|
|
|
static void prepare_lighthouse(faction *f, region *r, int range)
|
|
{
|
|
if (range > 3) {
|
|
selist *rlist = get_regions_distance(r, range);
|
|
prepare_lighthouse_ql(f, rlist);
|
|
selist_free(rlist);
|
|
}
|
|
else {
|
|
region *result[64];
|
|
int n, i;
|
|
|
|
n = get_regions_distance_arr(r, range, result, 64);
|
|
assert(n > 0 && n <= 64);
|
|
for (i = 0; i != n; ++i) {
|
|
region *rl = result[i];
|
|
add_seen_lighthouse(rl, f);
|
|
}
|
|
}
|
|
}
|
|
|
|
void reorder_units(region * r)
|
|
{
|
|
unit **unext = &r->units;
|
|
|
|
if (r->buildings) {
|
|
building *b = r->buildings;
|
|
while (*unext && b) {
|
|
unit **ufirst = unext; /* where the first unit in the building should go */
|
|
unit **umove = unext; /* a unit we consider moving */
|
|
unit *owner = building_owner(b);
|
|
while (owner && *umove) {
|
|
unit *u = *umove;
|
|
if (u->building == b) {
|
|
unit **uinsert = unext;
|
|
if (u == owner) {
|
|
uinsert = ufirst;
|
|
}
|
|
if (umove != uinsert) {
|
|
*umove = u->next;
|
|
u->next = *uinsert;
|
|
*uinsert = u;
|
|
}
|
|
else {
|
|
/* no need to move, skip ahead */
|
|
umove = &u->next;
|
|
}
|
|
if (unext == uinsert) {
|
|
/* we have a new well-placed unit. jump over it */
|
|
unext = &u->next;
|
|
}
|
|
}
|
|
else {
|
|
umove = &u->next;
|
|
}
|
|
}
|
|
b = b->next;
|
|
}
|
|
}
|
|
|
|
if (r->ships) {
|
|
ship *sh = r->ships;
|
|
/* first, move all units up that are not on ships */
|
|
unit **umove = unext; /* a unit we consider moving */
|
|
while (*umove) {
|
|
unit *u = *umove;
|
|
if (u->number && !u->ship) {
|
|
if (umove != unext) {
|
|
*umove = u->next;
|
|
u->next = *unext;
|
|
*unext = u;
|
|
}
|
|
else {
|
|
/* no need to move, skip ahead */
|
|
umove = &u->next;
|
|
}
|
|
/* we have a new well-placed unit. jump over it */
|
|
unext = &u->next;
|
|
}
|
|
else {
|
|
umove = &u->next;
|
|
}
|
|
}
|
|
|
|
while (*unext && sh) {
|
|
unit **ufirst = unext; /* where the first unit in the building should go */
|
|
unit *owner = ship_owner(sh);
|
|
umove = unext;
|
|
while (owner && *umove) {
|
|
unit *u = *umove;
|
|
if (u->number && u->ship == sh) {
|
|
unit **uinsert = unext;
|
|
if (u == owner) {
|
|
uinsert = ufirst;
|
|
owner = u;
|
|
}
|
|
if (umove != uinsert) {
|
|
*umove = u->next;
|
|
u->next = *uinsert;
|
|
*uinsert = u;
|
|
}
|
|
else {
|
|
/* no need to move, skip ahead */
|
|
umove = &u->next;
|
|
}
|
|
if (unext == uinsert) {
|
|
/* we have a new well-placed unit. jump over it */
|
|
unext = &u->next;
|
|
}
|
|
}
|
|
else {
|
|
umove = &u->next;
|
|
}
|
|
}
|
|
sh = sh->next;
|
|
}
|
|
}
|
|
}
|
|
|
|
static region *lastregion(faction * f)
|
|
{
|
|
if (!f->units) {
|
|
return NULL;
|
|
}
|
|
return f->last ? f->last->next : NULL;
|
|
}
|
|
|
|
static region *firstregion(faction * f)
|
|
{
|
|
region *r = f->first;
|
|
|
|
if (f->units == NULL)
|
|
return NULL;
|
|
if (r != NULL)
|
|
return r;
|
|
|
|
return f->first = regions;
|
|
}
|
|
|
|
static void cb_add_seen(region *r, unit *u, void *cbdata) {
|
|
faction *f = (faction *)cbdata;
|
|
if (u->faction == f) {
|
|
add_seen_nb(f, r, seen_travel);
|
|
}
|
|
}
|
|
|
|
void report_warnings(faction *f, int now)
|
|
{
|
|
if (f->age < NewbieImmunity()) {
|
|
ADDMSG(&f->msgs, msg_message("newbieimmunity", "turns",
|
|
NewbieImmunity() - f->age));
|
|
}
|
|
|
|
if (f->race == get_race(RC_INSECT)) {
|
|
season_t season = calendar_season(now + 1);
|
|
|
|
if (season == SEASON_WINTER) {
|
|
ADDMSG(&f->msgs, msg_message("nr_insectwinter", ""));
|
|
}
|
|
else if (season == SEASON_AUTUMN) {
|
|
/* warning: next turn is the last week of autumn */
|
|
if (calendar_season(now + 2) == SEASON_WINTER) {
|
|
ADDMSG(&f->msgs, msg_message("nr_insectfall", ""));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** set region.seen based on visibility by one faction.
|
|
*
|
|
* this function may also update ctx->last and ctx->first for potential
|
|
* lighthouses and travelthru reports
|
|
*/
|
|
void prepare_report(report_context *ctx, faction *f, const char *password)
|
|
{
|
|
region *r;
|
|
static int config;
|
|
static bool rule_region_owners;
|
|
static bool rule_lighthouse_units;
|
|
const struct building_type *bt_lighthouse = bt_find("lighthouse");
|
|
|
|
/* Insekten-Winter-Warnung */
|
|
report_warnings(f, turn);
|
|
|
|
if (bt_lighthouse && config_changed(&config)) {
|
|
rule_region_owners = config_token("rules.region_owner_pay_building", bt_lighthouse->_name);
|
|
rule_lighthouse_units = config_get_int("rules.lighthouse.unit_capacity", 0) != 0;
|
|
}
|
|
|
|
ctx->password = password;
|
|
ctx->f = f;
|
|
ctx->report_time = time(NULL);
|
|
ctx->addresses = NULL;
|
|
ctx->userdata = NULL;
|
|
/* [first,last) interval of regions with a unit in it: */
|
|
if (f->units) {
|
|
ctx->first = firstregion(f);
|
|
ctx->last = lastregion(f);
|
|
|
|
for (r = ctx->first; r != ctx->last; r = r->next) {
|
|
unit *u;
|
|
building *b;
|
|
int br = 0, c = 0, range = 0;
|
|
if (fval(r, RF_OBSERVER)) {
|
|
int skill = get_observer(r, f);
|
|
if (skill >= 0) {
|
|
add_seen_nb(f, r, seen_spell);
|
|
}
|
|
}
|
|
if (fval(r, RF_LIGHTHOUSE)) {
|
|
/* region owners get the report from lighthouses */
|
|
if (rule_region_owners && f == region_get_owner(r)) {
|
|
for (b = rbuildings(r); b; b = b->next) {
|
|
if (b && b->type == bt_lighthouse) {
|
|
/* region owners get maximum range */
|
|
int lhr = lighthouse_view_distance(b, NULL);
|
|
if (lhr > range) range = lhr;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
b = NULL;
|
|
for (u = r->units; u; u = u->next) {
|
|
/* if we have any unit in this region, then we get seen_unit access */
|
|
if (u->faction == f) {
|
|
add_seen_nb(f, r, seen_unit);
|
|
/* units inside the lighthouse get range based on their perception
|
|
* or the size, if perception is not a skill
|
|
*/
|
|
if (!fval(r, RF_LIGHTHOUSE)) {
|
|
/* it's enough to add the region once, and if there are
|
|
* no lighthouses here, there is no need to look at more units */
|
|
break;
|
|
}
|
|
}
|
|
if (range == 0 && u->building && u->building->type == bt_lighthouse) {
|
|
if (u->building && b != u->building) {
|
|
b = u->building;
|
|
c = buildingcapacity(b);
|
|
br = 0;
|
|
}
|
|
if (rule_lighthouse_units) {
|
|
--c;
|
|
}
|
|
else {
|
|
c -= u->number;
|
|
}
|
|
if (u->faction == f && c >= 0) {
|
|
/* unit is one of ours, and inside the current lighthouse */
|
|
if (br == 0) {
|
|
/* lazy-calculate the range */
|
|
br = lighthouse_view_distance(b, u);
|
|
if (br > range) {
|
|
range = br;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (range > 0) {
|
|
/* we are in at least one lighthouse. add the regions we can see from here! */
|
|
prepare_lighthouse(f, r, range);
|
|
}
|
|
|
|
if (fval(r, RF_TRAVELUNIT) && r->seen.mode < seen_travel) {
|
|
travelthru_map(r, cb_add_seen, f);
|
|
}
|
|
}
|
|
}
|
|
/* [fast,last) interval of seen regions (with lighthouses and travel)
|
|
* TODO: what about neighbours? when are they included? do we need
|
|
* them outside of the CR? */
|
|
ctx->first = firstregion(f);
|
|
ctx->last = lastregion(f);
|
|
}
|
|
|
|
void finish_reports(report_context *ctx) {
|
|
region *r;
|
|
selist_free(ctx->addresses);
|
|
for (r = ctx->first; r != ctx->last; r = r->next) {
|
|
r->seen.mode = seen_none;
|
|
}
|
|
}
|
|
|
|
int write_reports(faction * f)
|
|
{
|
|
bool gotit = false;
|
|
struct report_context ctx;
|
|
const unsigned char utf8_bom[4] = { 0xef, 0xbb, 0xbf, 0 };
|
|
report_type *rtype;
|
|
char buffer[PASSWORD_MAXSIZE], *password = NULL;
|
|
if (noreports) {
|
|
return false;
|
|
}
|
|
if (f->lastorders == 0 || f->age <= 1) {
|
|
/* neue Parteien, oder solche die noch NIE einen Zug gemacht haben,
|
|
* kriegen ein neues Passwort: */
|
|
password = faction_genpassword(f, buffer);
|
|
}
|
|
prepare_report(&ctx, f, password);
|
|
get_addresses(&ctx);
|
|
log_debug("Reports for %s", factionname(f));
|
|
for (rtype = report_types; rtype != NULL; rtype = rtype->next) {
|
|
if (f->options & rtype->flag) {
|
|
int error = 0;
|
|
do {
|
|
char filename[32];
|
|
char path[4096];
|
|
sprintf(filename, "%d-%s.%s", turn, itoa36(f->no),
|
|
rtype->extension);
|
|
path_join(reportpath(), filename, path, sizeof(path));
|
|
errno = 0;
|
|
if (rtype->write(path, &ctx, (const char *)utf8_bom) == 0) {
|
|
gotit = true;
|
|
}
|
|
if (errno) {
|
|
error = errno;
|
|
log_fatal("error %d during %s report for faction %s: %s", errno, rtype->extension, factionname(f), strerror(error));
|
|
errno = 0;
|
|
}
|
|
} while (error);
|
|
}
|
|
}
|
|
if (!gotit) {
|
|
log_warning("No report for faction %s!", itoa36(f->no));
|
|
}
|
|
finish_reports(&ctx);
|
|
return 0;
|
|
}
|
|
|
|
static void write_script(FILE * F, const faction * f)
|
|
{
|
|
report_type *rtype;
|
|
char buf[1024];
|
|
|
|
if (check_email(faction_getemail(f)) != 0) {
|
|
return;
|
|
}
|
|
|
|
fprintf(F, "faction=%s:email=%s:lang=%s", itoa36(f->no), faction_getemail(f),
|
|
locale_name(f->locale));
|
|
if (f->options & (1 << O_BZIP2))
|
|
fputs(":compression=bz2", F);
|
|
else
|
|
fputs(":compression=zip", F);
|
|
|
|
fputs(":reports=", F);
|
|
buf[0] = 0;
|
|
for (rtype = report_types; rtype != NULL; rtype = rtype->next) {
|
|
if (f->options & rtype->flag) {
|
|
if (buf[0]) {
|
|
str_strlcat(buf, ",", sizeof(buf));
|
|
}
|
|
str_strlcat(buf, rtype->extension, sizeof(buf));
|
|
}
|
|
}
|
|
fputs(buf, F);
|
|
fputc('\n', F);
|
|
}
|
|
|
|
static void check_messages_exist(void) {
|
|
ct_checknames();
|
|
}
|
|
|
|
int init_reports(void)
|
|
{
|
|
region *r;
|
|
check_messages_exist();
|
|
create_directories();
|
|
for (r = regions; r; r = r->next) {
|
|
reorder_units(r);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int reports(void)
|
|
{
|
|
faction *f;
|
|
FILE *mailit;
|
|
int retval = 0;
|
|
char path[PATH_MAX];
|
|
const char * rpath = reportpath();
|
|
|
|
log_info("Writing reports for turn %d:", turn);
|
|
report_donations();
|
|
remove_empty_units();
|
|
|
|
path_join(rpath, "reports.txt", path, sizeof(path));
|
|
mailit = fopen(path, "w");
|
|
if (mailit == NULL) {
|
|
log_error("%s could not be opened!\n", path);
|
|
}
|
|
|
|
for (f = factions; f; f = f->next) {
|
|
if (f->email && !fval(f, FFL_NPC)) {
|
|
int error = write_reports(f);
|
|
if (error)
|
|
retval = error;
|
|
if (mailit)
|
|
write_script(mailit, f);
|
|
}
|
|
}
|
|
if (mailit)
|
|
fclose(mailit);
|
|
return retval;
|
|
}
|
|
|
|
static variant var_copy_string(variant x)
|
|
{
|
|
x.v = x.v ? str_strdup((const char *)x.v) : 0;
|
|
return x;
|
|
}
|
|
|
|
static void var_free_string(variant x)
|
|
{
|
|
free(x.v);
|
|
}
|
|
|
|
static variant var_copy_order(variant x)
|
|
{
|
|
x.v = copy_order((order *)x.v);
|
|
return x;
|
|
}
|
|
|
|
static void var_free_order(variant x)
|
|
{
|
|
free_order(x.v);
|
|
}
|
|
|
|
static variant var_copy_items(variant x)
|
|
{
|
|
item *isrc;
|
|
resource *rdst = NULL, **rptr = &rdst;
|
|
|
|
for (isrc = (item *)x.v; isrc != NULL; isrc = isrc->next) {
|
|
resource *res = malloc(sizeof(resource));
|
|
if (!res) abort();
|
|
res->number = isrc->number;
|
|
res->type = isrc->type->rtype;
|
|
*rptr = res;
|
|
rptr = &res->next;
|
|
}
|
|
*rptr = NULL;
|
|
x.v = rdst;
|
|
return x;
|
|
}
|
|
|
|
static variant var_copy_resources(variant x)
|
|
{
|
|
resource *rsrc;
|
|
resource *rdst = NULL, **rptr = &rdst;
|
|
|
|
for (rsrc = (resource *)x.v; rsrc != NULL; rsrc = rsrc->next) {
|
|
resource *res = malloc(sizeof(resource));
|
|
if (!res) abort();
|
|
res->number = rsrc->number;
|
|
res->type = rsrc->type;
|
|
*rptr = res;
|
|
rptr = &res->next;
|
|
}
|
|
*rptr = NULL;
|
|
x.v = rdst;
|
|
return x;
|
|
}
|
|
|
|
static void var_free_resources(variant x)
|
|
{
|
|
resource *rsrc = (resource *)x.v;
|
|
while (rsrc) {
|
|
resource *res = rsrc->next;
|
|
free(rsrc);
|
|
rsrc = res;
|
|
}
|
|
x.v = 0;
|
|
}
|
|
|
|
static void var_free_regions(variant x) /*-V524 */
|
|
{
|
|
free(x.v);
|
|
}
|
|
|
|
const char *trailinto(const region * r, const struct locale *lang)
|
|
{
|
|
if (r) {
|
|
static char ref[32];
|
|
const char *s;
|
|
const char *tname = terrain_name(r);
|
|
size_t sz;
|
|
|
|
sz = str_strlcpy(ref, tname, sizeof(ref));
|
|
sz += str_strlcat(ref + sz, "_trail", sizeof(ref) - sz);
|
|
s = LOC(lang, ref);
|
|
if (s && *s) {
|
|
if (strstr(s, "%s"))
|
|
return s;
|
|
}
|
|
}
|
|
return "%s";
|
|
}
|
|
|
|
size_t
|
|
f_regionid(const region * r, const faction * f, char *buffer, size_t size)
|
|
{
|
|
size_t len;
|
|
if (!r) {
|
|
len = str_strlcpy(buffer, "(Chaos)", size);
|
|
}
|
|
else {
|
|
plane *pl = rplane(r);
|
|
const char *name = pl ? pl->name : 0;
|
|
int nx = r->x, ny = r->y;
|
|
int named = (name && name[0]);
|
|
pnormalize(&nx, &ny, pl);
|
|
adjust_coordinates(f, &nx, &ny, pl);
|
|
len = str_strlcpy(buffer, rname(r, f ? f->locale : 0), size);
|
|
snprintf(buffer + len, size - len, " (%d,%d%s%s)", nx, ny, named ? "," : "", (named) ? name : "");
|
|
buffer[size - 1] = 0;
|
|
len = strlen(buffer);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
static char *f_regionid_s(const region * r, const faction * f)
|
|
{
|
|
static char buf[NAMESIZE + 20]; /* FIXME: static return value */
|
|
|
|
f_regionid(r, f, buf, sizeof(buf));
|
|
return buf;
|
|
}
|
|
|
|
/*** BEGIN MESSAGE RENDERING ***/
|
|
static void eval_localize(struct opstack **stack, const void *userdata)
|
|
{ /* (string, locale) -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct locale *lang = f ? f->locale : default_locale;
|
|
const char *c = (const char *)opop_v(stack);
|
|
c = LOC(lang, c);
|
|
opush_v(stack, strcpy(balloc(strlen(c) + 1), c));
|
|
}
|
|
|
|
static void eval_trailto(struct opstack **stack, const void *userdata)
|
|
{ /* (int, int) -> int */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct locale *lang = f ? f->locale : default_locale;
|
|
const struct region *r = (const struct region *)opop(stack).v;
|
|
const char *trail = trailinto(r, lang);
|
|
const char *rn = f_regionid_s(r, f);
|
|
variant var;
|
|
char *x = var.v = balloc(strlen(trail) + strlen(rn));
|
|
sprintf(x, trail, rn);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_unit(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct unit *u = (const struct unit *)opop(stack).v;
|
|
const char *c = u ? unitname(u) : LOC(f->locale, "an_unknown_unit");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_unit_dative(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct unit *u = (const struct unit *)opop(stack).v;
|
|
const char *c = u ? unitname(u) : LOC(f->locale, "unknown_unit_dative");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_spell(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct spell *sp = (const struct spell *)opop(stack).v;
|
|
const char *c =
|
|
sp ? spell_name(sp, f->locale) : LOC(f->locale, "an_unknown_spell");
|
|
variant var;
|
|
|
|
assert(c || !"spell without description!");
|
|
var.v = strcpy(balloc(strlen(c) + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_curse(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct curse_type *sp = (const struct curse_type *)opop(stack).v;
|
|
const char *c =
|
|
sp ? curse_name(sp, f->locale) : LOC(f->locale, "an_unknown_curse");
|
|
variant var;
|
|
|
|
assert(c || !"spell effect without description!");
|
|
var.v = strcpy(balloc(strlen(c) + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_unitid(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> int */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct unit *u = (const struct unit *)opop(stack).v;
|
|
const char *c = u ? unit_getname(u) : LOC(f->locale, "an_unknown_unit");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_unitsize(struct opstack **stack, const void *userdata)
|
|
{ /* unit -> int */
|
|
const struct unit *u = (const struct unit *)opop(stack).v;
|
|
variant var;
|
|
|
|
UNUSED_ARG(userdata);
|
|
var.i = u->number;
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_faction(struct opstack **stack, const void *userdata)
|
|
{ /* faction -> string */
|
|
const struct faction *f = (const struct faction *)opop(stack).v;
|
|
const char *c = factionname(f);
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
UNUSED_ARG(userdata);
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_alliance(struct opstack **stack, const void *userdata)
|
|
{ /* faction -> string */
|
|
const struct alliance *al = (const struct alliance *)opop(stack).v;
|
|
const char *c = alliancename(al);
|
|
variant var;
|
|
|
|
UNUSED_ARG(userdata);
|
|
if (c != NULL) {
|
|
size_t len = strlen(c);
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
}
|
|
else
|
|
var.v = NULL;
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_region(struct opstack **stack, const void *userdata)
|
|
{ /* region -> string */
|
|
char name[NAMESIZE + 32];
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct region *r = (const struct region *)opop(stack).v;
|
|
const char *c = write_regionname(r, f, name, sizeof(name));
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_terrain(struct opstack **stack, const void *userdata)
|
|
{ /* region -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct region *r = (const struct region *)opop(stack).v;
|
|
const char *c = LOC(f->locale, terrain_name(r));
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_ship(struct opstack **stack, const void *userdata)
|
|
{ /* ship -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct ship *u = (const struct ship *)opop(stack).v;
|
|
const char *c = u ? shipname(u) : LOC(f->locale, "an_unknown_ship");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_building(struct opstack **stack, const void *userdata)
|
|
{ /* building -> string */
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct building *u = (const struct building *)opop(stack).v;
|
|
const char *c = u ? buildingname(u) : LOC(f->locale, "an_unknown_building");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_weight(struct opstack **stack, const void *userdata)
|
|
{ /* region -> string */
|
|
char buffer[32];
|
|
const struct faction *f = (const struct faction *)userdata;
|
|
const struct locale *lang = f->locale;
|
|
int weight = opop_i(stack);
|
|
variant var;
|
|
|
|
if (weight % SCALEWEIGHT == 0) {
|
|
if (weight == SCALEWEIGHT) {
|
|
sprintf(buffer, "1 %s", LOC(lang, "weight_unit"));
|
|
}
|
|
else {
|
|
sprintf(buffer, "%d %s", weight / SCALEWEIGHT, LOC(lang,
|
|
"weight_unit_p"));
|
|
}
|
|
}
|
|
else {
|
|
if (weight == 1) {
|
|
sprintf(buffer, "1 %s %d", LOC(lang, "weight_per"), SCALEWEIGHT);
|
|
}
|
|
else {
|
|
sprintf(buffer, "%d %s %d", weight, LOC(lang, "weight_per_p"),
|
|
SCALEWEIGHT);
|
|
}
|
|
}
|
|
|
|
var.v = strcpy(balloc(strlen(buffer) + 1), buffer);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_resource(struct opstack **stack, const void *userdata)
|
|
{
|
|
const faction *report = (const faction *)userdata;
|
|
const struct locale *lang = report ? report->locale : default_locale;
|
|
int j = opop(stack).i;
|
|
const struct resource_type *res = (const struct resource_type *)opop(stack).v;
|
|
const char *name = resourcename(res, j != 1);
|
|
const char *c = LOC(lang, name);
|
|
variant var;
|
|
if (c) {
|
|
size_t len = strlen(c);
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
} else {
|
|
log_error("missing translation for %s in eval_resource", name);
|
|
var.v = NULL;
|
|
}
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_race(struct opstack **stack, const void *userdata)
|
|
{
|
|
const faction *report = (const faction *)userdata;
|
|
const struct locale *lang = report ? report->locale : default_locale;
|
|
int j = opop(stack).i;
|
|
const race *r = (const race *)opop(stack).v;
|
|
const char *name = rc_name_s(r, (j == 1) ? NAME_SINGULAR : NAME_PLURAL);
|
|
const char *c = LOC(lang, name);
|
|
variant var;
|
|
if (c) {
|
|
size_t len = strlen(c);
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
}
|
|
else {
|
|
log_error("missing translation for %s in eval_race", name);
|
|
var.v = NULL;
|
|
}
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_order(struct opstack **stack, const void *userdata)
|
|
{ /* order -> string */
|
|
const faction *f = (const faction *)userdata;
|
|
const struct order *ord = (const struct order *)opop(stack).v;
|
|
char buf[4096];
|
|
size_t len;
|
|
variant var;
|
|
const struct locale *lang = f ? f->locale : default_locale;
|
|
|
|
UNUSED_ARG(userdata);
|
|
write_order(ord, lang, buf, sizeof(buf));
|
|
len = strlen(buf);
|
|
var.v = strcpy(balloc(len + 1), buf);
|
|
opush(stack, var);
|
|
}
|
|
|
|
void report_battle_start(battle * b)
|
|
{
|
|
bfaction *bf;
|
|
char zText[32 * MAXSIDES];
|
|
sbstring sbs;
|
|
|
|
for (bf = b->factions; bf; bf = bf->next) {
|
|
message *m;
|
|
faction *f = bf->faction;
|
|
const char *lastf = NULL;
|
|
bool first = false;
|
|
side *s;
|
|
|
|
sbs_init(&sbs, zText, sizeof(zText));
|
|
for (s = b->sides; s != b->sides + b->nsides; ++s) {
|
|
fighter *df;
|
|
for (df = s->fighters; df; df = df->next) {
|
|
if (is_attacker(df)) {
|
|
if (first) {
|
|
sbs_strcat(&sbs, ", ");
|
|
}
|
|
if (lastf) {
|
|
sbs_strcat(&sbs, lastf);
|
|
first = true;
|
|
}
|
|
if (seematrix(f, s))
|
|
lastf = sidename(s);
|
|
else
|
|
lastf = LOC(f->locale, "unknown_faction_dative");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (lastf) {
|
|
if (first) {
|
|
sbs_strcat(&sbs, " ");
|
|
sbs_strcat(&sbs, LOC(f->locale, "and"));
|
|
sbs_strcat(&sbs, " ");
|
|
}
|
|
sbs_strcat(&sbs, lastf);
|
|
}
|
|
|
|
m = msg_message("start_battle", "factions", zText);
|
|
battle_message_faction(b, f, m);
|
|
msg_release(m);
|
|
}
|
|
}
|
|
|
|
static void eval_resources(struct opstack **stack, const void *userdata)
|
|
{ /* order -> string */
|
|
const faction *f = (const faction *)userdata;
|
|
const struct locale *lang = f ? f->locale : default_locale;
|
|
const struct resource *res = (const struct resource *)opop(stack).v;
|
|
char buf[1024]; /* but we only use about half of this */
|
|
variant var;
|
|
sbstring sbs;
|
|
|
|
sbs_init(&sbs, buf, sizeof(buf));
|
|
while (res != NULL) {
|
|
const char *rname =
|
|
resourcename(res->type, (res->number != 1) ? NMF_PLURAL : 0);
|
|
sbs_strcat(&sbs, str_itoa(res->number));
|
|
sbs_strcat(&sbs, " ");
|
|
sbs_strcat(&sbs, LOC(lang, rname));
|
|
|
|
res = res->next;
|
|
if (res != NULL) {
|
|
sbs_strcat(&sbs, ", ");
|
|
}
|
|
}
|
|
var.v = strcpy(balloc(sbs_length(&sbs)), buf);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_regions(struct opstack **stack, const void *userdata)
|
|
{ /* order -> string */
|
|
const faction *report = (const faction *)userdata;
|
|
int i = opop(stack).i;
|
|
int handle_end, begin = opop(stack).i;
|
|
const arg_regions *aregs = (const arg_regions *)opop(stack).v;
|
|
char buf[256];
|
|
variant var;
|
|
sbstring sbs;
|
|
|
|
sbs_init(&sbs, buf, sizeof(buf));
|
|
if (aregs == NULL) {
|
|
handle_end = begin;
|
|
}
|
|
else if (i >= 0) {
|
|
handle_end = begin + i;
|
|
}
|
|
else {
|
|
handle_end = aregs->nregions + i;
|
|
}
|
|
|
|
for (i = begin; i < handle_end; ++i) {
|
|
const char *rname = (const char *)regionname(aregs->regions[i], report);
|
|
sbs_strcat(&sbs, rname);
|
|
|
|
if (i + 1 < handle_end) {
|
|
sbs_strcat(&sbs, ", ");
|
|
}
|
|
}
|
|
var.v = strcpy(balloc(sbs_length(&sbs)), buf);
|
|
opush(stack, var);
|
|
}
|
|
|
|
const char *get_mailcmd(const struct locale *loc)
|
|
{
|
|
static char result[64]; /* FIXME: static return buffer */
|
|
snprintf(result, sizeof(result), "%s %d %s", game_mailcmd(), game_id(), LOC(loc, "mailcmd"));
|
|
return result;
|
|
}
|
|
|
|
static void print_trail(const faction *f, const region *r,
|
|
const struct locale *lang, struct sbstring *sbp)
|
|
{
|
|
char buf[64];
|
|
const char *trail = trailinto(r, lang);
|
|
const char *rn = f_regionid_s(r, f);
|
|
if (snprintf(buf, sizeof(buf), trail, rn) != 0) {
|
|
sbs_strcat(sbp, buf);
|
|
}
|
|
}
|
|
|
|
static void eval_trail(struct opstack **stack, const void *userdata)
|
|
{ /* order -> string */
|
|
const faction *report = (const faction *)userdata;
|
|
const struct locale *lang = report ? report->locale : default_locale;
|
|
const arg_regions *aregs = (const arg_regions *)opop(stack).v;
|
|
char buf[512];
|
|
variant var;
|
|
sbstring sbs;
|
|
#ifdef _SECURECRT_ERRCODE_VALUES_DEFINED
|
|
/* MSVC touches errno in snprintf */
|
|
int eold = errno;
|
|
#endif
|
|
|
|
sbs_init(&sbs, buf, sizeof(buf));
|
|
if (aregs != NULL) {
|
|
int i, handle_end = 0, begin = 0;
|
|
handle_end = aregs->nregions;
|
|
for (i = begin; i < handle_end; ++i) {
|
|
region *r = aregs->regions[i];
|
|
|
|
print_trail(report, r, lang, &sbs);
|
|
|
|
if (i + 2 < handle_end) {
|
|
sbs_strcat(&sbs, ", ");
|
|
}
|
|
else if (i + 1 < handle_end) {
|
|
sbs_strcat(&sbs, LOC(lang, "list_and"));
|
|
}
|
|
}
|
|
}
|
|
var.v = strcpy(balloc(sbs_length(&sbs)), buf);
|
|
opush(stack, var);
|
|
#ifdef _SECURECRT_ERRCODE_VALUES_DEFINED
|
|
if (errno == ERANGE) {
|
|
errno = eold;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void report_race_skills(const race *rc, const struct locale *lang, sbstring *sbp)
|
|
{
|
|
int dh = 0, dh1 = 0, sk;
|
|
|
|
for (sk = 0; sk < MAXSKILLS; ++sk) {
|
|
if (skill_enabled(sk) && rc->bonus[sk] > -5)
|
|
dh++;
|
|
}
|
|
|
|
for (sk = 0; sk < MAXSKILLS; sk++) {
|
|
if (skill_enabled(sk) && rc->bonus[sk] > -5) {
|
|
dh--;
|
|
if (dh1 == 0) {
|
|
dh1 = 1;
|
|
}
|
|
else {
|
|
if (dh == 0) {
|
|
sbs_strcat(sbp, LOC(lang, "list_and"));
|
|
}
|
|
else {
|
|
sbs_strcat(sbp, ", ");
|
|
}
|
|
}
|
|
sbs_strcat(sbp, skillname((skill_t)sk, lang));
|
|
}
|
|
}
|
|
}
|
|
|
|
void report_race_skills_depr(const race *rc, char *zText, size_t length, const struct locale *lang)
|
|
{
|
|
sbstring sbs;
|
|
sbs_init(&sbs, zText, length);
|
|
report_race_skills(rc, lang, &sbs);
|
|
}
|
|
|
|
static void eval_direction(struct opstack **stack, const void *userdata)
|
|
{
|
|
const faction *report = (const faction *)userdata;
|
|
const struct locale *lang = report ? report->locale : default_locale;
|
|
int i = opop(stack).i;
|
|
const char *c = LOC(lang, (i >= 0) ? directions[i] : "unknown_direction");
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_skill(struct opstack **stack, const void *userdata)
|
|
{
|
|
const faction *report = (const faction *)userdata;
|
|
const struct locale *lang = report ? report->locale : default_locale;
|
|
skill_t sk = (skill_t)opop(stack).i;
|
|
const char *c = skillname(sk, lang);
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
}
|
|
|
|
static void eval_int36(struct opstack **stack, const void *userdata)
|
|
{
|
|
int i = opop(stack).i;
|
|
const char *c = itoa36(i);
|
|
size_t len = strlen(c);
|
|
variant var;
|
|
|
|
var.v = strcpy(balloc(len + 1), c);
|
|
opush(stack, var);
|
|
UNUSED_ARG(userdata);
|
|
}
|
|
|
|
/*** END MESSAGE RENDERING ***/
|
|
|
|
int stream_printf(struct stream * out, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
int result;
|
|
char buffer[4096];
|
|
size_t bytes = sizeof(buffer);
|
|
/* TODO: should be in storage/stream.c (doesn't exist yet) */
|
|
va_start(args, format);
|
|
result = vsnprintf(buffer, bytes, format, args);
|
|
if (result >= 0 && (size_t)result < bytes) {
|
|
bytes = (size_t)result;
|
|
/* TODO: else = buffer too small */
|
|
}
|
|
out->api->write(out->handle, buffer, bytes);
|
|
va_end(args);
|
|
return result;
|
|
}
|
|
|
|
typedef struct count_data {
|
|
int n;
|
|
const struct faction *f;
|
|
} count_data;
|
|
|
|
static void count_cb(region *r, unit *u, void *cbdata) {
|
|
count_data *data = (count_data *)cbdata;
|
|
const struct faction *f = data->f;
|
|
if (r != u->region && (!u->ship || ship_owner(u->ship) == u)) {
|
|
if (cansee_durchgezogen(f, r, u, 0)) {
|
|
++data->n;
|
|
}
|
|
}
|
|
}
|
|
|
|
int count_travelthru(struct region *r, const struct faction *f) {
|
|
count_data data = { 0 };
|
|
data.f = f;
|
|
travelthru_map(r, count_cb, &data);
|
|
return data.n;
|
|
}
|
|
|
|
bool visible_unit(const unit *u, const faction *f, int stealthmod, seen_mode mode)
|
|
{
|
|
if (u->faction == f) {
|
|
return true;
|
|
}
|
|
else {
|
|
if (stealthmod > INT_MIN && mode >= seen_lighthouse) {
|
|
if (mode != seen_travel || u->building || u->ship || is_guard(u)) {
|
|
return cansee(f, u->region, u, stealthmod);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool see_region_details(const region *r)
|
|
{
|
|
return r->seen.mode >= seen_travel;
|
|
}
|
|
|
|
void register_reports(void)
|
|
{
|
|
/* register datatypes for the different message objects */
|
|
register_argtype("alliance", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("building", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("direction", NULL, NULL, VAR_INT);
|
|
register_argtype("faction", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("race", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("region", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("resource", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("ship", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("skill", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("spell", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("curse", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("unit", NULL, NULL, VAR_VOIDPTR);
|
|
register_argtype("int", NULL, NULL, VAR_INT);
|
|
register_argtype("string", var_free_string, var_copy_string, VAR_VOIDPTR);
|
|
register_argtype("order", var_free_order, var_copy_order, VAR_VOIDPTR);
|
|
register_argtype("resources", var_free_resources, var_copy_resources, VAR_VOIDPTR);
|
|
register_argtype("items", var_free_resources, var_copy_items, VAR_VOIDPTR);
|
|
register_argtype("regions", var_free_regions, NULL, VAR_VOIDPTR);
|
|
|
|
/* register functions that turn message contents to readable strings */
|
|
add_function("alliance", &eval_alliance);
|
|
add_function("region", &eval_region);
|
|
add_function("terrain", &eval_terrain);
|
|
add_function("weight", &eval_weight);
|
|
add_function("resource", &eval_resource);
|
|
add_function("race", &eval_race);
|
|
add_function("faction", &eval_faction);
|
|
add_function("ship", &eval_ship);
|
|
add_function("unit", &eval_unit);
|
|
add_function("unit.dative", &eval_unit_dative);
|
|
add_function("unit.id", &eval_unitid);
|
|
add_function("unit.size", &eval_unitsize);
|
|
add_function("building", &eval_building);
|
|
add_function("skill", &eval_skill);
|
|
add_function("order", &eval_order);
|
|
add_function("direction", &eval_direction);
|
|
add_function("int36", &eval_int36);
|
|
add_function("trailto", &eval_trailto);
|
|
add_function("localize", &eval_localize);
|
|
add_function("spell", &eval_spell);
|
|
add_function("curse", &eval_curse);
|
|
add_function("resources", &eval_resources);
|
|
add_function("regions", &eval_regions);
|
|
add_function("trail", &eval_trail);
|
|
}
|