diff --git a/conf/e2/config.json b/conf/e2/config.json
index b8ca2f178..0c85b316b 100644
--- a/conf/e2/config.json
+++ b/conf/e2/config.json
@@ -27,7 +27,7 @@
"rules.reserve.twophase": true,
"rules.give.max_men": -1,
"rules.check_overload": false,
- "rules.limit.faction": 3000,
+ "rules.limit.faction": 2500,
"rules.maxskills.magic": 5,
"rules.guard.base_stop_prob": 0.30,
"rules.guard.skill_stop_prob": 0.05,
diff --git a/conf/e3/config.json b/conf/e3/config.json
index 3305ce221..b70d415a7 100644
--- a/conf/e3/config.json
+++ b/conf/e3/config.json
@@ -88,7 +88,7 @@
"rules.economy.herbrot": 0,
"rules.region_owner_pay_building": "market harbour lighthouse",
"rules.dwarf_castles": true,
- "rules.limit.faction": 500,
+ "rules.limit.faction": 250,
"rules.grow.formula": 1,
"rules.tactics.formula": 1,
"rules.help.mask": "fight guard money give",
diff --git a/res/core/de/strings.xml b/res/core/de/strings.xml
index c26bfe03b..ea4acf758 100644
--- a/res/core/de/strings.xml
+++ b/res/core/de/strings.xml
@@ -4990,12 +4990,12 @@
Mit Hilfe dieses Zaubers kann der Magier einen Teil
seiner magischen Kraft permanent auf einen anderen Magier übertragen.
- Auf einen Tybied-Magier kann er die Hälfte der eingesetzten Kraft
- übertragen, auf einen Magier eines anderen Gebietes ein Drittel.
+ Auf einen Magier des selben Magiegebietes kann er die Hälfte der
+ eingesetzten Kraft übertragen, auf andere Magier ein Drittel.
This spell allows the magician to transfer part of
- his magical powers to another magician. Tybied magicians will receive
- half the power invested, magicians of another school will receive one
- third.
+ his magical powers to another magician. Magicians of the seam school
+ will receive half the power invested, magicians of other schoolsreceive
+ receive one third.
Mit dieser Formel bindet der Magier auf ewig die
diff --git a/scripts/eressea/autoseed.lua b/scripts/eressea/autoseed.lua
index 2896708f8..10a33627f 100644
--- a/scripts/eressea/autoseed.lua
+++ b/scripts/eressea/autoseed.lua
@@ -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 = {}
-- minimum required resources in the 7-hex neighborhood:
diff --git a/scripts/eressea/embassy.lua b/scripts/eressea/embassy.lua
index 93ff66d7c..00b50b482 100644
--- a/scripts/eressea/embassy.lua
+++ b/scripts/eressea/embassy.lua
@@ -1,6 +1,6 @@
-- Muschelplateau
-if not config.embassy then return nil end
+if not config.embassy or config.embassy==0 then return nil end
local embassy = {}
local home = nil
diff --git a/scripts/eressea/eternath.lua b/scripts/eressea/eternath.lua
index 063577b74..208af0c8b 100644
--- a/scripts/eressea/eternath.lua
+++ b/scripts/eressea/eternath.lua
@@ -1,5 +1,5 @@
-- 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
-- this module is deprecated, because it puts functions in the global environment for at_building_action
diff --git a/scripts/eressea/modules.lua b/scripts/eressea/modules.lua
new file mode 100644
index 000000000..6a93362cb
--- /dev/null
+++ b/scripts/eressea/modules.lua
@@ -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
diff --git a/scripts/eressea/ponnuki.lua b/scripts/eressea/ponnuki.lua
index b5ba58fbe..3ead8c57c 100644
--- a/scripts/eressea/ponnuki.lua
+++ b/scripts/eressea/ponnuki.lua
@@ -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 directions = { "NW", "NO", "O", "SO", "SW", "W" }
diff --git a/scripts/eressea/wedding.lua b/scripts/eressea/wedding.lua
index 2c06d9891..71664eec9 100644
--- a/scripts/eressea/wedding.lua
+++ b/scripts/eressea/wedding.lua
@@ -1,5 +1,5 @@
-- 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
-- used on the jadee/wildente wedding island. the two _action functions
diff --git a/scripts/eressea/xmas.lua b/scripts/eressea/xmas.lua
index 0316e6265..b728c9976 100644
--- a/scripts/eressea/xmas.lua
+++ b/scripts/eressea/xmas.lua
@@ -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 = {
e2 = {
diff --git a/scripts/tests/economy.lua b/scripts/tests/economy.lua
index a1bfa3bb6..9289efa2b 100644
--- a/scripts/tests/economy.lua
+++ b/scripts/tests/economy.lua
@@ -11,6 +11,23 @@ function setup()
eressea.settings.set("rules.encounters", "0")
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()
local r = region.create(0, 0, "mountain")
r:set_resource("iron", 100)
diff --git a/src/creport.c b/src/creport.c
index 57ba12ee0..14f2a6c56 100644
--- a/src/creport.c
+++ b/src/creport.c
@@ -254,7 +254,7 @@ cr_output_curses(struct stream *out, const faction * viewer, const void *obj, ob
}
while (a) {
- if (fval(a->type, ATF_CURSE)) {
+ if (a->type == &at_curse) {
curse *c = (curse *)a->data.v;
message *msg;
diff --git a/src/economy.c b/src/economy.c
index 4122fa6a1..546f7876e 100644
--- a/src/economy.c
+++ b/src/economy.c
@@ -661,7 +661,12 @@ static int forget_cmd(unit * u, order * ord)
init_order(ord);
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));
set_level(u, sk, 0);
}
diff --git a/src/eressea.c b/src/eressea.c
index a9dddd130..2174450eb 100755
--- a/src/eressea.c
+++ b/src/eressea.c
@@ -2,6 +2,17 @@
#include "settings.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
#include
@@ -25,16 +36,6 @@
#include
#include
-#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)
{
#ifdef CLEANUP_CODE
@@ -53,6 +54,7 @@ void game_done(void)
calendar_cleanup();
free_functions();
free_config();
+ free_special_directions();
free_locales();
kernel_done();
}
diff --git a/src/items.c b/src/items.c
index dd4abe1ac..8190693b0 100644
--- a/src/items.c
+++ b/src/items.c
@@ -145,7 +145,7 @@ struct order *ord)
while (*ap && force > 0) {
curse *c;
attrib *a = *ap;
- if (!(a->type->flags & ATF_CURSE)) {
+ if (a->type != &at_curse) {
do {
ap = &(*ap)->next;
} while (*ap && a->type == (*ap)->type);
diff --git a/src/kernel/curse.c b/src/kernel/curse.c
index b0367484d..9702d7649 100644
--- a/src/kernel/curse.c
+++ b/src/kernel/curse.c
@@ -273,8 +273,7 @@ attrib_type at_curse = {
curse_age,
curse_write,
curse_read,
- NULL,
- ATF_CURSE
+ NULL
};
/* ------------------------------------------------------------- */
@@ -363,7 +362,7 @@ void ct_checknames(void) {
static bool cmp_curse(const attrib * a, const void *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)
return true;
}
@@ -375,7 +374,7 @@ curse *get_curse(attrib * ap, const curse_type * ctype)
attrib *a = ap;
if (!ctype) return NULL;
while (a) {
- if (a->type->flags & ATF_CURSE) {
+ if (a->type == &at_curse) {
const attrib_type *at = a->type;
while (a && a->type == at) {
curse *c = (curse *)a->data.v;
@@ -710,7 +709,7 @@ bool is_cursed_with(const attrib * ap, const curse * c)
const attrib *a = ap;
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;
}
a = a->next;
diff --git a/src/kernel/curse.h b/src/kernel/curse.h
index 9b6113d96..054387d59 100644
--- a/src/kernel/curse.h
+++ b/src/kernel/curse.h
@@ -311,9 +311,6 @@ extern "C" {
#define get_curseeffect(a, ctype) \
curse_geteffect(get_curse(a, ctype))
- /* eressea-defined attribute-type flags */
-#define ATF_CURSE ATF_USER_DEFINED
-
#ifdef __cplusplus
}
#endif
diff --git a/src/kernel/region.c b/src/kernel/region.c
index 7e6104406..e8da83c50 100644
--- a/src/kernel/region.c
+++ b/src/kernel/region.c
@@ -745,11 +745,17 @@ int rtrees(const region * r, int ageclass)
int rsettrees(const region * r, int ageclass, int value)
{
- if (!r->land)
+ if (!r->land) {
assert(value == 0);
+ }
else {
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;
}
diff --git a/src/kernel/region.h b/src/kernel/region.h
index 5117567a6..c81411468 100644
--- a/src/kernel/region.h
+++ b/src/kernel/region.h
@@ -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 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
a hash-access each time a neighbour is needed, 6 extra pointers per hex */
diff --git a/src/kernel/region.test.c b/src/kernel/region.test.c
index 879faad50..8f16842c4 100644
--- a/src/kernel/region.test.c
+++ b/src/kernel/region.test.c
@@ -78,10 +78,29 @@ static void test_region_getset_resource(CuTest *tc) {
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 *suite = CuSuiteNew();
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_get_owner);
return suite;
diff --git a/src/magic.c b/src/magic.c
index 16ee37e60..7d33a28e1 100644
--- a/src/magic.c
+++ b/src/magic.c
@@ -329,8 +329,9 @@ sc_mage *get_mage(const unit * u)
{
if (has_skill(u, SK_MAGIC)) {
attrib *a = a_find(u->attribs, &at_mage);
- if (a)
- return a->data.v;
+ if (a) {
+ return (sc_mage *)a->data.v;
+ }
}
return (sc_mage *)NULL;
}
diff --git a/src/report.c b/src/report.c
index 2cafe3157..18d48b86b 100644
--- a/src/report.c
+++ b/src/report.c
@@ -568,7 +568,7 @@ nr_curses_i(struct stream *out, int indent, const faction *viewer, objtype_t typ
char buf[4096];
message *msg = 0;
- if (fval(a->type, ATF_CURSE)) {
+ if (a->type == &at_curse) {
curse *c = (curse *)a->data.v;
self = curse_cansee(c, viewer, typ, obj, self);
diff --git a/src/spells.c b/src/spells.c
index 31d75c6b3..c6860bebe 100644
--- a/src/spells.c
+++ b/src/spells.c
@@ -144,7 +144,7 @@ static void magicanalyse_region(region * r, unit * mage, double force)
double probability;
int mon;
- if (!fval(a->type, ATF_CURSE))
+ if (a->type != &at_curse)
continue;
/* 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;
double probability;
int mon;
- if (!fval(a->type, ATF_CURSE))
+ if (a->type != &at_curse)
continue;
c = (curse *)a->data.v;
@@ -225,7 +225,7 @@ static void magicanalyse_building(building * b, unit * mage, double force)
double probability;
int mon;
- if (!fval(a->type, ATF_CURSE))
+ if (a->type != &at_curse)
continue;
c = (curse *)a->data.v;
@@ -266,7 +266,7 @@ static void magicanalyse_ship(ship * sh, unit * mage, double force)
curse *c;
double probability;
int mon;
- if (!fval(a->type, ATF_CURSE))
+ if (a->type != &at_curse)
continue;
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) {
curse *c1;
attrib *a = *ap;
- if (!fval(a->type, ATF_CURSE)) {
+ if (a->type != &at_curse) {
do {
ap = &(*ap)->next;
} while (*ap && a->type == (*ap)->type);
@@ -2970,7 +2970,7 @@ static int sp_deathcloud(castorder * co)
unit *u;
while (a) {
- if ((a->type->flags & ATF_CURSE)) {
+ if (a->type == &at_curse) {
curse *c = a->data.v;
if (c->type == &ct_deathcloud) {
report_failure(mage, co->order);
@@ -3116,46 +3116,6 @@ static int sp_summonshadowlords(castorder * co)
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
* Stufe: 14
diff --git a/src/spells/borders.c b/src/spells/borders.c
index 00019e92c..331e7380f 100644
--- a/src/spells/borders.c
+++ b/src/spells/borders.c
@@ -1,6 +1,7 @@
#include
#include "borders.h"
+#include "vortex.h"
#include
#include
@@ -28,46 +29,15 @@ typedef struct wallcurse {
connection *wall;
} wallcurse;
-static void cw_init(attrib * a)
-{
- 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)
+static int cw_read_depr(attrib * a, void *target, gamedata *data)
{
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);
- br->self = c;
- READ_INT(store, &br->id);
-
- 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;
+ curse_done(a);
+ READ_INT(store, NULL);
+ return AT_READ_DEPR;
}
/* ------------------------------------------------------------- */
@@ -105,61 +75,6 @@ const curse_type ct_firewall = {
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)
{
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
};
+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)
{
border_convert_cb = &convert_firewall_timeouts;
- at_register(&at_cursewall);
+ at_deprecate("cursewall", cw_read_depr);
register_bordertype(&bt_firewall);
register_bordertype(&bt_wisps);
diff --git a/src/spells/borders.h b/src/spells/borders.h
index b14661b9f..40bc55682 100644
--- a/src/spells/borders.h
+++ b/src/spells/borders.h
@@ -15,6 +15,7 @@ extern "C" {
**/
extern struct border_type bt_chaosgate;
extern struct border_type bt_firewall;
+ extern const struct curse_type ct_firewall;
typedef struct wall_data {
struct unit *mage;
@@ -23,7 +24,6 @@ extern "C" {
int countdown;
} wall_data;
- extern const struct curse_type ct_firewall;
#ifdef __cplusplus
}
diff --git a/src/tests.c b/src/tests.c
index cfcd6d0ca..49b5b7c05 100644
--- a/src/tests.c
+++ b/src/tests.c
@@ -4,6 +4,7 @@
#include "prefix.h"
#include "reports.h"
#include "calendar.h"
+#include "vortex.h"
#include
#include
@@ -209,6 +210,7 @@ static void test_reset(void) {
default_locale = 0;
calendar_cleanup();
close_orders();
+ free_special_directions();
free_locales();
free_spells();
free_buildingtypes();
diff --git a/src/vortex.c b/src/vortex.c
index 7b6206d03..773982124 100644
--- a/src/vortex.c
+++ b/src/vortex.c
@@ -26,6 +26,16 @@ typedef struct dir_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)
{
const char *token = locale_string(lang, name, false);
diff --git a/src/vortex.h b/src/vortex.h
index 993f5cdab..ea7af624a 100644
--- a/src/vortex.h
+++ b/src/vortex.h
@@ -26,6 +26,8 @@ extern "C" {
struct region *find_special_direction(const struct region *r,
const char *token);
void register_special_direction(struct locale *lang, const char *name);
+ void free_special_directions(void);
+
struct spec_direction *special_direction(const struct region * from,
const struct region * to);
struct attrib *create_special_direction(struct region *r, struct region *rt,