/* vi: set ts=2: * * Eressea PB(E)M host Copyright (C) 1998-2003 * Christian Schlittchen (corwin@amber.kn-bremen.de) * Katja Zedel (katze@felidae.kn-bremen.de) * Henning Peters (faroul@beyond.kn-bremen.de) * Enno Rehling (enno@eressea-pbem.de) * Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de) * * based on: * * Atlantis v1.0 13 September 1993 Copyright 1993 by Russell Wallace * Atlantis v1.7 Copyright 1996 by Alex Schröder * * This program may not be used, modified or distributed without * prior permission by the authors of Eressea. * This program may not be sold or used commercially without prior written * permission from the authors. */ #include #include "eressea.h" #include "movement.h" #include "alchemy.h" #include "border.h" #include "build.h" #include "building.h" #include "curse.h" #include "faction.h" #include "item.h" #include "karma.h" #include "magic.h" #include "message.h" #include "order.h" #include "plane.h" #include "race.h" #include "region.h" #include "render.h" #include "spell.h" #include "ship.h" #include "skill.h" #include "teleport.h" #include "unit.h" /* util includes */ #include #include #include #include /* libc includes */ #include #include #include #include #include /* attributes includes */ #include #include #include extern border_type bt_wisps; 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 ungünstiger Stelle */ DEFAULT_WRITE, DEFAULT_READ }; typedef struct follower { struct follower * next; unit * uf; unit * ut; region_list * route_end; } follower; static void get_followers(unit * target, region * r, region_list * route_end, follower ** followers) { unit * uf; for (uf=r->units;uf;uf=uf->next) { if (fval(uf, UFL_FOLLOWING) && !fval(uf, UFL_LONGACTION)) { const attrib * a = a_findc(uf->attribs, &at_follow); if (a && a->data.v == target) { follower * fnew = malloc(sizeof(follower)); fnew->uf = uf; fnew->ut = target; fnew->route_end = route_end; fnew->next = *followers; *followers = fnew; } } } } static void shiptrail_init(attrib *a) { a->data.v = calloc(1, sizeof(traveldir)); } static void shiptrail_finalize(attrib *a) { free(a->data.v); } static int shiptrail_age(attrib *a) { traveldir *t = (traveldir *)(a->data.v); t->age--; if(t->age == 0) return 0; return 1; } static int shiptrail_read(attrib *a, FILE *f) { traveldir *t = (traveldir *)(a->data.v); int no, age, dir; fscanf(f, "%d %d %d", &no, &dir, &age); t->no = no; t->dir = (direction_t)dir; t->age = age; return AT_READ_OK; } static void shiptrail_write(const attrib *a, FILE *f) { traveldir *t = (traveldir *)(a->data.v); fprintf(f, "%d %d %d ", t->no, (int)t->dir, t->age); } attrib_type at_shiptrail = { "traveldir_new", shiptrail_init, shiptrail_finalize, shiptrail_age, shiptrail_write, shiptrail_read }; static int age_speedup(attrib *a) { if (a->data.sa[0] > 0) { a->data.sa[0] = a->data.sa[0] - a->data.sa[1]; } return a->data.sa[0]>0; } attrib_type at_speedup = { "speedup", NULL, NULL, age_speedup, a_writedefault, a_readdefault }; /* ------------------------------------------------------------- */ direction_t getdirection(const struct locale * lang) { return finddirection(getstrtoken(), lang); } /* ------------------------------------------------------------- */ static attrib_type at_driveweight = { "driveweight", NULL, NULL, NULL, NULL, NULL }; static boolean entrance_allowed(const struct unit * u, const struct region * r) { #ifdef REGIONOWNERS unit * owner = region_owner(r); if (owner==NULL || u->faction==owner->faction) return true; if (alliedunit(owner, u->faction, HELP_TRAVEL)) return true; if (is_enemy(u->faction, owner->faction)) return true; return false; #endif return true; } int personcapacity(const unit *u) { #if RACE_CAPACITY int cap = u->race->weight+u->race->capacity; #else int cap = u->race->weight+540; if (old_race(u->race) == RC_TROLL) cap += 540; #if RACE_ADJUSTMENTS else if(old_race(u->race) == RC_GOBLIN) cap -= 100; #endif #endif if (fspecial(u->faction, FS_QUICK)) cap -= 200; 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 int ridingcapacity(unit * u) { int n; int wagen, pferde; n = 0; /* Man trägt sein eigenes Gewicht plus seine Kapazität! Die Menschen * tragen nichts (siehe walkingcapacity). Ein Wagen zählt nur, wenn er * von zwei Pferden gezogen wird */ pferde = min(get_item(u, I_HORSE), effskill(u, SK_RIDING) * u->number * 2); if(fval(u->race, RCF_HORSE)) pferde += u->number; /* maximal diese Pferde können zum Ziehen benutzt werden */ wagen = min(pferde / HORSESNEEDED, get_item(u, I_WAGON)); n = wagen * WAGONCAPACITY + pferde * HORSECAPACITY; return n; } int walkingcapacity(const struct unit * u) { int n, tmp, personen, pferde, pferde_fuer_wagen; int wagen, wagen_ohne_pferde, wagen_mit_pferden, wagen_mit_trollen; /* Das Gewicht, welches die Pferde tragen, plus das Gewicht, welches * die Leute tragen */ pferde = get_item(u, I_HORSE); if (fval(u->race, RCF_HORSE)) { pferde += u->number; personen = 0; } else { personen = u->number; } wagen = get_item(u, I_WAGON); pferde_fuer_wagen = min(pferde, effskill(u, SK_RIDING) * u->number * 4); /* maximal diese Pferde können zum Ziehen benutzt werden */ wagen_mit_pferden = min(wagen, pferde_fuer_wagen / HORSESNEEDED); n = wagen_mit_pferden * WAGONCAPACITY; if (old_race(u->race) == RC_TROLL) { /* 4 Trolle ziehen einen Wagen. */ /* Unbesetzte Wagen feststellen */ wagen_ohne_pferde = wagen - wagen_mit_pferden; /* Genug Trolle, um die Restwagen zu ziehen? */ wagen_mit_trollen = min(u->number / 4, wagen_ohne_pferde); /* Wagenkapazität hinzuzählen */ n += wagen_mit_trollen * WAGONCAPACITY; wagen_ohne_pferde -= wagen_mit_trollen; } n += pferde * HORSECAPACITY; n += personen * personcapacity(u); /* Goliathwasser */ tmp = get_effect(u, oldpotiontype[P_STRONG]); n += min(personen, tmp) * (HORSECAPACITY - personcapacity(u)); /* change_effect wird in ageing gemacht */ tmp = get_item(u, I_TROLLBELT); n += min(personen, tmp) * (STRENGTHMULTIPLIER-1) * personcapacity(u); return n; } enum { E_CANWALK_OK = 0, E_CANWALK_TOOMANYHORSES, E_CANWALK_TOOMANYCARTS, E_CANWALK_TOOHEAVY }; static int canwalk(unit * u) { int wagen, maxwagen; int pferde, maxpferde; /* workaround: monsters are too stupid to drop items, therefore they have * infinite carrying capacity */ if (u->faction->no == 0) return E_CANWALK_OK; wagen = get_item(u, I_WAGON); pferde = get_item(u, I_HORSE); maxwagen = effskill(u, SK_RIDING) * u->number * 2; if (old_race(u->race) == RC_TROLL) { maxwagen = max(maxwagen, u->number / 4); } maxpferde = effskill(u, SK_RIDING) * u->number * 4 + u->number; if (pferde > maxpferde) return E_CANWALK_TOOMANYHORSES; if (walkingcapacity(u) - eff_weight(u) >= 0) return E_CANWALK_OK; /* Stimmt das Gewicht, impliziert dies hier, daß 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 (wagen > maxwagen) return E_CANWALK_TOOMANYCARTS; /* Es muß nicht zwingend an den Wagen liegen, aber egal... (man * könnte z.B. auch 8 Eisen abladen, damit ein weiterer Wagen als * Fracht draufpaßt) */ return E_CANWALK_TOOHEAVY; } boolean canfly(unit *u) { if (get_movement(&u->attribs, MV_CANNOTMOVE)) return false; if(get_item(u, I_HORSE)) return false; if(get_item(u, I_PEGASUS) >= u->number && effskill(u, SK_RIDING) >= 4) return true; if (fval(u->race, RCF_FLY)) return true; if (get_movement(&u->attribs, MV_FLY)) return true; return false; } boolean canswim(unit *u) { if (get_movement(&u->attribs, MV_CANNOTMOVE)) return false; if (get_item(u, I_HORSE)) return false; if (get_item(u, I_DOLPHIN) >= u->number && effskill(u, SK_RIDING) >= 4) return true; if(fspecial(u->faction, FS_AMPHIBIAN)) return true; if (u->race->flags & RCF_FLY) return true; if (u->race->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 canride(unit * u) { int pferde, maxpferde, unicorns, maxunicorns; int skill = effskill(u, SK_RIDING); unicorns = get_item(u, I_UNICORN); pferde = get_item(u, I_HORSE); maxunicorns = (skill/5) * u->number; maxpferde = skill * u->number * 2; if(!(u->race->flags & RCF_HORSE) && ((pferde == 0 && unicorns == 0) || pferde > maxpferde || unicorns > maxunicorns)) { return 0; } if(ridingcapacity(u) - eff_weight(u) >= 0) { if(pferde == 0 && unicorns >= u->number && !(u->race->flags & RCF_HORSE)) { return 2; } return 1; } return 0; } static boolean cansail(const region * r, ship * sh) { int n = 0, p = 0; /* sonst ist construction:: size nicht ship_type::maxsize */ assert(!sh->type->construction || sh->type->construction->improvement==NULL); if (sh->type->construction && sh->size!=sh->type->construction->maxsize) return false; getshipweight(sh, &n, &p); if( is_cursed(sh->attribs, C_SHIP_FLYING, 0) ) { if (sh->type->cargo>500*100) assert(!"Ein Schiff wurde verzaubert, das zu groß ist"); if (n > 10000) return false; } if (n > shipcapacity(sh)) return false; if (p > sh->type->cabins) return false; return true; } int enoughsailors(const ship * sh, const region * r) { int n; unit *u; n = 0; for (u = r->units; u; u = u->next) { if (u->ship == sh) n += eff_skill(u, SK_SAILING, r) * u->number; } return n >= sh->type->sumskill; } /* ------------------------------------------------------------- */ static ship * do_maelstrom(region *r, unit *u) { int damage; damage = rand()%150 - eff_skill(u, SK_SAILING, r)*5; if(damage <= 0) { add_message(&u->faction->msgs, new_message(u->faction, "entermaelstrom%r:region%h:ship%i:damage%i:sink", r, u->ship, damage, 1)); return u->ship; } damage_ship(u->ship, 0.01*damage); if (u->ship->damage >= u->ship->size * DAMAGE_SCALE) { ADDMSG(&u->faction->msgs, msg_message("entermaelstrom", "region ship damage sink", r, u->ship, damage, 1)); destroy_ship(u->ship); return NULL; } ADDMSG(&u->faction->msgs, msg_message("entermaelstrom", "region ship damage sink", r, u->ship, damage, 0)); return u->ship; } /** sets a marker in the region telling that the unit has travelled through it * this is used for two distinctly different purposes: * - to report that a unit has travelled through. the report function * makes sure to only report the ships of travellers, not the travellers * themselves * - to report the region to the traveller */ void travelthru(const unit * u, region * r) { attrib *ru = a_add(&r->attribs, a_new(&at_travelunit)); ru->data.v = (void*)u; /* the first and last region of the faction gets reset, because travelthrough * could be in regions that are located before the [first, last] interval, * and recalculation is needed */ u->faction->first = 0; u->faction->last = 0; } static 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=0) { traveldir * td = NULL; attrib * a = a_find(r->attribs, &at_shiptrail); while (a!=NULL) { td = (traveldir *)a->data.v; if (td->no == sh->no) break; a = a->nexttype; } if (a==NULL) { 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(const unit * u, region * r, region_list * route, region_list * route_end) { /* kein travelthru in der letzten region! */ while (route!=route_end) { travelthru(u, r); r = route->data; route = route->next; } } ship * move_ship(ship * sh, region * from, region * to, region_list * route) { unit **iunit = &from->units; unit **ulist = &to->units; boolean trail = (route==NULL); if (from!=to) { translist(&from->ships, &to->ships, sh); sh->region = to; } while (*iunit!=NULL) { unit *u = *iunit; assert(u->region==from); if (u->ship == sh) { if (!trail) { leave_trail(sh, from, route); trail = true; } if (route!=NULL) mark_travelthru(u, from, route, NULL); if (from!=to) { u->ship = NULL; /* damit move_unit() kein leave() macht */ move_unit(u, to, ulist); ulist = &u->next; u->ship = sh; } if (route && eff_skill(u, SK_SAILING, from) >= 1) { produceexp(u, SK_SAILING, u->number); } } if (*iunit==u) iunit=&u->next; } #ifndef NDEBUG if (rterrain(sh->region)!=T_OCEAN && sh->coast!=NODIRECTION) { region * rcoast = rconnect(sh->region, sh->coast); if (rterrain(rcoast)!=T_OCEAN) { log_error(("ship %s sailed into a coast with no ocean neighbours.\n", shipid(sh))); } } #endif return sh; } static boolean check_working_buildingtype(const region * r, const building_type * bt) { building *b; for (b = rbuildings(r); b; b = b->next) { if (b->type == bt) { if (b->size >= bt->maxsize && fval(b, BLD_WORKING)) { return true; } } } return false; } static boolean is_freezing(const unit * u) { if (old_race(u->race)!=RC_INSECT) return false; if (is_cursed(u->attribs, C_KAELTESCHUTZ, 0)) return false; return true; } static boolean ship_allowed(const struct ship * sh, const region * r) { int c = 0; terrain_t t = rterrain(r); static const building_type * bt_harbour=NULL; if (bt_harbour==NULL) bt_harbour=bt_find("harbour"); if (r_insectstalled(r)) { /* insekten dürfen nicht hier rein. haben wir welche? */ unit * u; for (u=sh->region->units;u!=NULL;u=u->next) { if (u->ship!=sh) continue; if (is_freezing(u)) { unit * captain = shipowner(sh); ADDMSG(&captain->faction->msgs, msg_message("detectforbidden", "unit region", u, r)); return false; } } } if (check_working_buildingtype(r, bt_harbour)) return true; for (c=0;sh->type->coast[c]!=NOTERRAIN;++c) { if (sh->type->coast[c]==t) return true; } return false; } static boolean flying_ship(const ship * sh) { if (sh->type->flags & SFL_FLY) return true; if (is_cursed(sh->attribs, C_SHIP_FLYING, 0)) return true; return false; } static void set_coast(ship * sh, region * r, region * rnext) { if (rnext->terrain != T_OCEAN && !flying_ship(sh)) { sh->coast = reldirection(rnext, r); assert(rterrain(r)==T_OCEAN); } else { sh->coast = NODIRECTION; } } static void drifting_ships(region * r) { direction_t d; if (rterrain(r) == T_OCEAN) { ship** shp = &r->ships; while (*shp) { ship * sh = *shp; region * rnext = NULL; region_list * route = NULL; unit * captain; int d_offset; /* Schiff schon abgetrieben oder durch Zauber geschützt? */ if (fval(sh, SF_DRIFTED) || is_cursed(sh->attribs, C_SHIP_NODRIFT, 0)) { shp = &sh->next; continue; } /* Kapitän bestimmen */ for (captain = r->units; captain; captain = captain->next) { if (captain->ship != sh) continue; if (eff_skill(captain, SK_SAILING, r) >= sh->type->cptskill) { break; } } /* Kapitän da? Beschädigt? Genügend Matrosen? * Genügend leicht? Dann ist alles OK. */ assert(sh->type->construction->improvement==NULL); /* sonst ist construction::size nicht ship_type::maxsize */ if (captain && sh->size==sh->type->construction->maxsize && enoughsailors(sh, r) && cansail(r, sh)) { shp = &sh->next; continue; } /* Auswahl einer Richtung: Zuerst auf Land, dann * zufällig. Falls unmögliches Resultat: vergiß es. */ d_offset = rand() % MAXDIRECTIONS; for (d = 0; d != MAXDIRECTIONS; ++d) { region * rn = rconnect(r, (direction_t)((d + d_offset) % MAXDIRECTIONS)); terrain_t t = rterrain(rn); if (rn!=NULL && (terrain[t].flags & SAIL_INTO) && ship_allowed(sh, rn)) { rnext = rn; if (t!=T_OCEAN) break; } } if (rnext==NULL) { shp = &sh->next; continue; } /* 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); sh = move_ship(sh, r, rnext, route); free_regionlist(route); if (sh!=NULL) { fset(sh, SF_DRIFTED); damage_ship(sh, 0.02); if (sh->damage>=sh->size * DAMAGE_SCALE) { destroy_ship(sh); } } if (*shp == sh) shp = &sh->next; } } } static boolean present(region * r, unit * u) { return (boolean) (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 (!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)); } } } /* TODO: Unsichtbarkeit bedenken ! */ static unit * bewegung_blockiert_von(unit * reisender, region * r) { unit *u; int perception = 0; boolean contact = false; unit * guard = NULL; if (fval(reisender->race, RCF_ILLUSIONARY)) return NULL; for (u=r->units;u && !contact;u=u->next) { if (getguard(u) & GUARD_TRAVELTHRU) { int sk = eff_skill(u, SK_OBSERVATION, r); if (invisible(reisender) >= reisender->number && !get_item(u, I_AMULET_OF_TRUE_SEEING)) continue; if (u->faction==reisender->faction) contact = true; else if (ucontact(u, reisender)) contact = true; else if (alliedunit(u, reisender->faction, HELP_GUARD)) contact = true; else if (sk>=perception) { perception = sk; guard = u; } } } if (!contact && guard) { double prob = 0.3; /* 30% base chance */ prob += 0.1 * (perception - eff_stealth(reisender, r)); prob += 0.1 * max(guard->number, get_item(guard, I_AMULET_OF_TRUE_SEEING)); if (chance(prob)) { return guard; } } return NULL; } static boolean is_guardian(unit * u2, unit *u, unsigned int mask) { if (u2->faction != u->faction && getguard(u2)&mask && u2->number && !ucontact(u2, u) && !besieged(u2) && alliedunit(u2, u->faction, HELP_GUARD) != HELP_GUARD && armedmen(u2) && cansee(u2->faction, u->region, u, 0) ) return true; return false; } unit * is_guarded(region * r, unit * u, unsigned int mask) { unit *u2; for (u2 = r->units; u2; u2 = u2->next) if (is_guardian(u2, u, mask)) return u2; return NULL; } static const char *shortdirections[MAXDIRECTIONS] = { "dir_nw", "dir_ne", "dir_east", "dir_se", "dir_sw", "dir_west" }; static void cycle_route(unit *u, int gereist) { int cm = 0; char tail[1024]; char neworder[2048]; const char *token; direction_t d = NODIRECTION; boolean paused = false; boolean pause; if (get_keyword(u->thisorder) != K_ROUTE) return; tail[0] = '\0'; strcpy(neworder, locale_string(u->faction->locale, keywords[K_ROUTE])); init_tokens(u->thisorder); skip_token(); for (cm=0;;++cm) { const struct locale * lang = u->faction->locale; pause = false; token = getstrtoken(); d = finddirection(token, lang); if(d == D_PAUSE) { pause = true; } else if (d==NODIRECTION) { break; } if (cmsizeof(neworder)/2) break; else if (cm==gereist && !paused && pause) { strcat(strcat(tail, " "), LOC(lang, parameters[P_PAUSE])); paused=true; } else if (pause) { /* da PAUSE nicht in ein shortdirections[d] umgesetzt wird (ist * hier keine normale direction), muss jede PAUSE einzeln * herausgefiltert und explizit gesetzt werden */ strcat(strcat(neworder, " "), LOC(lang, parameters[P_PAUSE])); } else { strcat(strcat(neworder, " "), LOC(lang, shortdirections[d])); } } strcat(neworder, tail); set_order(&u->lastorder, parse_order(neworder, u->faction->locale)); free_order(u->lastorder); /* parse_order & set_order have each increased the refcount */ } static boolean transport(unit * ut, unit * u) { order * ord; if (LongHunger(u)) { return false; } for (ord = ut->orders; ord; ord = ord->next) { if (get_keyword(ord) == K_TRANSPORT) { init_tokens(ord); skip_token(); if (getunit(ut->region, ut->faction) == u) { return true; } } } return false; } 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 (get_keyword(u->thisorder) == K_DRIVE && !fval(u, UFL_LONGACTION) && !LongHunger(u)) { unit * ut; init_tokens(u->thisorder); skip_token(); ut = getunit(r, u->faction); if (ut==NULL) { cmistake(u, u->thisorder, 63, MSG_MOVE); continue; } if (!transport(ut, u)) { if (cansee(u->faction, r, ut, 0)) { cmistake(u, u->thisorder, 286, MSG_MOVE); } else { cmistake(u, u->thisorder, 63, MSG_MOVE); } } } } /* 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. */ for (u=r->units; u; u=u->next) { order * ord; int w = 0; for (ord = u->orders; ord; ord = ord->next) { if (get_keyword(ord) == K_TRANSPORT) { unit * ut; init_tokens(ord); skip_token(); ut = getunit(r, u->faction); if (ut==NULL) continue; if (get_keyword(ut->thisorder) == K_DRIVE && !fval(ut, UFL_LONGACTION) && !LongHunger(ut)) { init_tokens(ut->thisorder); skip_token(); if (getunit(r, ut->faction) == u) { w += weight(ut); break; } } } } if (w > 0) a_add(&u->attribs, a_new(&at_driveweight))->data.i = w; } } } static boolean 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; static const curse_type * roads_ct = NULL; if (roads_ct==NULL) roads_ct = ct_find("magicstreet"); if (roads_ct!=NULL) { curse *c = get_curse(r->attribs, roads_ct); if (c!=NULL) return true; } if (dir>=MAXDIRECTIONS || dir<0) return false; r2 = rconnect(r, dir); if (r==NULL || r2==NULL) return false; if (terrain[rterrain(r)].roadreq==0) return false; if (terrain[rterrain(r2)].roadreq==0) return false; if (rroad(r, dir) < terrain[rterrain(r)].roadreq) return false; if (rroad(r2, dir_invert(dir)) < terrain[rterrain(r2)].roadreq) return false; return true; } static region_list * cap_route(region * r, region_list * route, region_list * route_end, int speed) { region * current = r; int moves = speed; region_list * iroute = route; while (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 void make_route(unit * u, order * ord, region_list ** routep) { region_list **iroute = routep; region * current = u->region; region * next = NULL; const char * token = getstrtoken(); int error = movewhere(u, token, current, &next); if (error!=E_MOVE_OK) { message * msg = movement_error(u, token, ord, error); if (msg!=NULL) { add_message(&u->faction->msgs, msg); msg_release(msg); } next = NULL; } while (next!=NULL) { border * b = get_borders(current, next); direction_t reldir; if (current==next) { /* PAUSE */ break; } 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 (bt_wisps, for example). check the * new target region for borders */ next = rto; b = get_borders(current, next); continue; } } b = b->next; } reldir = reldirection(current, next); add_regionlist(iroute, next); iroute = &(*iroute)->next; current = next; token = getstrtoken(); error = movewhere(u, token, current, &next); if (error) { message * msg = movement_error(u, token, ord, error); if (msg!=NULL) { add_message(&u->faction->msgs, msg); msg_release(msg); } next = NULL; } } } /** calculate the speed of a unit * * zu Fuß reist man 1 Region, zu Pferd 2 Regionen. Mit Straßen reist * man zu Fuß 2, mit Pferden 3 weit. * * Berechnet wird das mit BPs. Zu Fuß hat man 4 BPs, zu Pferd 6. * Normalerweise verliert man 3 BP pro Region, bei Straßen nur 2 BP. * Außerdem: Wenn Einheit transportiert, nur halbe BP */ static int movement_speed(unit * u) { int mp; static const curse_type * speed_ct; static boolean init = false; double dk = u->race->speed; /* dragons have a fixed speed, and no other effects work on them: */ switch (old_race(u->race)) { case RC_DRAGON: case RC_WYRM: case RC_FIREDRAGON: case RC_BIRTHDAYDRAGON: case RC_PSEUDODRAGON: return BP_DRAGON; } if (!init) { init = true; speed_ct = ct_find("speed"); } if (speed_ct) { curse *c = get_curse(u->attribs, speed_ct); if (c!=NULL) { int men = get_cursedmen(u, c); dk *= 1.0 + (double)men/(double)u->number; } } switch (canride(u)) { case 1: /* Pferd */ mp = BP_RIDING; break; case 2: /* Einhorn */ mp = BP_UNICORN; break; default: mp = BP_WALKING; /* faction special */ if (fspecial(u->faction, FS_QUICK)) mp = BP_RIDING; /* Siebenmeilentee */ if (get_effect(u, oldpotiontype[P_FAST]) >= u->number) { mp *= 2; change_effect(u, oldpotiontype[P_FAST], -u->number); } /* unicorn in inventory */ if (u->number <= get_item(u, I_FEENSTIEFEL)) { mp *= 2; } /* Im Astralraum sind Tyb und Ill-Magier doppelt so schnell. * Nicht kumulativ mit anderen Beschleunigungen! */ if (mp*dk <= BP_WALKING*u->race->speed && getplane(u->region) == get_astralplane() && is_mage(u)) { sc_mage * mage = get_mage(u); if (mage->magietyp == M_ASTRAL || mage->magietyp == M_TRAUM) { mp *= 2; } } break; } return (int)(dk*mp); } enum { TRAVEL_NORMAL, TRAVEL_FOLLOWING, TRAVEL_TRANSPORTED, TRAVEL_RUNNING }; static region_list * travel_route(unit * u, region_list * route_begin, region_list * route_end, order * ord, int mode) { region * r = u->region; region * current = u->region; region_list * iroute = route_begin; int steps = 0; while (iroute && iroute!=route_end) { region * next = iroute->data; direction_t reldir = reldirection(current, next); border * 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->flags & RCF_FLY) == 0) { if (rterrain(current)==T_OCEAN && rterrain(next) != T_OCEAN) { int moving = u->race->flags & (RCF_SWIM|RCF_WALK); /* Die Einheit kann nicht fliegen, ist im Ozean, und will an Land */ if (moving != (RCF_SWIM|RCF_WALK) && old_race(u->race) != RC_AQUARIAN) { /* can't swim+walk and isn't an aquarian */ if (ord!=NULL) cmistake(u, ord, 44, MSG_MOVE); break; } } if (rterrain(current)==T_OCEAN || rterrain(next) == T_OCEAN) { /* trying to enter or exit ocean with horses, are we? */ if (get_item(u, I_HORSE) > 0) { /* tries to do it with horses */ if (ord!=NULL) cmistake(u, ord, 67, MSG_MOVE); break; } } /* Ozeanfelder können nur von Einheiten mit Schwimmen und ohne * Pferde betreten werden. */ if (rterrain(next) == T_OCEAN && !canswim(u)) { ADDMSG(&u->faction->msgs, msg_message("detectocean", "unit region", u, next)); 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, RCF_ILLUSIONARY)) { curse * c = get_curse(next->attribs, ct_find("antimagiczone")); if (curse_active(c)) { curse_changevigour(&next->attribs, c, - u->number); ADDMSG(&u->faction->msgs, msg_message("illusionantimagic", "unit", u)); set_number(u, 0); break; } } /* terrain is marked as forbidden (curse, etc) */ if (terrain[rterrain(next)].flags & FORBIDDEN_LAND) { ADDMSG(&u->faction->msgs, msg_message("detectforbidden", "unit region", u, next)); break; } /* unit is an insect and cannot move into a glacier */ if (old_race(u->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) { region * rto = b->type->move(b, u, current, next, false); assert(rto==NULL); /* should return NULL when not routing */ } 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; region_list *rlist = route_begin; char * p = buf; region * next = r; setguard(u, GUARD_NONE); cycle_route(u, steps); if (mode==TRAVEL_RUNNING) { walkmode = 0; } if (canride(u)) { walkmode = 1; produceexp(u, SK_RIDING, u->number); } else { walkmode = 2; } /* Berichte über Durchreiseregionen */ *p = 0; while (rlist!=iroute) { if (next!=u->region && next!=current) { MSG(("travelthru_trail", "region", next), p, sizeof(buf) - (p-buf), u->faction->locale, u->faction); if (rlist->data!=current) { if (rlist->next->data==current) scat(" und "); else scat(", "); } p += strlen(p); } next = rlist->data; rlist = rlist->next; } if (mode!=TRAVEL_TRANSPORTED) { ADDMSG(&u->faction->msgs, msg_message("travel", "unit mode start end regions", u, walkmode, r, current, strdup(buf))); } mark_travelthru(u, r, route_begin, iroute); move_unit(u, current, NULL); /* make orders for the followers */ } fset(u, UFL_LONGACTION); setguard(u, GUARD_NONE); assert(u->region==current); return iroute; } static boolean ship_ready(const region * r, unit * u) { if (!fval(u, UFL_OWNER)) { cmistake(u, u->thisorder, 146, MSG_MOVE); return false; } if (eff_skill(u, SK_SAILING, r) < u->ship->type->cptskill) { sprintf(buf, "Der Kapitän muß mindestens Segeln %d haben, " "um %s zu befehligen ", u->ship->type->cptskill, LOC(u->faction->locale, u->ship->type->name[1])); mistake(u, u->thisorder, buf, MSG_MOVE); return false; } assert(u->ship->type->construction->improvement==NULL); /* sonst ist construction::size nicht ship_type::maxsize */ if (u->ship->size!=u->ship->type->construction->maxsize) { cmistake(u, u->thisorder, 15, MSG_MOVE); return false; } if (!enoughsailors(u->ship, r)) { cmistake(u, u->thisorder, 1, MSG_MOVE); /* mistake(u, u->thisorder, "Auf dem Schiff befinden sich zuwenig erfahrene Seeleute.", MSG_MOVE); */ return false; } if (!cansail(r, u->ship)) { if( is_cursed(u->ship->attribs, C_SHIP_FLYING, 0) ) cmistake(u, u->thisorder, 17, MSG_MOVE); else cmistake(u, u->thisorder, 18, MSG_MOVE); return false; } return true; } unit * owner_buildingtyp(const region * r, const building_type * bt) { building *b; unit *owner; for (b = rbuildings(r); b; b = b->next) { owner = buildingowner(r, b); if (b->type == bt && owner != NULL) { if (b->size >= bt->maxsize) { return owner; } } } return NULL; } boolean buildingtype_exists(const region * r, const building_type * bt) { building *b; for (b = rbuildings(r); b; b = b->next) { if (b->type == bt) { if (b->size >= bt->maxsize) { return true; } } } return false; } /* Prüft, ob Ablegen von einer Küste in eine der erlaubten Richtungen erfolgt. */ static boolean check_takeoff(ship *sh, region *from, region *to) { if (rterrain(from)!=T_OCEAN && 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 && !check_working_buildingtype(from, bt_find("harbour"))) { return false; } } return true; } static void sail(unit * u, order * ord, boolean move_on_land, region_list **routep) { region *starting_point = u->region; region *current_point, *last_point; int k, step = 0; region_list **iroute = routep; ship * sh = u->ship; faction * f = u->faction; region * next_point = NULL; int error; const char * token = getstrtoken(); if (routep) *routep = NULL; 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)) return; /* Wir suchen so lange nach neuen Richtungen, wie es geht. Diese werden * dann nacheinander ausgeführt. */ k = shipspeed(sh, u); last_point = starting_point; current_point = starting_point; /* die nächste 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 gültige, * befahrene Region. */ while (next_point && current_point!=next_point && step < k) { const char * token; int error; terrain_t tthis = rterrain(current_point); /* these values need to be updated if next_point changes (due to storms): */ terrain_t tnext = rterrain(next_point); direction_t dir = reldirection(current_point, next_point); assert(sh==u->ship || !"ship has sunk, but we didn't notice it"); if (terrain[tnext].flags & FORBIDDEN_LAND) { plane *pl = getplane(next_point); if (pl && fval(pl, PFL_NOCOORDS)) { ADDMSG(&f->msgs, msg_message("sailforbiddendir", "ship direction", sh, dir)); } else { ADDMSG(&f->msgs, msg_message("sailforbidden", "ship region", sh, next_point)); } break; } if (!flying_ship(sh)) { /* storms should be the first thing we do. */ int stormchance = storms[month(0)] * 5 / shipspeed(sh, u); if (check_leuchtturm(next_point, NULL)) stormchance /= 3; if (rand()%10000 < stormchance && next_point->terrain == T_OCEAN) { if (!is_cursed(sh->attribs, C_SHIP_NODRIFT, 0)) { region * rnext = NULL; boolean storm = true; int d_offset = rand() % MAXDIRECTIONS; direction_t d; /* Sturm nur, wenn nächste 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) { terrain_t t = rterrain(rn); if (terrain[t].flags & FORBIDDEN_LAND) continue; if (t!=T_OCEAN) { 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 the ship. we handle destruction in the end */ damage_ship(sh, 0.02); if (sh->damage>=sh->size * DAMAGE_SCALE) break; next_point = rnext; /* these values need to be updated if next_point changes (due to storms): */ tnext = rterrain(next_point); dir = reldirection(current_point, next_point); } } } if (tthis != T_OCEAN) { if (tnext != T_OCEAN) { if (!move_on_land) { /* check that you're not traveling from one land region to another. */ plane *pl = getplane(next_point); if (pl!=NULL && fval(pl, PFL_NOCOORDS)) { /* we don't have that case yet, but hey... */ sprintf(buf, "Die %s entdeckt, daß im %s Festland ist.", shipname(sh), locale_string(u->faction->locale, directions[dir])); } else { sprintf(buf, "Die %s entdeckt, daß (%d,%d) Festland ist.", shipname(sh), region_x(next_point,u->faction), region_y(next_point,u->faction)); } addmessage(0, u->faction, buf, MSG_MOVE, ML_WARN); break; } } else { if (check_takeoff(sh, current_point, next_point) == false) { /* Schiff kann nicht ablegen */ cmistake(u, ord, 182, MSG_MOVE); break; } } } else if (tnext==T_OCEAN) { /* 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 (rterrain(rc) != T_OCEAN) break; } if (d==MAXDIRECTIONS) { /* Schiff kann nicht aufs offene Meer */ cmistake(u, ord, 249, MSG_MOVE); break; } } } if (!ship_allowed(sh, next_point)) { /* for some reason or another, we aren't allowed in there.. */ if (check_leuchtturm(current_point, NULL)) { ADDMSG(&f->msgs, msg_message("sailnolandingstorm", "ship", sh)); } else { damage_ship(sh, 0.10); /* we handle destruction at the end */ } break; } if (is_cursed(next_point->attribs, C_MAELSTROM, 0)) { 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 (rterrain(current_point) != T_OCEAN && !is_cursed(sh->attribs, C_SHIP_FLYING, 0)) 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) { ADDMSG(&f->msgs, msg_message("shipsink", "ship", sh)); destroy_ship(sh); sh = NULL; } /* Nun enthält current_point die Region, in der das Schiff seine Runde * beendet hat. Wir generieren hier ein Ereignis für den Spieler, das * ihm sagt, bis wohin er gesegelt ist, falls er überhaupt vom Fleck * gekommen ist. Das ist nicht der Fall, wenn er von der Küste ins * Inland zu segeln versuchte */ if (sh!=NULL && fval(sh, SF_MOVED)) { unit * hafenmeister; /* nachdem alle Richtungen abgearbeitet wurden, und alle Einheiten * transferiert wurden, kann der aktuelle Befehl gelöscht werden. */ cycle_route(u, step); set_order(&u->thisorder, NULL); set_coast(sh, last_point, current_point); sprintf(buf, "Die %s ", shipname(sh)); if( is_cursed(sh->attribs, C_SHIP_FLYING, 0) ) scat("fliegt"); else scat("segelt"); scat(" von "); scat(regionid(starting_point)); scat(" nach "); scat(regionid(current_point)); scat("."); addmessage(0, u->faction, buf, MSG_MOVE, ML_INFO); /* 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); sh = move_ship(sh, starting_point, current_point, *routep); /* Hafengebühren ? */ hafenmeister = owner_buildingtyp(current_point, bt_find("harbour")); if (sh && hafenmeister != NULL) { item * itm; unit * u2; boolean first = true; item * trans = NULL; for (u2 = current_point->units; u2; u2 = u2->next) { if (u2->ship == sh && !alliedunit(hafenmeister, u->faction, HELP_GUARD)) { if (effskill(hafenmeister, SK_OBSERVATION) > effskill(u2, SK_STEALTH)) { 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(hafenmeister, SK_TRADE) / 50; st = min(itm->number, st); if (st > 0) { i_change(&u2->items, itm->type, -st); i_change(&hafenmeister->items, itm->type, st); i_add(&trans, i_new(itm->type, st)); } } } } } } if (trans) { sprintf(buf, "%s erhielt ", hafenmeister->name); for (itm = trans; itm; itm=itm->next) { if (!first) { if (itm->next!=NULL && itm->next->next==NULL) { scat(" und "); } else { scat(", "); } } first = false; icat(trans->number); scat(" "); if (itm->number == 1) { scat(locale_string(default_locale, resourcename(itm->type->rtype, 0))); } else { scat(locale_string(default_locale, resourcename(itm->type->rtype, NMF_PLURAL))); } } scat(" von der "); scat(shipname(u->ship)); scat("."); addmessage(0, u->faction, buf, MSG_COMMERCE, ML_INFO); addmessage(0, hafenmeister->faction, buf, MSG_INCOME, ML_INFO); while (trans) i_remove(&trans, trans); } } } } unit * get_captain(const ship * sh) { const region * r = sh->region; unit *u; for (u = r->units; u; u = u->next) { if (u->ship == sh && eff_skill(u, SK_SAILING, r) >= sh->type->cptskill) return u; } return NULL; } /* 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! */ /** fleeing units use this function */ void run_to(unit * u, region * to) { region_list * route = NULL; add_regionlist(&route, to); travel_route(u, route, NULL, NULL, TRAVEL_RUNNING); /* weder transport noch follow */ } static region_list * travel_i(unit * u, region_list * route_begin, region_list * route_end, order * ord, int mode, follower ** followers) { region * r = u->region; 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; } route_end = cap_route(r, route_begin, route_end, movement_speed(u)); route_end = travel_route(u, route_begin, route_end, ord, mode); get_followers(u, r, route_end, followers); /* transportation */ for (ord = u->orders; ord; ord = ord->next) { unit * ut; if (get_keyword(ord) != K_TRANSPORT) continue; init_tokens(ord); skip_token(); ut = getunit(r, u->faction); if (ut!=NULL) { boolean found = false; if (get_keyword(ut->thisorder) == K_DRIVE) { if (!fval(ut, UFL_LONGACTION) && !LongHunger(ut)) { init_tokens(ut->thisorder); skip_token(); if (getunit(u->region, ut->faction) == u) { 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 { cmistake(u, ord, 63, MSG_MOVE); } } } else { cmistake(u, ord, 99, MSG_MOVE); } } else { cmistake(u, ord, 63, MSG_MOVE); } } return route_end; } /** traveling without ships * walking, flying or riding units use this function */ static void travel(unit * u, region_list ** routep) { region * r = u->region; region_list * route_end; region_list * route_begin = NULL; follower * followers = NULL; if (routep) *routep = NULL; /* a few pre-checks that need not be done for each step: */ if (rterrain(r)!=T_OCEAN) { /* An Land kein NACH wenn in dieser Runde Schiff VERLASSEN! */ if ((u->ship || leftship(u)) && is_guarded(r, u, GUARD_LANDING)) { cmistake(u, u->thisorder, 70, MSG_MOVE); return; } if (u->ship && u->race->flags & RCF_SWIM) { cmistake(u, u->thisorder, 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, u->thisorder, 13, MSG_MOVE); return; } make_route(u, u->thisorder, routep); route_begin = *routep; /* und ab die post: */ route_end = travel_i(u, route_begin, NULL, u->thisorder, TRAVEL_NORMAL, &followers); /* followers */ while (followers!=NULL) { follower * fnext = followers->next; unit * uf = followers->uf; unit * ut = followers->ut; region_list * route_end = followers->route_end; free(followers); followers = fnext; if (uf->region==r) { order * follow_order; char str[32]; /* construct an order */ sprintf(str, "%s %s %s", locale_string(uf->faction->locale, keywords[K_FOLLOW]), locale_string(uf->faction->locale, parameters[P_UNIT]), itoa36(ut->no)); follow_order = parse_order(str, uf->faction->locale); route_end = travel_i(uf, route_begin, route_end, follow_order, TRAVEL_FOLLOWING, &followers); caught_target(uf->region, uf); free_order(follow_order); } } } static void move(unit * u, boolean move_on_land) { region_list * route = NULL; if (u->ship && fval(u, UFL_OWNER)) { sail(u, u->thisorder, move_on_land, &route); } else { travel(u, &route); } fset(u, UFL_LONGACTION); set_order(&u->thisorder, NULL); if (route!=NULL) free_regionlist(route); } typedef struct piracy_data { const struct faction * follow; direction_t dir; } piracy_data; static void piracy_init(struct attrib * a) { a->data.v = calloc(1, sizeof(piracy_data)); } static void piracy_done(struct attrib * a) { free(a->data.v); } static attrib_type at_piracy_direction = { "piracy_direction", piracy_init, piracy_done, DEFAULT_AGE, NO_WRITE, NO_READ }; static attrib * mk_piracy(const faction * f, direction_t target_dir) { attrib * a = a_new(&at_piracy_direction); piracy_data * data = a->data.v; data->follow = f; data->dir = target_dir; return a; } static void piracy_cmd(unit *u, struct order * ord) { region *r = u->region; ship *sh = u->ship, *sh2; direction_t dir, target_dir = NODIRECTION; int aff[MAXDIRECTIONS]; int saff = 0; int *il; const char *s; boolean all = true; attrib *a; if (!sh) { cmistake(u, ord, 144, MSG_MOVE); return; } if (!fval(u, UFL_OWNER)) { cmistake(u, ord, 46, MSG_MOVE); return; } /* Feststellen, ob schon ein anderer alliierter Pirat ein * Ziel gefunden hat. */ il = intlist_init(); init_tokens(ord); skip_token(); s = getstrtoken(); while(s && *s) { il = intlist_add(il, atoi(s)); all = false; s = getstrtoken(); } for (a = a_find(r->attribs, &at_piracy_direction); a; a=a->nexttype) { piracy_data * data = a->data.v; const faction *f = data->follow; if (alliedunit(u, f, HELP_FIGHT) && intlist_find(il, a->data.sa[1])) { target_dir = data->dir; break; } } /* Wenn nicht, sehen wir, ob wir ein Ziel finden. */ if (target_dir == NODIRECTION) { /* Einheit ist also Kapitän. Jetzt gucken, in wievielen * Nachbarregionen potentielle Opfer sind. */ for(dir = 0; dir < MAXDIRECTIONS; dir++) { region *rc = rconnect(r, dir); aff[dir] = 0; if(rc && (terrain[rterrain(rc)].flags & SWIM_INTO) && check_takeoff(sh, r, rc) == true) { for(sh2 = rc->ships; sh2; sh2 = sh2->next) { unit * cap = shipowner(sh2); if (cap && (intlist_find(il, cap->faction->no) || all)) { aff[dir]++; } } /* Und aufaddieren. */ saff += aff[dir]; } } if(saff != 0) { saff = rand()%saff; for(dir = 0; dir < MAXDIRECTIONS; dir++) { if(saff < aff[dir]) break; saff -= aff[dir]; } target_dir = dir; a = a_add(&r->attribs, mk_piracy(u->faction, target_dir)); } } free(il); /* Wenn kein Ziel gefunden, entsprechende Meldung generieren */ if (target_dir == NODIRECTION) { add_message(&u->faction->msgs, new_message(u->faction, "piratenovictim%h:ship%r:region", sh, r)); return; } /* Meldung generieren */ add_message(&u->faction->msgs, new_message(u->faction, "piratesawvictim%h:ship%r:region%d:dir", sh, r, target_dir)); /* Befehl konstruieren */ sprintf(buf, "%s %s", locale_string(u->faction->locale, keywords[K_MOVE]), locale_string(u->faction->locale, directions[target_dir])); set_order(&u->thisorder, parse_order(buf, u->faction->locale)); free_order(u->thisorder); /* parse_order & set_order have each increased the refcount */ /* Bewegung ausführen */ init_tokens(u->thisorder); skip_token(); move(u, true); } static void age_traveldir(region *r) { attrib *a = a_find(r->attribs, &at_traveldir); while(a) { a->data.ca[3]--; if(a->data.ca[3] <= 0) { attrib *an = a->nexttype; a_remove(&r->attribs, a); a = an; } else { a = a->nexttype; } } } static direction_t hunted_dir(attrib *at, int id) { attrib *a = a_find(at, &at_shiptrail); while (a!=NULL) { traveldir *t = (traveldir *)(a->data.v); if (t->no == id) return t->dir; a = a->nexttype; } return NODIRECTION; } static boolean can_move(const unit * u) { if (u->race->flags & RCF_CANNOTMOVE) return false; if (get_movement(&u->attribs, MV_CANNOTMOVE)) return false; return true; } static int hunt(unit *u) { region *rc = u->region; int moves, id; char command[256]; direction_t dir; if (fval(u, UFL_LONGACTION)) { return 0; } else if (u->ship==NULL) { cmistake(u, u->thisorder, 144, MSG_MOVE); return 0; } else if(!fval(u, UFL_OWNER)) { cmistake(u, u->thisorder, 146, MSG_MOVE); return 0; } else if(attacked(u)) { cmistake(u, u->thisorder, 52, MSG_MOVE); return 0; } else if (!can_move(u)) { cmistake(u, u->thisorder, 55, MSG_MOVE); return 0; } id = getshipid(); if (id <= 0) { cmistake(u, u->thisorder, 20, MSG_MOVE); return 0; } dir = hunted_dir(rc->attribs, id); if (dir == NODIRECTION) { ship * sh = findship(id); if (sh==NULL || sh->region!=rc) { cmistake(u, u->thisorder, 20, MSG_MOVE); } return 0; } sprintf(command, "%s %s", locale_string(u->faction->locale, keywords[K_MOVE]), locale_string(u->faction->locale, directions[dir])); moves = 1; rc = rconnect(rc, dir); while (moves < shipspeed(u->ship, u) && (dir = hunted_dir(rc->attribs, id)) != NODIRECTION) { strcat(command, " "); strcat(command, locale_string(u->faction->locale, directions[dir])); moves++; rc = rconnect(rc, dir); } /* In command steht jetzt das NACH-Kommando. */ /* NACH ignorieren und Parsing initialisieren. */ igetstrtoken(command); /* NACH ausführen */ move(u, false); fset(u, UFL_LONGACTION); /* Von Hand setzen, um Endlosschleife zu vermeiden, wenn Verfolgung nicht erfolgreich */ return 1; /* true -> Einheitenliste von vorne durchgehen */ } void destroy_damaged_ships(void) { region *r; ship *sh,*shn; for(r=regions;r;r=r->next) { for(sh=r->ships;sh;) { shn = sh->next; if (sh->damage>=sh->size * DAMAGE_SCALE) { destroy_ship(sh); } sh = shn; } } } #ifdef TODO /* Wenn Feature ausgearbeitet */ static boolean is_disorientated(unit *u) { static boolean init = false; static const curse_type * shipconf_ct, * regconf_ct; if (!init) { init = true; regconf_ct = ct_find("disorientationzone"); shipconf_ct = ct_find("shipdisorientation"); } if (u->ship && curse_active(get_curse(u->ship->attribs, shipconf_ct))) return true; if (curse_active(get_curse(u->region->attribs, regconf_ct))) return true; return false; } void regain_orientation(region * r) { ship *sh; curse *c; unit *u, *cap; for(sh = r->ships; sh; sh = sh->next) { c = get_curse(sh->attribs, C_DISORIENTATION, 0); if(!c) continue; /* Das Schiff bekommt seine Orientierung zurück, wenn es: * a) An Land treibt. * b) Glück hat. * c) In einer Region mit einem nicht verwirrten alliierten * Schiff steht. */ cap = shipowner(r, sh); if(r->terrain != T_OCEAN || rand() % 10 >= storms[month(0)]) { remove_curse(&sh->attribs, C_DISORIENTATION, 0); ADDMSG(&cap->faction->msgs, msg_message("shipnoconf", "ship", sh)); continue; } for(u=r->units;u;u=u->next) { if(u != cap && allied(cap, u->faction, HELP_GUARD) && is_disorientated(u) == false) { remove_curse(&sh->attribs, C_DISORIENTATION, 0); ADDMSG(&cap->faction->msgs, msg_message("shipnoconf", "ship", sh)); break; } } } } #endif /* 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_hunters(void) { region *r; for (r = regions; r; r = r->next) { unit ** up = &r->units, * u; for (u=r->units; u; u=u->next) freset(u, FL_DH); while (*up!=NULL) { unit * u = *up; if (!fval(u, FL_DH)) { order * ord; fset(u, FL_DH); for (ord=u->orders;ord;ord=ord->next) { if (get_keyword(ord) == K_FOLLOW) { param_t p; init_tokens(ord); skip_token(); 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_LONGACTION)) { cmistake(u, ord, 52, MSG_MOVE); break; } else if (!can_move(u)) { cmistake(u, ord, 55, MSG_MOVE); break; } if (!fval(u, UFL_LONGACTION) && !LongHunger(u) && hunt(u)) { 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_LONGACTION) && get_keyword(u->thisorder) == K_PIRACY) { piracy_cmd(u, u->thisorder); fset(u, UFL_LONGACTION); } 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 */ } a_removeall(&r->attribs, &at_piracy_direction); 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; boolean repeat = false; while (*up) { unit *u = *up; keyword_t kword = get_keyword(u->thisorder); switch (kword) { case K_ROUTE: case K_MOVE: if (attacked(u)) { cmistake(u, u->thisorder, 52, 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 && fval(u, UFL_OWNER)) { init_tokens(u->thisorder); skip_token(); move(u, false); } } else { if (u->ship==NULL || !fval(u, UFL_OWNER)) { init_tokens(u->thisorder); skip_token(); move(u, false); } } } break; } 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 beschädigten, unterbemannten, überladenen Schiffen */ drifting_ships(r); } r = r->next; } } move_hunters(); move_pirates(); } /** Overrides long orders with a FOLLOW order if the target is moving. * FOLLOW SHIP is a long order, and doesn't need to be treated in here. */ void follow_unit(void) { region * r; for (r=regions;r;r=r->next) { unit * u; for (u=r->units;u;u=u->next) { attrib * a; order * ord; if (fval(u, UFL_LONGACTION) || LongHunger(u)) continue; a = a_find(u->attribs, &at_follow); for (ord=u->orders;ord;ord=ord->next) { const struct locale * lang = u->faction->locale; if (get_keyword(ord) == K_FOLLOW) { init_tokens(ord); skip_token(); if (getparam(lang) == P_UNIT) { int id = read_unitid(u->faction, r); 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)) { unit * u2 = a->data.v; if (!u2 || u2->region!=r || !cansee(u->faction, r, u2, 0)) continue; for (ord=u2->orders;ord;ord=ord->next) { switch (get_keyword(ord)) { case K_MOVE: case K_ROUTE: case K_DRIVE: case K_FOLLOW: case K_PIRACY: fset(u, UFL_FOLLOWING); fset(u2, UFL_FOLLOWED); set_order(&u->thisorder, NULL); break; default: continue; } break; } } } } }