Merge pull request #725 from ennorehling/develop

Bugfixes for 3.14 release
This commit is contained in:
Enno Rehling 2017-09-01 22:23:48 +02:00 committed by GitHub
commit 5f518ae4f0
28 changed files with 175 additions and 178 deletions

View File

@ -27,7 +27,7 @@
"rules.reserve.twophase": true, "rules.reserve.twophase": true,
"rules.give.max_men": -1, "rules.give.max_men": -1,
"rules.check_overload": false, "rules.check_overload": false,
"rules.limit.faction": 3000, "rules.limit.faction": 2500,
"rules.maxskills.magic": 5, "rules.maxskills.magic": 5,
"rules.guard.base_stop_prob": 0.30, "rules.guard.base_stop_prob": 0.30,
"rules.guard.skill_stop_prob": 0.05, "rules.guard.skill_stop_prob": 0.05,

View File

@ -88,7 +88,7 @@
"rules.economy.herbrot": 0, "rules.economy.herbrot": 0,
"rules.region_owner_pay_building": "market harbour lighthouse", "rules.region_owner_pay_building": "market harbour lighthouse",
"rules.dwarf_castles": true, "rules.dwarf_castles": true,
"rules.limit.faction": 500, "rules.limit.faction": 250,
"rules.grow.formula": 1, "rules.grow.formula": 1,
"rules.tactics.formula": 1, "rules.tactics.formula": 1,
"rules.help.mask": "fight guard money give", "rules.help.mask": "fight guard money give",

View File

@ -4990,12 +4990,12 @@
<string name="sacrifice_strength"> <string name="sacrifice_strength">
<text locale="de">Mit Hilfe dieses Zaubers kann der Magier einen Teil <text locale="de">Mit Hilfe dieses Zaubers kann der Magier einen Teil
seiner magischen Kraft permanent auf einen anderen Magier übertragen. seiner magischen Kraft permanent auf einen anderen Magier übertragen.
Auf einen Tybied-Magier kann er die Hälfte der eingesetzten Kraft Auf einen Magier des selben Magiegebietes kann er die Hälfte der
übertragen, auf einen Magier eines anderen Gebietes ein Drittel.</text> eingesetzten Kraft übertragen, auf andere Magier ein Drittel.</text>
<text locale="en">This spell allows the magician to transfer part of <text locale="en">This spell allows the magician to transfer part of
his magical powers to another magician. Tybied magicians will receive his magical powers to another magician. Magicians of the seam school
half the power invested, magicians of another school will receive one will receive half the power invested, magicians of other schoolsreceive
third.</text> receive one third.</text>
</string> </string>
<string name="eternal_walls"> <string name="eternal_walls">
<text locale="de">Mit dieser Formel bindet der Magier auf ewig die <text locale="de">Mit dieser Formel bindet der Magier auf ewig die

View File

@ -1,4 +1,4 @@
if not config.autoseed then return nil end if not config.autoseed or config.autoseed==0 then return nil end
local autoseed = {} local autoseed = {}
-- minimum required resources in the 7-hex neighborhood: -- minimum required resources in the 7-hex neighborhood:

View File

@ -1,6 +1,6 @@
-- Muschelplateau -- Muschelplateau
if not config.embassy then return nil end if not config.embassy or config.embassy==0 then return nil end
local embassy = {} local embassy = {}
local home = nil local home = nil

View File

@ -1,5 +1,5 @@
-- DEPRECATED -- DEPRECATED
if not config.eternath then return nil end if not config.eternath or config.eternath==0 then return nil end
-- implements parts of a quest in E2 -- implements parts of a quest in E2
-- this module is deprecated, because it puts functions in the global environment for at_building_action -- this module is deprecated, because it puts functions in the global environment for at_building_action

View File

@ -0,0 +1,21 @@
local modules = {}
function add_module(pkg)
table.insert(modules, pkg)
end
local pkg = {}
function pkg.init()
for k, v in ipairs(modules) do
if v.init then v.init() end
end
end
function pkg.update()
for k, v in ipairs(modules) do
if v.update then v.update() end
end
end
return pkg

View File

@ -1,4 +1,4 @@
if not config.ponnuki then return nil end if not config.ponnuki or config.ponnuki==0 then return nil end
local ponnuki = {} local ponnuki = {}
local directions = { "NW", "NO", "O", "SO", "SW", "W" } local directions = { "NW", "NO", "O", "SO", "SW", "W" }

