server/src/move.c
2020-08-16 21:17:23 +02:00

2581 lines
78 KiB
C

#ifdef _MSC_VER
#include <platform.h>
#endif
/* kernel includes */
#include "kernel/ally.h"
#include "kernel/attrib.h"
#include "kernel/build.h"
#include "kernel/building.h"
#include "kernel/calendar.h"
#include "kernel/config.h"
#include "kernel/connection.h"
#include "kernel/curse.h"
#include "kernel/faction.h"
#include "kernel/gamedata.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/render.h"
#include "kernel/ship.h"
#include "kernel/terrain.h"
#include "kernel/terrainid.h"
#include "kernel/unit.h"
#include "alchemy.h"
#include "contact.h"
#include "guard.h"
#include "laws.h"
#include "lighthouse.h"
#include "monsters.h"
#include "move.h"
#include "piracy.h"
#include "reports.h"
#include "study.h"
#include "spy.h"
#include "travelthru.h"
#include "vortex.h"
#include <spells/flyingship.h>
#include <spells/unitcurse.h>
#include <spells/regioncurse.h>
#include <spells/shipcurse.h>
/* attributes includes */
#include <attributes/follow.h>
#include <attributes/movement.h>
#include <attributes/stealth.h>
#include <attributes/targetregion.h>
#include "teleport.h"
#include "direction.h"
#include "skill.h"
/* util includes */
#include <util/assert.h>
#include <util/base36.h>
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
#include <util/macros.h>
#include <util/param.h>
#include <util/parser.h>
#include <util/rand.h>
#include <util/rng.h>
#include <util/strings.h>
#include <storage.h>
/* libc includes */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <float.h>
int *storms;
typedef struct traveldir {
int no;
direction_t dir;
int age;
} traveldir;
static attrib_type at_traveldir = {
"traveldir",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE, /* Weil normales Aging an unguenstiger Stelle */
a_writechars,
a_readchars
};
typedef struct follower {
struct follower *next;
unit *uf;
unit *ut;
const region_list *route_end;
} follower;
static void
get_followers(unit * target, region * r, const region_list * route_end,
follower ** followers)
{
unit *uf;
for (uf = r->units; uf; uf = uf->next) {
if (fval(uf, UFL_FOLLOWING) && !fval(uf, UFL_NOTMOVING)) {
const attrib *a = a_find(uf->attribs, &at_follow);
if (a && a->data.v == target) {
follower *fnew = (follower *)malloc(sizeof(follower));
assert_alloc(fnew);
fnew->uf = uf;
fnew->ut = target;
fnew->route_end = route_end;
fnew->next = *followers;
*followers = fnew;
}
}
}
}
static void shiptrail_init(variant *var)
{
var->v = calloc(1, sizeof(traveldir));
}
static int shiptrail_age(attrib * a, void *owner)
{
traveldir *t = (traveldir *)(a->data.v);
(void)owner;
t->age--;
return (t->age > 0) ? AT_AGE_KEEP : AT_AGE_REMOVE;
}
static int shiptrail_read(variant *var, void *owner, struct gamedata *data)
{
storage *store = data->store;
int n;
traveldir *t = (traveldir *)var->v;
UNUSED_ARG(owner);
READ_INT(store, &t->no);
READ_INT(store, &n);
t->dir = (direction_t)n;
READ_INT(store, &t->age);
return AT_READ_OK;
}
static void
shiptrail_write(const variant *var, const void *owner, struct storage *store)
{
traveldir *t = (traveldir *)var->v;
UNUSED_ARG(owner);
WRITE_INT(store, t->no);
WRITE_INT(store, t->dir);
WRITE_INT(store, t->age);
}
attrib_type at_shiptrail = {
"traveldir_new",
shiptrail_init,
a_free_voidptr,
shiptrail_age,
shiptrail_write,
shiptrail_read
};
/* ------------------------------------------------------------- */
static attrib_type at_driveweight = {
"driveweight", NULL, NULL, NULL, NULL, NULL
};
static bool entrance_allowed(const struct unit *u, const struct region *r)
{
#ifdef REGIONOWNERS
faction *owner = region_get_owner(r);
if (owner == NULL || u->faction == owner)
return true;
if (alliedfaction(rplane(r), owner, u->faction, HELP_TRAVEL))
return true;
return false;
#else
UNUSED_ARG(u);
UNUSED_ARG(r);
return true;
#endif
}
int personcapacity(const unit * u)
{
int cap = u_race(u)->weight + u_race(u)->capacity;
return cap;
}
static int eff_weight(const unit * u)
{
attrib *a = a_find(u->attribs, &at_driveweight);
if (a)
return weight(u) + a->data.i;
return weight(u);
}
static void
get_transporters(const item * itm, int *p_animals, int *p_acap, int *p_vehicles,
int *p_vcap)
{
int vehicles = 0, vcap = 0;
int animals = 0, acap = 0;
for (; itm != NULL; itm = itm->next) {
const item_type *itype = itm->type;
if (itype->capacity > 0) {
if (itype->flags & ITF_ANIMAL) {
animals += itm->number;
if (acap == 0)
acap = itype->capacity;
assert(acap == itype->capacity
|| !"animals with different capacity not supported");
}
if (itype->flags & ITF_VEHICLE) {
vehicles += itm->number;
if (vcap == 0)
vcap = itype->capacity;
assert(vcap == itype->capacity
|| !"vehicles with different capacity not supported");
}
}
}
*p_vehicles = vehicles;
*p_animals = animals;
*p_vcap = vcap;
*p_acap = acap;
}
static int walking_horse_limit(const unit *u, int skill) {
return (1 + skill * 4) * u->number;
}
static int riding_horse_limit(const unit *u, int skill) {
return skill * 2 * u->number;
}
static int ridingcapacity(const unit * u)
{
int vehicles = 0, vcap = 0;
int animals = 0, acap = 0;
int horses;
get_transporters(u->items, &animals, &acap, &vehicles, &vcap);
/* Man traegt sein eigenes Gewicht plus seine Kapazitaet! Die Menschen
** tragen nichts (siehe walkingcapacity). Ein Wagen zaehlt nur, wenn er
** von zwei Pferden gezogen wird */
horses = riding_horse_limit(u, effskill(u, SK_RIDING, NULL));
if (animals > horses) animals = horses;
if (fval(u_race(u), RCF_HORSE))
animals += u->number;
/* maximal diese Pferde koennen zum Ziehen benutzt werden */
horses = animals / HORSES_PER_CART;
if (horses < vehicles) vehicles = horses;
return vehicles * vcap + animals * acap;
}
int walkingcapacity(const struct unit *u)
{
int n, people, pferde_fuer_wagen, horses;
int wagen_mit_pferden;
int vehicles = 0, vcap = 0;
int animals = 0, acap = 0;
const struct resource_type *rhorse = rt_find("horse");
const struct resource_type *rbelt = rt_find("trollbelt");
get_transporters(u->items, &animals, &acap, &vehicles, &vcap);
/* Das Gewicht, welches die Pferde tragen, plus das Gewicht, welches
* die Leute tragen */
horses = walking_horse_limit(u, effskill(u, SK_RIDING, NULL));
pferde_fuer_wagen = (animals < horses) ? animals : horses;
if (fval(u_race(u), RCF_HORSE)) {
animals += u->number;
people = 0;
}
else {
people = u->number;
}
/* maximal diese Pferde koennen zum Ziehen benutzt werden */
horses = pferde_fuer_wagen / HORSES_PER_CART;
wagen_mit_pferden = (vehicles < horses) ? vehicles : horses;
n = wagen_mit_pferden * vcap;
if (u_race(u) == get_race(RC_TROLL)) {
int wagen_ohne_pferde, wagen_mit_trollen;
/* 4 Trolle ziehen einen Wagen. */
/* Unbesetzte Wagen feststellen */
wagen_ohne_pferde = vehicles - wagen_mit_pferden;
/* Genug Trolle, um die Restwagen zu ziehen? */
wagen_mit_trollen = u->number / 4;
if (wagen_mit_trollen > wagen_ohne_pferde) {
wagen_mit_trollen = wagen_ohne_pferde;
}
/* Wagenkapazitaet hinzuzaehlen */
n += wagen_mit_trollen * vcap;
}
n += animals * acap;
n += people * personcapacity(u);
/* Goliathwasser */
if (rhorse) {
int tmp = get_effect(u, oldpotiontype[P_STRONG]);
if (tmp > 0) {
int horsecap = rhorse->itype->capacity;
if (tmp > people) {
tmp = people;
}
n += tmp * (horsecap - personcapacity(u));
}
}
if (rbelt) {
int belts = i_get(u->items, rbelt->itype);
if (belts) {
int multi = config_get_int("rules.trollbelt.multiplier", STRENGTHMULTIPLIER);
if (belts > people) belts = people;
n += belts * (multi - 1) * u_race(u)->capacity;
}
}
return n;
}
enum {
E_CANWALK_OK = 0,
E_CANWALK_TOOMANYHORSES,
E_CANWALK_TOOMANYCARTS,
E_CANWALK_TOOHEAVY
};
static int canwalk(unit * u)
{
int maxwagen, maxpferde;
int vehicles = 0, vcap = 0;
int animals = 0, acap = 0;
/* workaround: monsters are too stupid to drop items, therefore they have
* infinite carrying capacity */
if (is_monsters(u->faction))
return E_CANWALK_OK;
get_transporters(u->items, &animals, &acap, &vehicles, &vcap);
maxpferde = walking_horse_limit(u, effskill(u, SK_RIDING, NULL));
maxwagen = maxpferde / 2;
if (u_race(u) == get_race(RC_TROLL)) {
int trolls = u->number / 4;
if (maxwagen < trolls) maxwagen = trolls;
}
if (animals > maxpferde)
return E_CANWALK_TOOMANYHORSES;
if (walkingcapacity(u) - eff_weight(u) >= 0)
return E_CANWALK_OK;
/* Stimmt das Gewicht, impliziert dies hier, dass alle Wagen ohne
* Zugpferde/-trolle als Fracht aufgeladen wurden: zu viele Pferde hat
* die Einheit nicht zum Ziehen benutzt, also nicht mehr Wagen gezogen
* als erlaubt. */
if (vehicles > maxwagen)
return E_CANWALK_TOOMANYCARTS;
/* Es muss nicht zwingend an den Wagen liegen, aber egal... (man
* koennte z.B. auch 8 Eisen abladen, damit ein weiterer Wagen als
* Fracht draufpasst) */
return E_CANWALK_TOOHEAVY;
}
bool canfly(unit * u)
{
if (i_get(u->items, it_find("pegasus")) >= u->number && effskill(u, SK_RIDING, NULL) >= 4)
return true;
if (fval(u_race(u), RCF_FLY))
return true;
if (get_movement(&u->attribs, MV_FLY))
return true;
return false;
}
bool canswim(unit * u)
{
if (i_get(u->items, it_find("dolphin")) >= u->number && effskill(u, SK_RIDING, NULL) >= 4)
return true;
if (u_race(u)->flags & RCF_FLY)
return true;
if (u_race(u)->flags & RCF_SWIM)
return true;
if (get_movement(&u->attribs, MV_FLY))
return true;
if (get_movement(&u->attribs, MV_SWIM))
return true;
return false;
}
static int walk_mode(const unit * u)
{
int horses = 0, maxhorses, unicorns = 0, maxunicorns;
int skill;
item *itm;
const item_type *it_horse, *it_elvenhorse, *it_charger;
const resource_type *rtype;
it_horse = ((rtype = get_resourcetype(R_HORSE)) != NULL) ? rtype->itype : 0;
it_elvenhorse = ((rtype = get_resourcetype(R_UNICORN)) != NULL) ? rtype->itype : 0;
it_charger = ((rtype = get_resourcetype(R_CHARGER)) != NULL) ? rtype->itype : 0;
for (itm = u->items; itm; itm = itm->next) {
if (itm->type == it_horse || itm->type == it_charger) {
horses += itm->number;
}
else if (itm->type == it_elvenhorse) {
unicorns += itm->number;
}
}
skill = effskill(u, SK_RIDING, NULL);
maxunicorns = (skill / 5) * u->number;
maxhorses = riding_horse_limit(u, skill);
if (!(u_race(u)->flags & RCF_HORSE)
&& ((horses == 0 && unicorns == 0)
|| horses > maxhorses || unicorns > maxunicorns)) {
return BP_WALKING;
}
if (ridingcapacity(u) - eff_weight(u) >= 0) {
if (horses == 0 && unicorns >= u->number && !(u_race(u)->flags & RCF_HORSE)) {
return BP_UNICORN;
}
return BP_RIDING;
}
return BP_WALKING;
}
static bool cansail(const region * r, ship * sh)
{
UNUSED_ARG(r);
if (!ship_finished(sh)) {
return false;
}
else {
int n = 0, p = 0;
int mweight = ship_capacity(sh);
int mcabins = ship_cabins(sh);
getshipweight(sh, &n, &p);
if (n > mweight)
return false;
if (mcabins && p > mcabins)
return false;
}
return true;
}
static double overload(const region * r, ship * sh)
{
UNUSED_ARG(r);
if (!ship_finished(sh)) {
return DBL_MAX;
}
else {
int n = 0, p = 0;
int mcabins = sh->type->cabins * sh->number;
double ovl;
getshipweight(sh, &n, &p);
ovl = n / (double)(sh->type->cargo * sh->number);
if (mcabins) {
ovl = fmax(ovl, p / (double)mcabins);
}
return ovl;
}
}
/* ------------------------------------------------------------- */
static ship *do_maelstrom(region * r, unit * u)
{
int damage;
ship *sh = u->ship;
damage = rng_int() % 75 + rng_int() % 75 - effskill(u, SK_SAILING, r) * 4;
if (damage <= 0) {
return sh;
}
damage_ship(u->ship, 0.01 * damage);
if (sh->damage >= sh->size * DAMAGE_SCALE) {
ADDMSG(&u->faction->msgs, msg_message("entermaelstrom",
"region ship damage sink", r, sh, damage, 1));
sink_ship(sh);
remove_ship(&sh->region->ships, sh);
return NULL;
}
ADDMSG(&u->faction->msgs, msg_message("entermaelstrom",
"region ship damage sink", r, sh, damage, 0));
return u->ship;
}
static direction_t
koor_reldirection(int ax, int ay, int bx, int by, const struct plane *pl)
{
int dir;
for (dir = 0; dir != MAXDIRECTIONS; ++dir) {
int x = ax + delta_x[dir];
int y = ay + delta_y[dir];
pnormalize(&x, &y, pl);
if (bx == x && by == y)
return (direction_t)dir;
}
return NODIRECTION;
}
direction_t reldirection(const region * from, const region * to)
{
plane *pl = rplane(from);
if (pl == rplane(to)) {
direction_t dir = koor_reldirection(from->x, from->y, to->x, to->y, pl);
if (dir == NODIRECTION) {
spec_direction *sd = special_direction(from, to);
if (sd != NULL && sd->active)
return D_SPECIAL;
}
return dir;
}
return NODIRECTION;
}
void leave_trail(ship * sh, region * from, region_list * route)
{
region *r = from;
while (route != NULL) {
region *rn = route->data;
direction_t dir = reldirection(r, rn);
/* TODO: we cannot leave a trail into special directions
* if we use this kind of direction-attribute */
if (dir < MAXDIRECTIONS && dir >= 0) {
traveldir *td = NULL;
attrib *a = a_find(r->attribs, &at_shiptrail);
while (a != NULL && a->type == &at_shiptrail) {
td = (traveldir *)a->data.v;
if (td->no == sh->no) {
td->dir = dir;
td->age = 2;
break;
}
a = a->next;
}
if (a == NULL || a->type != &at_shiptrail) {
a = a_add(&(r->attribs), a_new(&at_shiptrail));
td = (traveldir *)a->data.v;
td->no = sh->no;
td->dir = dir;
td->age = 2;
}
}
route = route->next;
r = rn;
}
}
static void
mark_travelthru(unit * u, region * r, const region_list * route,
const region_list * route_end)
{
/* kein travelthru in der letzten region! */
while (route != route_end) {
travelthru_add(r, u);
r = route->data;
route = route->next;
}
}
void move_ship(ship * sh, region * from, region * to, region_list * route)
{
unit **iunit = &from->units;
unit **ulist = &to->units;
assert(sh);
if (from != to) {
translist(&from->ships, &to->ships, sh);
sh->region = to;
}
if (route) {
leave_trail(sh, from, route);
}
while (*iunit != NULL) {
unit *u = *iunit;
assert(u->region == from);
if (u->ship == sh) {
if (route != NULL)
mark_travelthru(u, from, route, NULL);
if (from != to) {
u->ship = 0; /* temporary trick -- do not use u_set_ship here */
move_unit(u, to, ulist);
ulist = &u->next;
u->ship = sh; /* undo the trick -- do not use u_set_ship here */
}
if (route && effskill(u, SK_SAILING, from) >= 1) {
produceexp(u, SK_SAILING, u->number);
}
}
if (*iunit == u)
iunit = &u->next;
}
}
static bool is_freezing(const unit * u)
{
if (u_race(u) != get_race(RC_INSECT))
return false;
if (is_cursed(u->attribs, &ct_insectfur))
return false;
return true;
}
int check_ship_allowed(struct ship *sh, const region * r)
{
const building_type *bt_harbour = bt_find("harbour");
if (sh->region && r_insectstalled(r)) {
/* insekten duerfen nicht hier rein. haben wir welche? */
unit *u = ship_owner(sh);
if (u && is_freezing(u)) {
return SA_NO_INSECT;
}
}
if (bt_harbour && buildingtype_exists(r, bt_harbour, true)) {
unit* harbourmaster = owner_buildingtyp(r, bt_harbour);
if (!harbourmaster || !sh->_owner) {
return SA_HARBOUR;
}
else if ((sh->_owner->faction == harbourmaster->faction) || (ucontact(harbourmaster, sh->_owner)) || (alliedunit(harbourmaster, sh->_owner->faction, HELP_GUARD))) {
return SA_HARBOUR;
}
}
if (fval(r->terrain, SEA_REGION)) {
return SA_COAST;
}
if (sh->type->coasts) {
int c;
for (c = 0; sh->type->coasts[c] != NULL; ++c) {
if (sh->type->coasts[c] == r->terrain) {
return SA_COAST;
}
}
}
return SA_NO_COAST;
}
static enum direction_t set_coast(ship * sh, region * r, region * rnext)
{
if (sh->type->flags & SFL_NOCOAST) {
return sh->coast = NODIRECTION;
}
else if (!fval(rnext->terrain, SEA_REGION) && !flying_ship(sh)) {
if (fval(r->terrain, SEA_REGION)) {
return sh->coast = reldirection(rnext, r);
}
}
return sh->coast = NODIRECTION;
}
static double overload_start(void) {
return config_get_flt("rules.ship.overload.start", 1.1);
}
static double overload_worse(void) {
return config_get_flt("rules.ship.overload.worse", 1.5);
}
static double overload_worst(void) {
return config_get_flt("rules.ship.overload.worst", 5.0);
}
static double overload_default_damage(void) {
return config_get_flt("rules.ship.overload.damage.default", 0.05);
}
static double overload_max_damage(void) {
return config_get_flt("rules.ship.overload.damage.max", 0.37);
}
double damage_overload(double overload)
{
double damage, badness;
if (overload < overload_start())
return 0;
damage = overload_default_damage();
badness = overload - overload_worse();
if (badness >= 0) {
assert(overload_worst() > overload_worse() || !"overload.worst must be > overload.worse");
damage += fmin(badness, overload_worst() - overload_worse()) *
(overload_max_damage() - damage) /
(overload_worst() - overload_worse());
}
return damage;
}
/* message to all factions in ship, start from firstu, end before lastu (may be NULL) */
static void msg_to_passengers(ship *sh, unit **firstu, unit **lastu, message *msg) {
unit *u, *shipfirst = NULL;
for (u = *firstu; u != *lastu; u = u->next) {
if (u->ship == sh) {
if (shipfirst == NULL)
shipfirst = u;
if (!fval(u->faction, FFL_MARK)) {
fset(u->faction, FFL_MARK);
add_message(&u->faction->msgs, msg);
}
*lastu = u->next;
}
}
if (shipfirst) {
*firstu = shipfirst;
for (u = *firstu; u != *lastu; u = u->next) {
freset(u->faction, FFL_MARK);
}
}
msg_release(msg);
}
direction_t drift_target(ship *sh) {
direction_t d, dir = rng_int() % MAXDIRECTIONS;
direction_t result = NODIRECTION;
for (d = 0; d != MAXDIRECTIONS; ++d) {
region *rn;
direction_t dn = (direction_t)((d + dir) % MAXDIRECTIONS);
rn = rconnect(sh->region, dn);
if (rn != NULL && check_ship_allowed(sh, rn) >= 0) {
result = dn;
if (!fval(rn->terrain, SEA_REGION)) {
/* prefer drifting towards non-ocean regions */
break;
}
}
}
return result;
}
static void drifting_ships(region * r)
{
static int config;
static bool drift;
static double damage_drift;
if (config_changed(&config)) {
drift = config_get_int("rules.ship.drifting", 1) != 0;
damage_drift = config_get_flt("rules.ship.damage_drift", 0.02);
}
if (fval(r->terrain, SEA_REGION)) {
ship **shp = &r->ships;
while (*shp) {
ship *sh = *shp;
region *rnext = NULL;
region_list *route = NULL;
unit *firstu = r->units, *lastu = NULL;
direction_t dir = NODIRECTION;
double ovl;
if (sh->type->fishing > 0) {
sh->flags |= SF_FISHING;
}
/* Schiff schon abgetrieben oder durch Zauber geschuetzt? */
if (!drift || fval(sh, SF_DRIFTED) || is_cursed(sh->attribs, &ct_nodrift)) {
shp = &sh->next;
continue;
}
/* Kapitaen da? Beschaedigt? Genuegend Matrosen?
* Genuegend leicht? Dann ist alles OK. */
if (ship_finished(sh) && ship_crewed(sh, ship_owner(sh)) && cansail(r, sh)) {
shp = &sh->next;
continue;
}
ovl = overload(r, sh);
if (ovl < overload_start()) {
/* Auswahl einer Richtung: Zuerst auf Land, dann
* zufaellig. Falls unmoegliches Resultat: vergiss es. */
dir = drift_target(sh);
if (dir != NODIRECTION) {
rnext = rconnect(sh->region, dir);
}
}
if (rnext && firstu) {
message *msg = msg_message("ship_drift", "ship dir", sh, dir);
msg_to_passengers(sh, &firstu, &lastu, msg);
}
fset(sh, SF_DRIFTED);
if (ovl >= overload_start()) {
damage_ship(sh, damage_overload(ovl));
msg_to_passengers(sh, &firstu, &lastu, msg_message("massive_overload", "ship", sh));
}
else {
damage_ship(sh, damage_drift);
}
if (sh->damage >= sh->size * DAMAGE_SCALE) {
msg_to_passengers(sh, &firstu, &lastu, msg_message("shipsink", "ship", sh));
sink_ship(sh);
remove_ship(shp, sh);
}
else if (rnext) {
/* Das Schiff und alle Einheiten darin werden nun von r
* nach rnext verschoben. Danach eine Meldung. */
add_regionlist(&route, rnext);
set_coast(sh, r, rnext);
move_ship(sh, r, rnext, route);
free_regionlist(route);
}
else {
shp = &sh->next;
}
}
}
}
static bool present(region * r, unit * u)
{
return (u && u->region == r);
}
static void caught_target(region * r, unit * u)
{
attrib *a = a_find(u->attribs, &at_follow);
/* Verfolgungen melden */
/* Misserfolgsmeldung, oder bei erfolgreichem Verfolgen unter
* Umstaenden eine Warnung. */
if (a) {
unit *target = (unit *)a->data.v;
if (target == u || !present(r, target)) {
ADDMSG(&u->faction->msgs, msg_message("followfail", "unit follower",
target, u));
}
else if (!alliedunit(target, u->faction, HELP_ALL)
&& cansee(target->faction, r, u, 0)) {
ADDMSG(&target->faction->msgs, msg_message("followdetect",
"unit follower", target, u));
}
}
}
static unit *bewegung_blockiert_von(unit * reisender, region * r)
{
unit *u;
double prob = 0.0;
unit *guard = NULL;
int guard_count = 0;
int stealth = eff_stealth(reisender, r);
const struct resource_type *ramulet = get_resourcetype(R_AMULET_OF_TRUE_SEEING);
const struct building_type *castle_bt = bt_find("castle");
double base_prob = config_get_flt("rules.guard.base_stop_prob", .3);
double skill_prob = config_get_flt("rules.guard.skill_stop_prob", .1);
double amulet_prob = config_get_flt("rules.guard.amulet_stop_prob", .1);
double guard_number_prob = config_get_flt("rules.guard.guard_number_stop_prob", .001);
double castle_prob = config_get_flt("rules.guard.castle_stop_prob", .1);
double region_type_prob = config_get_flt("rules.guard.region_type_stop_prob", .1);
if (fval(u_race(reisender), RCF_ILLUSIONARY))
return NULL;
for (u = r->units; u; u = u->next) {
if (is_guard(u)) {
int sk = effskill(u, SK_PERCEPTION, r);
if (invisible(reisender, u) >= reisender->number)
continue;
if (!(u_race(u)->flags & RCF_FLY) && u_race(reisender)->flags & RCF_FLY)
continue;
if ((u->faction == reisender->faction) || (ucontact(u, reisender)) || (alliedunit(u, reisender->faction, HELP_GUARD)))
guard_count = guard_count - u->number;
else if (sk >= stealth) {
double prob_u = (sk - stealth) * skill_prob;
guard_count += u->number;
/* amulet counts at most once */
prob_u += fmin(1, fmin(u->number, i_get(u->items, ramulet->itype))) * amulet_prob;
if (u->building && (u->building->type == castle_bt) && u == building_owner(u->building))
prob_u += castle_prob*buildingeffsize(u->building, 0);
if (prob_u >= prob) {
prob = prob_u;
guard = u;
}
}
}
}
if (guard) {
prob += base_prob; /* 30% base chance */
prob += guard_count*guard_number_prob;
if (r->terrain == newterrain(T_GLACIER))
prob += region_type_prob * 2;
if (r->terrain == newterrain(T_SWAMP))
prob += region_type_prob * 2;
if (r->terrain == newterrain(T_MOUNTAIN))
prob += region_type_prob;
if (r->terrain == newterrain(T_VOLCANO))
prob += region_type_prob;
if (r->terrain == newterrain(T_VOLCANO_SMOKING))
prob += region_type_prob;
if (prob > 0 && chance(prob)) {
return guard;
}
}
return NULL;
}
bool move_blocked(const unit * u, const region * r, const region * r2)
{
connection *b;
if (r2 == NULL)
return true;
b = get_borders(r, r2);
while (b) {
if (b->type->block && b->type->block(b, u, r)) {
return true;
}
b = b->next;
}
if (r->attribs) {
curse *c = get_curse(r->attribs, &ct_fogtrap);
if (curse_active(c)) {
return true;
}
}
if (r2->attribs && fval(u_race(u), RCF_UNDEAD)) {
curse *c = get_curse(r2->attribs, &ct_holyground);
return curse_active(c);
}
return false;
}
int movewhere(const unit * u, const char *token, region * r, region ** resultp)
{
region *r2;
direction_t d;
if (!token || *token == '\0') {
*resultp = NULL;
return E_MOVE_OK;
}
d = get_direction(token, u->faction->locale);
switch (d) {
case D_PAUSE:
*resultp = r;
break;
case NODIRECTION:
token = (const char *)get_translation(u->faction->locale, token, UT_SPECDIR);
if (!token) {
return E_MOVE_NOREGION;
}
r2 = find_special_direction(r, token);
if (r2 == NULL) {
return E_MOVE_NOREGION;
}
*resultp = r2;
break;
default:
r2 = rconnect(r, d);
if (r2 == NULL || move_blocked(u, r, r2)) {
return E_MOVE_BLOCKED;
}
*resultp = r2;
}
return E_MOVE_OK;
}
order * cycle_route(order * ord, const struct locale *lang, int gereist)
{
int cm = 0;
char tail[1024];
char neworder[2048];
char token[128];
direction_t d = NODIRECTION;
bool paused = false;
order *norder;
sbstring sbtail;
sbstring sborder;
sbs_init(&sbtail, tail, sizeof(tail));
sbs_init(&sborder, neworder, sizeof(neworder));
assert(getkeyword(ord) == K_ROUTE);
tail[0] = '\0';
neworder[0] = '\0';
init_order(ord, lang);
for (cm = 0;; ++cm) {
const char *s;
s = gettoken(token, sizeof(token));
if (s && *s) {
d = get_direction(s, lang);
if (d == NODIRECTION) {
break;
}
}
else {
break;
}
if (cm < gereist) {
/* TODO: hier sollte keine PAUSE auftreten */
assert (d != D_PAUSE);
if (d != D_PAUSE) {
const char *loc = LOC(lang, shortdirections[d]);
assert(loc);
if (sbs_length(&sbtail) > 0) {
sbs_strcat(&sbtail, " ");
}
sbs_strcat(&sbtail, loc);
}
}
else if (strlen(neworder) > sizeof(neworder) / 2)
break;
else if (cm == gereist && !paused && (d == D_PAUSE)) {
const char *loc = LOC(lang, parameters[P_PAUSE]);
sbs_strcat(&sbtail, " ");
sbs_strcat(&sbtail, loc);
paused = true;
}
else {
if (sbs_length(&sbtail) > 0) {
sbs_strcat(&sborder, " ");
}
if (d == D_PAUSE) {
/* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist
* hier keine normale direction), muss jede PAUSE einzeln
* herausgefiltert und explizit gesetzt werden */
sbs_strcat(&sborder, LOC(lang, parameters[P_PAUSE]));
}
else {
sbs_strcat(&sborder, LOC(lang, shortdirections[d]));
}
}
}
if (neworder[0]) {
norder = create_order(K_ROUTE, lang, "%s %s", neworder, tail);
}
else {
norder = create_order(K_ROUTE, lang, "%s", tail);
}
return norder;
}
order * make_movement_order(const struct locale *lang, direction_t steps[], int length)
{
sbstring sbs;
char zOrder[128];
int i;
sbs_init(&sbs, zOrder, sizeof(zOrder));
for (i = 0; i != length; ++i) {
direction_t dir = steps[i];
if (i > 0) {
sbs_strcat(&sbs, " ");
}
sbs_strcat(&sbs, LOC(lang, directions[dir]));
}
return create_order(K_MOVE, lang, zOrder);
}
static bool transport(unit * ut, unit * u)
{
order *ord;
if (LongHunger(u) || fval(ut->region->terrain, SEA_REGION)) {
return false;
}
for (ord = ut->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_TRANSPORT) {
unit *u2;
init_order(ord, NULL);
getunit(ut->region, ut->faction, &u2);
if (u2 == u) {
return true;
}
}
}
return false;
}
static bool can_move(const unit * u)
{
if (u_race(u)->flags & RCF_CANNOTMOVE)
return false;
if (get_movement(&u->attribs, MV_CANNOTMOVE))
return false;
return true;
}
static void init_transportation(void)
{
region *r;
for (r = regions; r; r = r->next) {
unit *u;
/* This is just a simple check for non-corresponding K_TRANSPORT/
* K_DRIVE. This is time consuming for an error check, but there
* doesn't seem to be an easy way to speed this up. */
for (u = r->units; u; u = u->next) {
if (getkeyword(u->thisorder) == K_DRIVE && can_move(u)
&& !fval(u, UFL_NOTMOVING) && !LongHunger(u)) {
unit *ut = 0;
init_order(u->thisorder, NULL);
if (getunit(r, u->faction, &ut) != GET_UNIT) {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
"feedback_unit_not_found", ""));
continue;
}
if (!transport(ut, u)) {
if (cansee(u->faction, r, ut, 0)) {
cmistake(u, u->thisorder, 286, MSG_MOVE);
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
"feedback_unit_not_found", ""));
}
}
}
}
/* This calculates the weights of all transported units and
* adds them to an internal counter which is used by travel () to
* calculate effective weight and movement. */
if (!fval(r->terrain, SEA_REGION)) {
for (u = r->units; u; u = u->next) {
order *ord;
int w = 0;
for (ord = u->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_TRANSPORT) {
init_order(ord, NULL);
for (;;) {
unit *ut = 0;
if (getunit(r, u->faction, &ut) != GET_UNIT) {
break;
}
if (getkeyword(ut->thisorder) == K_DRIVE &&
can_move(ut) && !fval(ut, UFL_NOTMOVING) &&
!LongHunger(ut)) {
unit *u2;
init_order(ut->thisorder, NULL);
getunit(r, ut->faction, &u2);
if (u2 == u) {
w += weight(ut);
}
}
}
}
}
if (w > 0)
a_add(&u->attribs, a_new(&at_driveweight))->data.i = w;
}
}
}
}
static bool roadto(const region * r, direction_t dir)
{
/* wenn es hier genug strassen gibt, und verbunden ist, und es dort
* genug strassen gibt, dann existiert eine strasse in diese richtung */
region *r2;
assert(r);
assert(dir < MAXDIRECTIONS);
if (!r || dir >= MAXDIRECTIONS || dir < 0)
return false;
r2 = rconnect(r, dir);
if (!r2) {
return false;
}
if (r->attribs || r2->attribs) {
const curse_type *roads_ct = &ct_magicstreet;
if (roads_ct != NULL) {
if (get_curse(r->attribs, roads_ct) != NULL)
return true;
if (get_curse(r2->attribs, roads_ct) != NULL)
return true;
}
}
if (r->terrain->max_road <= 0)
return false;
if (r2->terrain->max_road <= 0)
return false;
if (rroad(r, dir) < r->terrain->max_road)
return false;
if (rroad(r2, dir_invert(dir)) < r2->terrain->max_road)
return false;
return true;
}
static const region_list *cap_route(region * r, const region_list * route,
const region_list * route_end, int speed)
{
region *current = r;
int moves = speed;
const region_list *iroute = route;
while (iroute && iroute != route_end) {
region *next = iroute->data;
direction_t reldir = reldirection(current, next);
/* adjust the range of the unit */
if (roadto(current, reldir))
moves -= BP_ROAD;
else
moves -= BP_NORMAL;
if (moves < 0)
break;
iroute = iroute->next;
current = next;
}
return iroute;
}
static region *next_region(unit * u, region * current, region * next)
{
connection *b;
b = get_borders(current, next);
while (b != NULL) {
if (b->type->move) {
region *rto = b->type->move(b, u, current, next, true);
if (rto != next) {
/* the target region was changed (wisps, for example). check the
* new target region for borders */
next = rto;
b = get_borders(current, next);
continue;
}
}
b = b->next;
}
return next;
}
static const region_list *reroute(unit * u, const region_list * route,
const region_list * route_end)
{
region *current = u->region;
while (route != route_end) {
region *next = next_region(u, current, route->data);
if (next != route->data)
break;
route = route->next;
}
return route;
}
static message *movement_error(unit * u, const char *token, order * ord,
int error_code)
{
direction_t d;
switch (error_code) {
case E_MOVE_BLOCKED:
d = get_direction(token, u->faction->locale);
return msg_message("moveblocked", "unit direction", u, d);
case E_MOVE_NOREGION:
return msg_feedback(u, ord, "unknowndirection", "dirname", token);
}
return NULL;
}
static void make_route(unit * u, order * ord, region_list ** routep)
{
region_list **iroute = routep;
region *current = u->region;
region *next = NULL;
char token[128];
const char *s = gettoken(token, sizeof(token));
int error = movewhere(u, s, current, &next);
if (error != E_MOVE_OK) {
message *msg = movement_error(u, s, ord, error);
if (msg != NULL) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
}
next = NULL;
}
while (next != NULL) {
if (current == next) {
/* PAUSE */
break;
}
next = next_region(u, current, next);
add_regionlist(iroute, next);
iroute = &(*iroute)->next;
current = next;
s = gettoken(token, sizeof(token));
error = movewhere(u, s, current, &next);
if (error != E_MOVE_OK) {
message *msg = movement_error(u, s, ord, error);
if (msg != NULL) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
}
next = NULL;
}
}
}
/** calculate the speed of a unit
*
* zu Fuss reist man 1 Region, zu Pferd 2 Regionen. Mit Strassen reist
* man zu Fuss 2, mit Pferden 3 weit.
*
* Berechnet wird das mit BPs. Zu Fuss hat man 4 BPs, zu Pferd 6.
* Normalerweise verliert man 3 BP pro Region, bei Strassen nur 2 BP.
* Ausserdem: Wenn Einheit transportiert, nur halbe BP
*/
int movement_speed(const unit * u)
{
int mp = 0;
const race *rc = u_race(u);
double dk = rc->speed;
assert(u->number);
/* dragons have a fixed speed, and no other effects work on them: */
if (u_race(u) == get_race(RC_SONGDRAGON)) {
mp = BP_DRAGON;
}
else {
if (fval(rc, RCF_DRAGON)) {
mp = BP_DRAGON;
}
else {
mp = walk_mode(u);
if (mp >= BP_RIDING) {
dk = 1.0;
}
if (u->attribs) {
curse *c = get_curse(u->attribs, &ct_speed);
if (c != NULL) {
int men = get_cursedmen(u, c);
dk *= 1.0 + (double)men / (double)u->number;
}
}
/* unicorn in inventory */
if (u->number <= i_get(u->items, it_find("fairyboot"))) {
mp *= 2;
}
/* Im Astralraum sind Tyb und Ill-Magier doppelt so schnell.
* Nicht kumulativ mit anderen Beschleunigungen! */
if (mp * dk <= BP_WALKING * u_race(u)->speed && is_astral(u->region)) {
struct sc_mage *mage = get_mage(u);
if (mage) {
magic_t mtype = mage_get_type(mage);
if (mtype == M_TYBIED || mtype == M_ILLAUN) {
mp *= 2;
}
}
}
}
}
return (int)(dk * mp);
}
enum {
TRAVEL_NORMAL,
TRAVEL_FOLLOWING,
TRAVEL_TRANSPORTED,
TRAVEL_RUNNING
};
static void var_create_regions(arg_regions *dst, const region_list * begin, int size)
{
const region_list *rsrc;
int i;
dst->nregions = size;
dst->regions = (region **) malloc(sizeof(region *) * (size_t)size);
assert_alloc(dst->regions);
for (i = 0, rsrc = begin; i != size; rsrc = rsrc->next, ++i) {
dst->regions[i] = rsrc->data;
}
}
static const region_list *travel_route(unit * u,
const region_list * route_begin, const region_list * route_end, order * ord,
int mode)
{
region *r = u->region;
region *current = u->region;
const region_list *iroute = route_begin;
int steps = 0;
bool landing = false; /* aquarians have landed */
while (iroute && iroute != route_end) {
region *next = iroute->data;
direction_t reldir = reldirection(current, next);
connection *b = get_borders(current, next);
/* check if we are caught by guarding units */
if (iroute != route_begin && mode != TRAVEL_RUNNING
&& mode != TRAVEL_TRANSPORTED) {
unit *wache = bewegung_blockiert_von(u, current);
if (wache != NULL) {
ADDMSG(&u->faction->msgs, msg_message("moveblockedbyguard",
"unit region guard", u, current, wache));
break;
}
}
/* check movement from/to oceans.
* aquarian special, flying units, horses, the works */
if ((u_race(u)->flags & RCF_FLY) == 0) {
if (!fval(next->terrain, SEA_REGION)) {
/* next region is land */
if (fval(current->terrain, SEA_REGION)) {
int moving = u_race(u)->flags & (RCF_SWIM | RCF_WALK | RCF_COASTAL);
/* Die Einheit kann nicht fliegen, ist im Ozean, und will an Land */
if (moving != (RCF_SWIM | RCF_WALK) && (moving & RCF_COASTAL) == 0) {
/* can't swim+walk and isn't allowed to enter coast from sea */
if (ord != NULL)
cmistake(u, ord, 44, MSG_MOVE);
break;
}
landing = true;
}
else if ((u_race(u)->flags & RCF_WALK) == 0) {
/* Spezialeinheiten, die nicht laufen koennen. */
ADDMSG(&u->faction->msgs, msg_message("detectocean",
"unit region terrain", u, next, terrain_name(next)));
break;
}
else if (landing) {
/* wir sind diese woche angelandet */
ADDMSG(&u->faction->msgs, msg_message("detectocean",
"unit region terrain", u, next, terrain_name(next)));
break;
}
}
else {
/* Ozeanfelder koennen nur von Einheiten mit Schwimmen und ohne
* Pferde betreten werden. */
if (!(canswim(u) || canfly(u))) {
ADDMSG(&u->faction->msgs, msg_message("detectocean",
"unit region terrain", u, next, terrain_name(next)));
break;
}
}
if (fval(current->terrain, SEA_REGION) || fval(next->terrain, SEA_REGION)) {
/* trying to enter or exit ocean with horses, are we? */
if (has_horses(u)) {
/* tries to do it with horses */
if (ord != NULL)
cmistake(u, ord, 67, MSG_MOVE);
break;
}
}
}
/* movement blocked by a wall */
if (reldir >= 0 && move_blocked(u, current, next)) {
ADDMSG(&u->faction->msgs, msg_message("leavefail",
"unit region", u, next));
break;
}
/* region ownership only: region owned by enemies */
if (!entrance_allowed(u, next)) {
ADDMSG(&u->faction->msgs, msg_message("regionowned",
"unit region target", u, current, next));
break;
}
/* illusionary units disappear in antimagic zones */
if (fval(u_race(u), RCF_ILLUSIONARY)) {
curse *c = get_curse(next->attribs, &ct_antimagiczone);
if (curse_active(c)) {
curse_changevigour(&next->attribs, c, (float)-u->number);
ADDMSG(&u->faction->msgs, msg_message("illusionantimagic", "unit", u));
set_number(u, 0);
break;
}
}
/* terrain is marked as forbidden (curse, etc) */
if (fval(next, RF_BLOCKED) || fval(next->terrain, FORBIDDEN_REGION)) {
ADDMSG(&u->faction->msgs, msg_message("detectforbidden",
"unit region", u, next));
break;
}
/* unit is an insect and cannot move into a glacier */
if (u_race(u) == get_race(RC_INSECT)) {
if (r_insectstalled(next) && is_freezing(u)) {
ADDMSG(&u->faction->msgs, msg_message("detectforbidden",
"unit region", u, next));
break;
}
}
/* effect of borders */
while (b != NULL) {
if (b->type->move) {
b->type->move(b, u, current, next, false);
}
b = b->next;
}
current = next;
iroute = iroute->next;
++steps;
if (u->number == 0)
break;
}
if (iroute != route_begin) {
/* the unit has moved at least one region */
int walkmode;
setguard(u, false);
if (getkeyword(ord) == K_ROUTE) {
order * norder = cycle_route(ord, u->faction->locale, steps);
replace_order(&u->orders, ord, norder);
free_order(norder);
}
if (mode == TRAVEL_RUNNING) {
walkmode = 0;
}
else if (walk_mode(u) >= BP_RIDING) {
walkmode = 1;
produceexp(u, SK_RIDING, u->number);
}
else {
walkmode = 2;
}
/* Berichte ueber Durchreiseregionen */
if (mode != TRAVEL_TRANSPORTED) {
arg_regions *arp = NULL;
if (steps > 1) {
arg_regions ar;
arp = &ar;
var_create_regions(arp, route_begin, steps - 1);
}
ADDMSG(&u->faction->msgs, msg_message("travel",
"unit mode start end regions", u, walkmode, r, current, arp));
}
mark_travelthru(u, r, route_begin, iroute);
move_unit(u, current, NULL);
/* make orders for the followers */
}
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
setguard(u, false);
assert(u->region == current);
return iroute;
}
static bool ship_ready(const region * r, unit * u, order * ord)
{
unit *cap = u->ship ? ship_owner(u->ship) : NULL;
if (u != cap) {
cmistake(u, ord, 146, MSG_MOVE);
return false;
}
if (effskill(u, SK_SAILING, r) < ship_captain_minskill(u->ship)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord,
"error_captain_skill_low", "value ship", ship_captain_minskill(u->ship),
u->ship));
return false;
}
if (u->number < u->ship->number) {
cmistake(u, ord, 329, MSG_MOVE);
return false;
}
if (!ship_finished(u->ship)) {
cmistake(u, ord, 15, MSG_MOVE);
return false;
}
if (!ship_crewed(u->ship, u)) {
cmistake(u, ord, 1, MSG_MOVE);
return false;
}
if (!cansail(r, u->ship)) {
cmistake(u, ord, 18, MSG_MOVE);
return false;
}
return true;
}
unit *owner_buildingtyp(const region * r, const building_type * bt)
{
building *b;
for (b = rbuildings(r); b; b = b->next) {
unit *owner = building_owner(b);
if (b->type == bt && owner != NULL) {
if (building_finished(b)) {
return owner;
}
}
}
return NULL;
}
/* Prueft, ob Ablegen von einer Kueste in eine der erlaubten Richtungen erfolgt. */
bool can_takeoff(const ship * sh, const region * from, const region * to)
{
if (!fval(from->terrain, SEA_REGION) && sh->coast != NODIRECTION) {
direction_t coast = sh->coast;
direction_t dir = reldirection(from, to);
direction_t coastr = (direction_t)((coast + 1) % MAXDIRECTIONS);
direction_t coastl =
(direction_t)((coast + MAXDIRECTIONS - 1) % MAXDIRECTIONS);
if (dir != coast && dir != coastl && dir != coastr
&& !buildingtype_exists(from, bt_find("harbour"), true)) {
return false;
}
}
return true;
}
static void sail(unit * u, order * ord, bool drifting)
{
region_list *route = NULL;
region *starting_point = u->region;
region *current_point, *last_point;
int k, step = 0;
region_list **iroute = &route;
ship *sh = u->ship;
faction *f = u->faction;
region *next_point = NULL;
int error;
bool storms_enabled = drifting && (config_get_int("rules.ship.storms", 1) != 0);
double damage_storm = storms_enabled ? config_get_flt("rules.ship.damage_storm", 0.02) : 0.0;
int lighthouse_div = config_get_int("rules.storm.lighthouse.divisor", 0);
const char *token = getstrtoken();
error = movewhere(u, token, starting_point, &next_point);
if (error) {
message *msg = movement_error(u, token, ord, error);
if (msg != NULL) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
}
return;
}
if (!ship_ready(starting_point, u, ord))
return;
/* Wir suchen so lange nach neuen Richtungen, wie es geht. Diese werden
* dann nacheinander ausgefuehrt. */
k = shipspeed(sh, u);
last_point = starting_point;
current_point = starting_point;
/* die naechste Region, in die man segelt, wird durch movewhere () aus der
* letzten Region bestimmt.
*
* Anfangen tun wir bei starting_point. next_point ist beim ersten
* Durchlauf schon gesetzt (Parameter!). current_point ist die letzte gueltige,
* befahrene Region. */
while (next_point && current_point != next_point && step < k) {
const terrain_type *tthis = current_point->terrain;
/* these values need to be updated if next_point changes (due to storms): */
const terrain_type *tnext = next_point->terrain;
assert(sh == u->ship || !"ship has sunk, but we didn't notice it");
if (fval(next_point->terrain, FORBIDDEN_REGION) || fval(next_point, RF_BLOCKED)) {
ADDMSG(&f->msgs, msg_message("sailforbidden",
"ship region", sh, next_point));
break;
}
if (!flying_ship(sh)) {
int reason;
if (storms_enabled) {
int stormchance = 0;
int stormyness;
gamedate date;
get_gamedate(turn, &date);
stormyness = storms ? storms[date.month] * 5 : 0;
/* storms should be the first thing we do. */
stormchance = stormyness / shipspeed(sh, u);
if (lighthouse_guarded(next_point)) {
if (lighthouse_div > 0) {
stormchance /= lighthouse_div;
}
else {
stormchance = 0;
}
}
if (rng_int() % 10000 < stormchance * sh->type->storm
&& fval(current_point->terrain, SEA_REGION)) {
if (!is_cursed(sh->attribs, &ct_nodrift)) {
region *rnext = NULL;
bool storm = true;
int d_offset = rng_int() % MAXDIRECTIONS;
direction_t d;
/* Sturm nur, wenn naechste Region Hochsee ist. */
for (d = 0; d != MAXDIRECTIONS; ++d) {
direction_t dnext = (direction_t)((d + d_offset) % MAXDIRECTIONS);
region *rn = rconnect(current_point, dnext);
if (rn != NULL) {
if (fval(rn->terrain, FORBIDDEN_REGION))
continue;
if (!fval(rn->terrain, SEA_REGION)) {
storm = false;
break;
}
if (rn != next_point)
rnext = rn;
}
}
if (storm && rnext != NULL) {
ADDMSG(&f->msgs, msg_message("storm", "ship region sink",
sh, current_point, sh->damage >= sh->size * DAMAGE_SCALE));
damage_ship(sh, damage_storm);
if (sh->damage >= sh->size * DAMAGE_SCALE) {
/* ship sinks, end journey here */
break;
}
next_point = rnext;
/* these values need to be updated if next_point changes (due to storms): */
tnext = next_point->terrain;
}
}
}
} /* storms_enabled */
if (!fval(tthis, SEA_REGION)) {
if (!fval(tnext, SEA_REGION)) {
/* check that you're not traveling from one land region to another. */
ADDMSG(&u->faction->msgs, msg_message("shipnoshore",
"ship region", sh, next_point));
break;
}
else {
if (!can_takeoff(sh, current_point, next_point)) {
/* Schiff kann nicht ablegen */
cmistake(u, ord, 182, MSG_MOVE);
break;
}
}
}
else if (fval(tnext, SEA_REGION)) {
/* target region is an ocean, and we're not leaving a shore */
if (!(sh->type->flags & SFL_OPENSEA)) {
/* ship can only stay close to shore */
direction_t d;
for (d = 0; d != MAXDIRECTIONS; ++d) {
region *rc = rconnect(next_point, d);
if (rc == NULL || !fval(rc->terrain, SEA_REGION))
break;
}
if (d == MAXDIRECTIONS) {
/* Schiff kann nicht aufs offene Meer */
cmistake(u, ord, 249, MSG_MOVE);
break;
}
}
}
reason = check_ship_allowed(sh, next_point);
if (reason < 0) {
/* for some reason or another, we aren't allowed in there.. */
if (reason == SA_NO_INSECT) {
ADDMSG(&f->msgs, msg_message("detectforbidden", "unit region", u, sh->region));
}
else if (lighthouse_guarded(current_point)) {
ADDMSG(&f->msgs, msg_message("sailnolandingstorm", "ship region", sh, next_point));
}
else {
double dmg = config_get_flt("rules.ship.damage.nolanding", 0.1);
ADDMSG(&f->msgs, msg_message("sailnolanding", "ship region", sh,
next_point));
damage_ship(sh, dmg);
/* we handle destruction at the end */
}
break;
}
if (curse_active(get_curse(next_point->attribs, &ct_maelstrom))) {
if (do_maelstrom(next_point, u) == NULL)
break;
}
}
/* !flying_ship */
/* Falls Blockade, endet die Seglerei hier */
if (move_blocked(u, current_point, next_point)) {
ADDMSG(&u->faction->msgs, msg_message("sailfail", "ship region", sh,
current_point));
break;
}
/* Falls kein Problem, eines weiter ziehen */
fset(sh, SF_MOVED);
if (iroute) {
add_regionlist(iroute, next_point);
iroute = &(*iroute)->next;
}
step++;
last_point = current_point;
current_point = next_point;
if (!fval(next_point->terrain, SEA_REGION)
&& !is_cursed(sh->attribs, &ct_flyingship)) {
break;
}
token = getstrtoken();
error = movewhere(u, token, current_point, &next_point);
if (error || next_point == NULL) {
message *msg = movement_error(u, token, ord, error);
if (msg != NULL) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
}
next_point = current_point;
break;
}
}
if (sh->damage >= sh->size * DAMAGE_SCALE) {
if (sh->region) {
ADDMSG(&f->msgs, msg_message("shipsink", "ship", sh));
sink_ship(sh);
remove_ship(&sh->region->ships, sh);
}
sh = NULL;
}
/* Nun enthaelt current_point die Region, in der das Schiff seine Runde
* beendet hat. Wir generieren hier ein Ereignis fuer den Spieler, das
* ihm sagt, bis wohin er gesegelt ist, falls er ueberhaupt vom Fleck
* gekommen ist. Das ist nicht der Fall, wenn er von der Kueste ins
* Inland zu segeln versuchte */
if (sh != NULL && fval(sh, SF_MOVED)) {
unit *harbourmaster;
/* nachdem alle Richtungen abgearbeitet wurden, und alle Einheiten
* transferiert wurden, kann der aktuelle Befehl geloescht werden. */
if (getkeyword(ord) == K_ROUTE) {
order * norder = cycle_route(ord, u->faction->locale, step);
replace_order(&u->orders, ord, norder);
free_order(norder);
}
set_coast(sh, last_point, current_point);
if (is_cursed(sh->attribs, &ct_flyingship)) {
ADDMSG(&f->msgs, msg_message("shipfly", "ship from to", sh,
starting_point, current_point));
}
else {
ADDMSG(&f->msgs, msg_message("shipsail", "ship from to", sh,
starting_point, current_point));
}
/* Das Schiff und alle Einheiten darin werden nun von
* starting_point nach current_point verschoben */
/* Verfolgungen melden */
if (fval(u, UFL_FOLLOWING)) {
caught_target(current_point, u);
}
move_ship(sh, starting_point, current_point, route);
/* Hafengebuehren ? */
harbourmaster = owner_buildingtyp(current_point, bt_find("harbour"));
if (harbourmaster != NULL) {
item *itm;
unit *u2;
item *trans = NULL;
for (u2 = current_point->units; u2; u2 = u2->next) {
if (u2->ship == sh && !alliedunit(harbourmaster, u->faction, HELP_GUARD)) {
if (effskill(harbourmaster, SK_PERCEPTION, NULL) > effskill(u2, SK_STEALTH, NULL)) {
for (itm = u2->items; itm; itm = itm->next) {
const luxury_type *ltype = resource2luxury(itm->type->rtype);
if (ltype != NULL && itm->number > 0) {
int st = itm->number * effskill(harbourmaster, SK_TRADE, NULL) / 50;
if (st > itm->number) st = itm->number;
if (st > 0) {
i_change(&u2->items, itm->type, -st);
i_change(&harbourmaster->items, itm->type, st);
i_add(&trans, i_new(itm->type, st));
}
}
}
}
}
}
if (trans) {
message *msg =
msg_message("harbor_trade", "unit items ship", harbourmaster, trans,
u->ship);
add_message(&u->faction->msgs, msg);
add_message(&harbourmaster->faction->msgs, msg);
msg_release(msg);
while (trans)
i_remove(&trans, trans);
}
}
}
free_regionlist(route);
}
/* Segeln, Wandern, Reiten
* when this routine returns a non-zero value, movement for the region needs
* to be done again because of followers that got new MOVE orders.
* Setting FL_LONGACTION will prevent a unit from being handled more than once
* by this routine
*
* the token parser needs to be initialized before calling this function!
*/
static const region_list *travel_i(unit * u, const region_list * route_begin,
const region_list * route_end, order * ord, int mode, follower ** followers)
{
region *r = u->region;
int mp;
if (u->building && !can_leave(u)) {
cmistake(u, ord, 150, MSG_MOVE);
return route_begin;
}
switch (canwalk(u)) {
case E_CANWALK_TOOHEAVY:
cmistake(u, ord, 57, MSG_MOVE);
return route_begin;
case E_CANWALK_TOOMANYHORSES:
cmistake(u, ord, 56, MSG_MOVE);
return route_begin;
case E_CANWALK_TOOMANYCARTS:
cmistake(u, ord, 42, MSG_MOVE);
return route_begin;
}
mp = movement_speed(u);
/* Siebenmeilentee */
if (get_effect(u, oldpotiontype[P_FAST]) >= u->number) {
mp *= 2;
change_effect(u, oldpotiontype[P_FAST], -u->number);
}
route_end = cap_route(r, route_begin, route_end, mp);
route_end = travel_route(u, route_begin, route_end, ord, mode);
if (u->flags&UFL_FOLLOWED) {
get_followers(u, r, route_end, followers);
}
/* transportation */
for (ord = u->orders; ord; ord = ord->next) {
unit *ut = 0;
if (getkeyword(ord) != K_TRANSPORT)
continue;
init_order(ord, NULL);
if (getunit(r, u->faction, &ut) == GET_UNIT) {
if (getkeyword(ut->thisorder) == K_DRIVE) {
if (ut->building && !can_leave(ut)) {
cmistake(ut, ut->thisorder, 150, MSG_MOVE);
cmistake(u, ord, 99, MSG_MOVE);
}
else if (!can_move(ut)) {
cmistake(u, ord, 99, MSG_MOVE);
}
else {
bool found = false;
if (!fval(ut, UFL_NOTMOVING) && !LongHunger(ut)) {
unit *u2;
init_order(ut->thisorder, NULL);
getunit(u->region, ut->faction, &u2);
if (u2 == u) {
const region_list *route_to =
travel_route(ut, route_begin, route_end, ord,
TRAVEL_TRANSPORTED);
if (route_to != route_begin) {
get_followers(ut, r, route_to, followers);
}
ADDMSG(&ut->faction->msgs, msg_message("transport",
"unit target start end", u, ut, r, ut->region));
found = true;
}
}
if (!found) {
if (cansee(u->faction, u->region, ut, 0)) {
cmistake(u, ord, 90, MSG_MOVE);
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord,
"feedback_unit_not_found", ""));
}
}
}
}
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "feedback_unit_not_found",
""));
}
}
return route_end;
}
/** traveling without ships
* walking, flying or riding units use this function
*/
static void travel(unit * u, order *ord)
{
region_list *route = NULL;
region *r = u->region;
follower *followers = NULL;
/* a few pre-checks that need not be done for each step: */
if (!fval(r->terrain, SEA_REGION)) {
ship *sh = u->ship;
if (!can_leave(u)) {
cmistake(u, ord, 150, MSG_MOVE);
return;
}
/* An Land kein NACH wenn in dieser Runde Schiff VERLASSEN! */
if (sh == NULL) {
sh = leftship(u);
if (sh && sh->region != u->region)
sh = NULL;
}
if (sh) {
unit *guard = is_guarded(r, u);
if (guard) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord,
"region_guarded", "guard", guard));
return;
}
}
if (u->ship && u_race(u)->flags & RCF_SWIM) {
cmistake(u, ord, 143, MSG_MOVE);
return;
}
}
else if (u->ship && fval(u->ship, SF_MOVED)) {
/* die Einheit ist auf einem Schiff, das sich bereits bewegt hat */
cmistake(u, ord, 13, MSG_MOVE);
return;
}
make_route(u, ord, &route);
if (route) {
/* und ab die post: */
travel_i(u, route, NULL, ord, TRAVEL_NORMAL, &followers);
/* followers */
while (followers != NULL) {
follower *fnext = followers->next;
unit *uf = followers->uf;
unit *ut = followers->ut;
const region_list *route_end = followers->route_end;
free(followers);
followers = fnext;
if (uf->region == r) {
order *follow_order;
const struct locale *lang = u->faction->locale;
const char *s = LOC(uf->faction->locale, parameters[P_UNIT]);
/* construct an order */
assert(s || !"missing translation for UNIT keyword");
follow_order = create_order(K_FOLLOW, lang, "%s %i",
s, ut->no);
route_end = reroute(uf, route, route_end);
travel_i(uf, route, route_end, follow_order, TRAVEL_FOLLOWING,
&followers);
caught_target(uf->region, uf);
free_order(follow_order);
}
}
free_regionlist(route);
}
}
void move_cmd_ex(unit * u, order * ord, const char *directions)
{
assert(u->number);
if (directions) {
/* Befehl ignorieren und Parsing vom String initialisieren. */
init_tokens_str(directions);
}
else {
init_order(ord, u->faction->locale);
}
if (u->ship && u == ship_owner(u->ship)) {
bool drifting = (getkeyword(ord) == K_MOVE);
sail(u, ord, drifting);
}
else {
travel(u, ord);
}
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
void move_cmd(unit * u, order * ord)
{
move_cmd_ex(u, ord, NULL);
set_order(&u->thisorder, NULL);
}
static void age_traveldir(region * r)
{
attrib *a = a_find(r->attribs, &at_traveldir);
while (a && a->type == &at_traveldir) {
attrib *an = a->next;
a->data.ca[3]--;
if (a->data.ca[3] <= 0) {
a_remove(&r->attribs, a);
}
a = an;
}
}
static direction_t hunted_dir(attrib * at, int id)
{
attrib *a = a_find(at, &at_shiptrail);
direction_t d = NODIRECTION;
while (a != NULL && a->type == &at_shiptrail) {
traveldir *t = (traveldir *)(a->data.v);
if (t->no == id) {
d = t->dir;
/* do not break, because we want the last one for this ship */
}
a = a->next;
}
return d;
}
int follow_ship(unit * u, order * ord)
{
region *rc = u->region;
sbstring sbcmd;
int moves, id, speed;
char command[256];
direction_t dir;
ship *sh;
if (fval(u, UFL_NOTMOVING)) {
return 0;
}
if (!fval(u_race(u), RCF_FLY|RCF_SWIM)) {
if (!u->ship) {
cmistake(u, ord, 144, MSG_MOVE);
return 0;
}
}
else if (u->ship && u != ship_owner(u->ship)) {
cmistake(u, ord, 146, MSG_MOVE);
return 0;
}
if (!can_move(u)) {
cmistake(u, ord, 55, MSG_MOVE);
return 0;
}
id = getid();
if (id <= 0) {
cmistake(u, ord, 20, MSG_MOVE);
return 0;
}
sh = findship(id);
dir = hunted_dir(rc->attribs, id);
if (dir == NODIRECTION) {
if (sh == NULL || sh->region != rc) {
cmistake(u, ord, 20, MSG_MOVE);
}
return 0;
}
sbs_init(&sbcmd, command, sizeof(command));
sbs_strcat(&sbcmd, LOC(u->faction->locale, directions[dir]));
moves = 1;
speed = getuint();
if (speed == 0) {
speed = shipspeed(u->ship, u);
}
else {
int maxspeed = shipspeed(u->ship, u);
if (maxspeed < speed)
speed = maxspeed;
}
rc = rconnect(rc, dir);
while (rc && (!sh || rc != sh->region) && moves < speed && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) {
const char *loc = LOC(u->faction->locale, directions[dir]);
sbs_strcat(&sbcmd, " ");
sbs_strcat(&sbcmd, loc);
moves++;
rc = rconnect(rc, dir);
}
/* In command steht jetzt das NACH-Kommando. */
/* NACH ausfuehren */
move_cmd_ex(u, ord, command);
return 1; /* true -> Einheitenliste von vorne durchgehen */
}
/* Bewegung, Verfolgung, Piraterie */
/** ships that folow other ships
* Dann generieren die jagenden Einheiten ihre Befehle und
* bewegen sich.
* Following the trails of other ships.
*/
static void move_followers(void)
{
region *r;
for (r = regions; r; r = r->next) {
unit **up = &r->units;
while (*up != NULL) {
unit *u = *up;
if (!fval(u, UFL_MOVED | UFL_NOTMOVING)) {
order *ord;
for (ord = u->orders; ord; ord = ord->next) {
if (getkeyword(ord) == K_FOLLOW) {
param_t p;
init_order(ord, NULL);
p = getparam(u->faction->locale);
if (p != P_SHIP) {
if (p != P_UNIT) {
cmistake(u, ord, 240, MSG_MOVE);
}
break;
}
/* wir folgen definitiv einem Schiff. */
if (fval(u, UFL_NOTMOVING)) {
cmistake(u, ord, 52, MSG_MOVE);
break;
}
else if (!can_move(u)) {
cmistake(u, ord, 55, MSG_MOVE);
break;
}
if (!LongHunger(u) && follow_ship(u, ord)) {
up = &r->units;
break;
}
}
}
}
if (*up == u)
up = &u->next;
}
}
}
/** Piraten and Drift
*
*/
static void move_pirates(void)
{
region *r;
for (r = regions; r; r = r->next) {
unit **up = &r->units;
while (*up) {
unit *u = *up;
if (!fval(u, UFL_NOTMOVING) && getkeyword(u->thisorder) == K_PIRACY) {
piracy_cmd(u);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
if (*up == u) {
/* not moved, use next unit */
up = &u->next;
}
else if (*up && (*up)->region != r) {
/* moved the previous unit along with u (units on same ship)
* must start from the beginning again */
up = &r->units;
}
/* else *up is already the next unit */
}
age_piracy(r);
age_traveldir(r);
}
}
void movement(void)
{
int ships;
/* Initialize the additional encumbrance by transported units */
init_transportation();
/* Move ships in last phase, others first
* This is to make sure you can't land someplace and then get off the ship
* in the same turn.
*/
for (ships = 0; ships <= 1; ++ships) {
region *r = regions;
while (r != NULL) {
unit **up = &r->units;
bool repeat = false;
while (*up) {
unit *u = *up;
keyword_t kword;
if (u->ship && fval(u->ship, SF_DRIFTED)) {
up = &u->next;
continue;
}
kword = getkeyword(u->thisorder);
if (kword == K_ROUTE || kword == K_MOVE) {
/* after moving, the unit has no thisorder. this prevents
* it from moving twice (or getting error messages twice).
* UFL_NOTMOVING is set in combat if the unit is not allowed
* to move because it was involved in a battle.
*/
if (fval(u, UFL_NOTMOVING)) {
if (fval(u, UFL_LONGACTION)) {
cmistake(u, u->thisorder, 52, MSG_MOVE);
set_order(&u->thisorder, NULL);
}
else {
cmistake(u, u->thisorder, 319, MSG_MOVE);
set_order(&u->thisorder, NULL);
}
}
else if (fval(u, UFL_MOVED)) {
cmistake(u, u->thisorder, 187, MSG_MOVE);
set_order(&u->thisorder, NULL);
}
else if (!can_move(u)) {
cmistake(u, u->thisorder, 55, MSG_MOVE);
set_order(&u->thisorder, NULL);
}
else {
if (ships) {
if (u->ship && ship_owner(u->ship) == u) {
move_cmd(u, u->thisorder);
}
}
else {
if (!u->ship || ship_owner(u->ship) != u) {
move_cmd(u, u->thisorder);
}
}
}
}
if (u->region == r) {
/* not moved, use next unit */
up = &u->next;
}
else {
if (*up && (*up)->region != r) {
/* moved the upcoming unit along with u (units on ships or followers,
* for example). must start from the beginning again immediately */
up = &r->units;
repeat = false;
}
else {
repeat = true;
}
}
/* else *up is already the next unit */
}
if (repeat)
continue;
if (ships == 0) {
/* Abtreiben von beschaedigten, unterbemannten, ueberladenen Schiffen */
drifting_ships(r);
}
r = r->next;
}
}
move_followers();
move_pirates();
}
/** Overrides long orders with a FOLLOW order if the target is moving.
* BUGS: http://bugs.eressea.de/view.php?id=1444 (A folgt B folgt C)
*/
void follow_unit(unit * u)
{
region *r = u->region;
attrib *a = NULL;
order *ord;
if (fval(u, UFL_NOTMOVING) || LongHunger(u))
return;
for (ord = u->orders; ord; ord = ord->next) {
const struct locale *lang = u->faction->locale;
if (getkeyword(ord) == K_FOLLOW) {
int id;
param_t p;
init_order(ord, NULL);
p = getparam(lang);
if (p == P_UNIT) {
id = read_unitid(u->faction, r);
if (id == u->no) {
ADDMSG(&u->faction->msgs, msg_message("followfail", "unit follower",
u, u));
continue;
}
if (a != NULL) {
a = a_find(u->attribs, &at_follow);
}
if (id > 0) {
unit *uf = findunit(id);
if (!a) {
a = a_add(&u->attribs, make_follow(uf));
}
else {
a->data.v = uf;
}
}
else if (a) {
a_remove(&u->attribs, a);
a = NULL;
}
}
}
}
if (a && !fval(u, UFL_MOVED | UFL_NOTMOVING)) {
bool follow = false;
unit *u2 = a->data.v;
if (!u2 || u2->region != r || !cansee(u->faction, r, u2, 0)) {
return;
}
switch (getkeyword(u2->thisorder)) {
case K_MOVE:
case K_ROUTE:
case K_DRIVE:
follow = true;
break;
default:
for (ord = u2->orders; ord; ord = ord->next) {
if (K_FOLLOW == getkeyword(ord)) {
follow = true;
break;
}
}
break;
}
if (!follow) {
attrib *a2 = a_find(u2->attribs, &at_follow);
if (a2 != NULL) {
unit *u3 = a2->data.v;
follow = (u3 && u2->region == u3->region);
}
}
if (follow) {
fset(u2, UFL_FOLLOWED);
/* FOLLOW unit on a (potentially) moving unit prevents long orders */
fset(u, UFL_FOLLOWING | UFL_LONGACTION);
set_order(&u->thisorder, NULL);
}
}
}