View File

@ -1,5 +1,5 @@
-- DEPRECATED -- DEPRECATED
if not config.wedding then return nil end if not config.wedding or config.wedding==0 then return nil end
-- this script contains the action functions for the two portals -- this script contains the action functions for the two portals
-- used on the jadee/wildente wedding island. the two _action functions -- used on the jadee/wildente wedding island. the two _action functions

View File

@ -1,4 +1,4 @@
if not config.xmas then return nil end if not config.xmas or config.xmas==0 then return nil end
local gifts = { local gifts = {
e2 = { e2 = {

View File

@ -11,6 +11,23 @@ function setup()
eressea.settings.set("rules.encounters", "0") eressea.settings.set("rules.encounters", "0")
end end
function test_bug_2361_forget_magic()
-- https://bugs.eressea.de/view.php?id=2361
-- familiars cannot forget magic
local r = region.create(0, 0, "plain")
local f = faction.create("human")
local u = unit.create(f, r, 1)
u:clear_orders()
u:add_order("VERGESSE Magie")
u:set_skill('magic', 5)
u.race = 'unicorn'
process_orders()
assert_equal(5, u:get_skill('magic'))
u.race = 'human'
process_orders()
assert_equal(0, u:get_skill('magic'))
end
function test_mine_bonus() function test_mine_bonus()
local r = region.create(0, 0, "mountain") local r = region.create(0, 0, "mountain")
r:set_resource("iron", 100) r:set_resource("iron", 100)

View File

@ -254,7 +254,7 @@ cr_output_curses(struct stream *out, const faction * viewer, const void *obj, ob
} }
while (a) { while (a) {
if (fval(a->type, ATF_CURSE)) { if (a->type == &at_curse) {
curse *c = (curse *)a->data.v; curse *c = (curse *)a->data.v;
message *msg; message *msg;

View File

@ -661,7 +661,12 @@ static int forget_cmd(unit * u, order * ord)
init_order(ord); init_order(ord);
s = gettoken(token, sizeof(token)); s = gettoken(token, sizeof(token));
if ((sk = get_skill(s, u->faction->locale)) != NOSKILL) { sk = get_skill(s, u->faction->locale);
if (sk != NOSKILL) {
if (sk == SK_MAGIC && (u_race(u)->flags & RCF_FAMILIAR)) {
/* some races cannot forget their innate magical abilities */
return 0;
}
ADDMSG(&u->faction->msgs, msg_message("forget", "unit skill", u, sk)); ADDMSG(&u->faction->msgs, msg_message("forget", "unit skill", u, sk));
set_level(u, sk, 0); set_level(u, sk, 0);
} }

View File

@ -2,6 +2,17 @@
#include "settings.h" #include "settings.h"
#include "eressea.h" #include "eressea.h"
#include "calendar.h"
#include "chaos.h"
#include "items.h"
#include "creport.h"
#include "report.h"
#include "names.h"
#include "reports.h"
#include "spells.h"
#include "vortex.h"
#include "wormhole.h"
#include <kernel/config.h> #include <kernel/config.h>
#include <util/log.h> #include <util/log.h>
@ -25,16 +36,6 @@
#include <util/message.h> #include <util/message.h>
#include <races/races.h> #include <races/races.h>
#include "calendar.h"
#include "chaos.h"
#include "items.h"
#include "creport.h"
#include "report.h"
#include "names.h"
#include "reports.h"
#include "spells.h"
#include "wormhole.h"
void game_done(void) void game_done(void)
{ {
#ifdef CLEANUP_CODE #ifdef CLEANUP_CODE
@ -53,6 +54,7 @@ void game_done(void)
calendar_cleanup(); calendar_cleanup();
free_functions(); free_functions();
free_config(); free_config();
free_special_directions();
free_locales(); free_locales();
kernel_done(); kernel_done();
} }

View File

@ -145,7 +145,7 @@ struct order *ord)
while (*ap && force > 0) { while (*ap && force > 0) {
curse *c; curse *c;
attrib *a = *ap; attrib *a = *ap;
if (!(a->type->flags & ATF_CURSE)) { if (a->type != &at_curse) {
do { do {
ap = &(*ap)->next; ap = &(*ap)->next;
} while (*ap && a->type == (*ap)->type); } while (*ap && a->type == (*ap)->type);

View File

@ -273,8 +273,7 @@ attrib_type at_curse = {
curse_age, curse_age,
curse_write, curse_write,
curse_read, curse_read,
NULL, NULL
ATF_CURSE
}; };
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -363,7 +362,7 @@ void ct_checknames(void) {
static bool cmp_curse(const attrib * a, const void *data) static bool cmp_curse(const attrib * a, const void *data)
{ {
const curse *c = (const curse *)data; const curse *c = (const curse *)data;
if (a->type->flags & ATF_CURSE) { if (a->type == &at_curse) {
if (!data || c == (curse *)a->data.v) if (!data || c == (curse *)a->data.v)
return true; return true;
} }
@ -375,7 +374,7 @@ curse *get_curse(attrib * ap, const curse_type * ctype)
attrib *a = ap; attrib *a = ap;
if (!ctype) return NULL; if (!ctype) return NULL;
while (a) { while (a) {
if (a->type->flags & ATF_CURSE) { if (a->type == &at_curse) {
const attrib_type *at = a->type; const attrib_type *at = a->type;
while (a && a->type == at) { while (a && a->type == at) {
curse *c = (curse *)a->data.v; curse *c = (curse *)a->data.v;
@ -710,7 +709,7 @@ bool is_cursed_with(const attrib * ap, const curse * c)
const attrib *a = ap; const attrib *a = ap;
while (a) { while (a) {
if ((a->type->flags & ATF_CURSE) && (c == (const curse *)a->data.v)) { if ((a->type == &at_curse) && (c == (const curse *)a->data.v)) {
return true; return true;
} }
a = a->next; a = a->next;

View File

@ -311,9 +311,6 @@ extern "C" {
#define get_curseeffect(a, ctype) \ #define get_curseeffect(a, ctype) \
curse_geteffect(get_curse(a, ctype)) curse_geteffect(get_curse(a, ctype))
/* eressea-defined attribute-type flags */
#define ATF_CURSE ATF_USER_DEFINED
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@ -745,11 +745,17 @@ int rtrees(const region * r, int ageclass)
int rsettrees(const region * r, int ageclass, int value) int rsettrees(const region * r, int ageclass, int value)
{ {
if (!r->land) if (!r->land) {
assert(value == 0); assert(value == 0);
}
else { else {
assert(value >= 0); assert(value >= 0);
return r->land->trees[ageclass] = value; if (value <= MAXTREES) {
return r->land->trees[ageclass] = value;
}
else {
r->land->trees[ageclass] = MAXTREES;
}
} }
return 0; return 0;
} }

View File

@ -26,6 +26,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#define MAXLUXURIES 16 /* there must be no more than MAXLUXURIES kinds of luxury goods in any game */ #define MAXLUXURIES 16 /* there must be no more than MAXLUXURIES kinds of luxury goods in any game */
#define MAXREGIONS 524287 /* must be prime for hashing. 262139 was a little small */ #define MAXREGIONS 524287 /* must be prime for hashing. 262139 was a little small */
#define MAXTREES 100 * 1000 * 1000 /* bug 2360: some players are crazy */
/* FAST_CONNECT: regions are directly connected to neighbours, saves doing /* FAST_CONNECT: regions are directly connected to neighbours, saves doing
a hash-access each time a neighbour is needed, 6 extra pointers per hex */ a hash-access each time a neighbour is needed, 6 extra pointers per hex */

View File

@ -78,10 +78,29 @@ static void test_region_getset_resource(CuTest *tc) {
test_cleanup(); test_cleanup();
} }
static void test_trees(CuTest *tc) {
region *r;
test_setup();
r = test_create_region(0, 0, NULL);
rsettrees(r, 0, 1000);
rsettrees(r, 1, 2000);
rsettrees(r, 2, 3000);
CuAssertIntEquals(tc, 1000, rtrees(r, 0));
CuAssertIntEquals(tc, 2000, rtrees(r, 1));
CuAssertIntEquals(tc, 3000, rtrees(r, 2));
rsettrees(r, 0, MAXTREES);
CuAssertIntEquals(tc, MAXTREES, rtrees(r, 0));
rsettrees(r, 0, MAXTREES+100);
CuAssertIntEquals(tc, MAXTREES, rtrees(r, 0));
test_cleanup();
}
CuSuite *get_region_suite(void) CuSuite *get_region_suite(void)
{ {
CuSuite *suite = CuSuiteNew(); CuSuite *suite = CuSuiteNew();
SUITE_ADD_TEST(suite, test_terraform); SUITE_ADD_TEST(suite, test_terraform);
SUITE_ADD_TEST(suite, test_trees);
SUITE_ADD_TEST(suite, test_region_getset_resource); SUITE_ADD_TEST(suite, test_region_getset_resource);
SUITE_ADD_TEST(suite, test_region_get_owner); SUITE_ADD_TEST(suite, test_region_get_owner);
return suite; return suite;

View File

@ -329,8 +329,9 @@ sc_mage *get_mage(const unit * u)
{ {
if (has_skill(u, SK_MAGIC)) { if (has_skill(u, SK_MAGIC)) {
attrib *a = a_find(u->attribs, &at_mage); attrib *a = a_find(u->attribs, &at_mage);
if (a) if (a) {
return a->data.v; return (sc_mage *)a->data.v;
}
} }
return (sc_mage *)NULL; return (sc_mage *)NULL;
} }

View File

@ -568,7 +568,7 @@ nr_curses_i(struct stream *out, int indent, const faction *viewer, objtype_t typ
char buf[4096]; char buf[4096];
message *msg = 0; message *msg = 0;
if (fval(a->type, ATF_CURSE)) { if (a->type == &at_curse) {
curse *c = (curse *)a->data.v; curse *c = (curse *)a->data.v;
self = curse_cansee(c, viewer, typ, obj, self); self = curse_cansee(c, viewer, typ, obj, self);

View File

@ -144,7 +144,7 @@ static void magicanalyse_region(region * r, unit * mage, double force)
double probability; double probability;
int mon; int mon;
if (!fval(a->type, ATF_CURSE)) if (a->type != &at_curse)
continue; continue;
/* ist der curse schwaecher als der Analysezauber, so ergibt sich /* ist der curse schwaecher als der Analysezauber, so ergibt sich
@ -184,7 +184,7 @@ static void magicanalyse_unit(unit * u, unit * mage, double force)
curse *c; curse *c;
double probability; double probability;
int mon; int mon;
if (!fval(a->type, ATF_CURSE)) if (a->type != &at_curse)
continue; continue;
c = (curse *)a->data.v; c = (curse *)a->data.v;
@ -225,7 +225,7 @@ static void magicanalyse_building(building * b, unit * mage, double force)
double probability; double probability;
int mon; int mon;
if (!fval(a->type, ATF_CURSE)) if (a->type != &at_curse)
continue; continue;
c = (curse *)a->data.v; c = (curse *)a->data.v;
@ -266,7 +266,7 @@ static void magicanalyse_ship(ship * sh, unit * mage, double force)
curse *c; curse *c;
double probability; double probability;
int mon; int mon;
if (!fval(a->type, ATF_CURSE)) if (a->type != &at_curse)
continue; continue;
c = (curse *)a->data.v; c = (curse *)a->data.v;
@ -308,7 +308,7 @@ static int break_curse(attrib ** alist, int cast_level, double force, curse * c)
while (*ap && force > 0) { while (*ap && force > 0) {
curse *c1; curse *c1;
attrib *a = *ap; attrib *a = *ap;
if (!fval(a->type, ATF_CURSE)) { if (a->type != &at_curse) {
do { do {
ap = &(*ap)->next; ap = &(*ap)->next;
} while (*ap && a->type == (*ap)->type); } while (*ap && a->type == (*ap)->type);
@ -2970,7 +2970,7 @@ static int sp_deathcloud(castorder * co)
unit *u; unit *u;
while (a) { while (a) {
if ((a->type->flags & ATF_CURSE)) { if (a->type == &at_curse) {
curse *c = a->data.v; curse *c = a->data.v;
if (c->type == &ct_deathcloud) { if (c->type == &ct_deathcloud) {
report_failure(mage, co->order); report_failure(mage, co->order);
@ -3116,46 +3116,6 @@ static int sp_summonshadowlords(castorder * co)
return cast_level; return cast_level;
} }
static bool chaosgate_valid(const connection * b)
{
const attrib *a = a_find(b->from->attribs, &at_direction);
if (!a)
a = a_find(b->to->attribs, &at_direction);
if (!a)
return false;
return true;
}
static struct region *chaosgate_move(const connection * b, struct unit *u,
struct region *from, struct region *to, bool routing)
{
UNUSED_ARG(from);
UNUSED_ARG(b);
if (!routing) {
int maxhp = u->hp / 4;
if (maxhp < u->number)
maxhp = u->number;
u->hp = maxhp;
}
return to;
}
border_type bt_chaosgate = {
"chaosgate", VAR_NONE,
b_transparent, /* transparent */
NULL, /* init */
NULL, /* destroy */
NULL, /* read */
NULL, /* write */
b_blocknone, /* block */
NULL, /* name */
b_rinvisible, /* rvisible */
b_finvisible, /* fvisible */
b_uinvisible, /* uvisible */
chaosgate_valid,
chaosgate_move
};
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
/* Name: Chaossog /* Name: Chaossog
* Stufe: 14 * Stufe: 14

View File

@ -1,6 +1,7 @@
#include <platform.h> #include <platform.h>
#include "borders.h" #include "borders.h"
#include "vortex.h"
#include <kernel/connection.h> #include <kernel/connection.h>
#include <kernel/curse.h> #include <kernel/curse.h>
@ -28,46 +29,15 @@ typedef struct wallcurse {
connection *wall; connection *wall;
} wallcurse; } wallcurse;
static void cw_init(attrib * a) static int cw_read_depr(attrib * a, void *target, gamedata *data)
{
curse *c;
curse_init(a);
c = (curse *)a->data.v;
c->data.v = calloc(sizeof(wallcurse), 1);
}
static void cw_write(const attrib * a, const void *target, storage * store)
{
connection *b = ((wallcurse *)((curse *)a->data.v)->data.v)->wall;
curse_write(a, target, store);
WRITE_INT(store, b->id);
}
typedef struct bresolve {
int id;
curse *self;
} bresolve;
static int resolve_buddy(variant data, void *addr);
static int cw_read(attrib * a, void *target, gamedata *data)
{ {
storage *store = data->store; storage *store = data->store;
bresolve *br = calloc(sizeof(bresolve), 1);
curse *c = (curse *)a->data.v;
wallcurse *wc = (wallcurse *)c->data.v;
variant var;
curse_init(a);
curse_read(a, store, target); curse_read(a, store, target);
br->self = c; curse_done(a);
READ_INT(store, &br->id); READ_INT(store, NULL);
return AT_READ_DEPR;
var.i = br->id;
ur_add(var, &wc->wall, resolve_borderid);
var.v = br;
ur_add(var, &wc->buddy, resolve_buddy);
return AT_READ_OK;
} }
/* ------------------------------------------------------------- */ /* ------------------------------------------------------------- */
@ -105,61 +75,6 @@ const curse_type ct_firewall = {
wall_vigour /* change_vigour */ wall_vigour /* change_vigour */
}; };
static attrib_type at_cursewall = {
"cursewall",
cw_init,
curse_done,
curse_age,
cw_write,
cw_read,
NULL,
ATF_CURSE
};
static int resolve_buddy(variant data, void *addr)
{
curse *result = NULL;
bresolve *br = (bresolve *)data.v;
connection *b;
assert(br->id > 0);
b = find_border(br->id);
if (b && b->from && b->to) {
attrib *a = a_find(b->from->attribs, &at_cursewall);
while (a && a->data.v != br->self) {
curse *c = (curse *)a->data.v;
wallcurse *wc = (wallcurse *)c->data.v;
if (wc->wall->id == br->id)
break;
a = a->next;
}
if (!a || a->type != &at_cursewall) {
a = a_find(b->to->attribs, &at_cursewall);
while (a && a->type == &at_cursewall && a->data.v != br->self) {
curse *c = (curse *)a->data.v;
wallcurse *wc = (wallcurse *)c->data.v;
if (wc->wall->id == br->id)
break;
a = a->next;
}
}
if (a && a->type == &at_cursewall) {
curse *c = (curse *)a->data.v;
free(br);
result = c;
}
}
else {
/* fail, object does not exist (but if you're still loading then
* you may want to try again later) */
*(curse **)addr = NULL;
return -1;
}
*(curse **)addr = result;
return 0;
}
static void wall_init(connection * b) static void wall_init(connection * b)
{ {
wall_data *fd = (wall_data *)calloc(sizeof(wall_data), 1); wall_data *fd = (wall_data *)calloc(sizeof(wall_data), 1);
@ -284,10 +199,50 @@ border_type bt_wisps = { /* only here for reading old data */
0 0
}; };
static bool chaosgate_valid(const connection * b)
{
const attrib *a = a_find(b->from->attribs, &at_direction);
if (!a)
a = a_find(b->to->attribs, &at_direction);
if (!a)
return false;
return true;
}
static struct region *chaosgate_move(const connection * b, struct unit *u,
struct region *from, struct region *to, bool routing)
{
UNUSED_ARG(from);
UNUSED_ARG(b);
if (!routing) {
int maxhp = u->hp / 4;
if (maxhp < u->number)
maxhp = u->number;
u->hp = maxhp;
}
return to;
}
border_type bt_chaosgate = {
"chaosgate", VAR_NONE,
b_transparent, /* transparent */
NULL, /* init */
NULL, /* destroy */
NULL, /* read */
NULL, /* write */
b_blocknone, /* block */
NULL, /* name */
b_rinvisible, /* rvisible */
b_finvisible, /* fvisible */
b_uinvisible, /* uvisible */
chaosgate_valid,
chaosgate_move
};
void register_borders(void) void register_borders(void)
{ {
border_convert_cb = &convert_firewall_timeouts; border_convert_cb = &convert_firewall_timeouts;
at_register(&at_cursewall); at_deprecate("cursewall", cw_read_depr);
register_bordertype(&bt_firewall); register_bordertype(&bt_firewall);
register_bordertype(&bt_wisps); register_bordertype(&bt_wisps);

View File

@ -15,6 +15,7 @@ extern "C" {
**/ **/
extern struct border_type bt_chaosgate; extern struct border_type bt_chaosgate;
extern struct border_type bt_firewall; extern struct border_type bt_firewall;
extern const struct curse_type ct_firewall;
typedef struct wall_data { typedef struct wall_data {
struct unit *mage; struct unit *mage;
@ -23,7 +24,6 @@ extern "C" {
int countdown; int countdown;
} wall_data; } wall_data;
extern const struct curse_type ct_firewall;
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -4,6 +4,7 @@
#include "prefix.h" #include "prefix.h"
#include "reports.h" #include "reports.h"
#include "calendar.h" #include "calendar.h"
#include "vortex.h"
#include <kernel/config.h> #include <kernel/config.h>
#include <kernel/alliance.h> #include <kernel/alliance.h>
@ -209,6 +210,7 @@ static void test_reset(void) {
default_locale = 0; default_locale = 0;
calendar_cleanup(); calendar_cleanup();
close_orders(); close_orders();
free_special_directions();
free_locales(); free_locales();
free_spells(); free_spells();
free_buildingtypes(); free_buildingtypes();

View File

@ -26,6 +26,16 @@ typedef struct dir_lookup {
static dir_lookup *dir_name_lookup; static dir_lookup *dir_name_lookup;
void free_special_directions(void)
{
while (dir_name_lookup) {
dir_lookup *dl = dir_name_lookup;
dir_name_lookup = dl->next;
free(dl->name);
free(dl);
}
}
void register_special_direction(struct locale *lang, const char *name) void register_special_direction(struct locale *lang, const char *name)
{ {
const char *token = locale_string(lang, name, false); const char *token = locale_string(lang, name, false);

View File

@ -26,6 +26,8 @@ extern "C" {
struct region *find_special_direction(const struct region *r, struct region *find_special_direction(const struct region *r,
const char *token); const char *token);
void register_special_direction(struct locale *lang, const char *name); void register_special_direction(struct locale *lang, const char *name);
void free_special_directions(void);
struct spec_direction *special_direction(const struct region * from, struct spec_direction *special_direction(const struct region * from,
const struct region * to); const struct region * to);
struct attrib *create_special_direction(struct region *r, struct region *rt, struct attrib *create_special_direction(struct region *r, struct region *rt,