From fdfe0d3b355688b8d16d40775cfc50716f4314c7 Mon Sep 17 00:00:00 2001
From: Enno Rehling <enno.rehling@gmail.com>
Date: Mon, 29 Jul 2019 23:53:59 +0200
Subject: [PATCH] check MAX_ENTERTAINERS. extract recruit from economy. Bug
 2600 (WIP).

---
 scripts/tests/orders.lua |   6 +-
 src/CMakeLists.txt       |   1 +
 src/economy.c            | 419 +-------------------------------
 src/economy.h            |   4 +-
 src/economy.test.c       |   5 +-
 src/laws.c               |   5 +-
 src/recruit.c            | 505 +++++++++++++++++++++++++++++++++++++++
 src/recruit.h            |  40 ++++
 8 files changed, 566 insertions(+), 419 deletions(-)
 create mode 100644 src/recruit.c
 create mode 100644 src/recruit.h

diff --git a/scripts/tests/orders.lua b/scripts/tests/orders.lua
index 63abaed00..6dc191d6c 100644
--- a/scripts/tests/orders.lua
+++ b/scripts/tests/orders.lua
@@ -70,15 +70,15 @@ end
 function test_give_temp()
     u.number = 2
     u:add_order("GIB TEMP 123 1 PERSON")
-    u:add_order("MACHE TEMP 123 'Herpderp'")
+    u:add_order("MACHE TEMP 123 'Lorax'")
     u:add_order("ENDE")
     _G.process_orders()
     assert_equal(1, u.number)
 
     for x in f.units do
-        if x.name == 'Herpderp' then u=x end
+        if x.name == 'Lorax' then u=x end
     end
-    assert_equal('Herpderp', u.name)
+    assert_equal('Lorax', u.name)
     assert_equal(1, u.number)
 end
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bd0ed2bff..76764f0bd 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -101,6 +101,7 @@ set (ERESSEA_SRC
   creport.c
   direction.c
   donations.c
+  recruit.c
   economy.c
   eressea.c
   exparse.c
diff --git a/src/economy.c b/src/economy.c
index 72e623542..393d9b023 100644
--- a/src/economy.c
+++ b/src/economy.c
@@ -86,25 +86,16 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 
 static int working;
 
-static econ_request entertainers[1024];
+#define MAX_WORKERS 1024
+static struct econ_request workers[MAX_WORKERS];
+
+#define MAX_ENTERTAINERS 256
+static econ_request entertainers[MAX_ENTERTAINERS];
 static econ_request *nextentertainer;
 static int entertaining;
 
 static econ_request **g_requests; /* TODO: no need for this to be module-global */
 
-#define RECRUIT_MERGE 1
-static int rules_recruit = -1;
-
-static void recruit_init(void)
-{
-    if (rules_recruit < 0) {
-        rules_recruit = 0;
-        if (config_get_int("recruit.allow_merge", 1)) {
-            rules_recruit |= RECRUIT_MERGE;
-        }
-    }
-}
-
 #define ENTERTAINFRACTION 20
 
 int entertainmoney(const region * r)
@@ -201,377 +192,6 @@ static int expandorders(region * r, econ_request * requests) {
 
 /* ------------------------------------------------------------- */
 
-typedef struct recruitment {
-    struct recruitment *next;
-    faction *f;
-    econ_request *requests;
-    int total, assigned;
-} recruitment;
-
-/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable production
- * to the faction's struct and to total.
- */
-static recruitment *select_recruitment(econ_request ** rop,
-    int(*quantify) (const struct race *, int), int *total)
-{
-    recruitment *recruits = NULL;
-
-    while (*rop) {
-        recruitment *rec = recruits;
-        econ_request *ro = *rop;
-        unit *u = ro->unit;
-        const race *rc = u_race(u);
-        int qty = quantify(rc, ro->qty);
-
-        if (qty < 0) {
-            rop = &ro->next;          /* skip this one */
-        }
-        else {
-            *rop = ro->next;          /* remove this one */
-            while (rec && rec->f != u->faction)
-                rec = rec->next;
-            if (rec == NULL) {
-                rec = (recruitment *)malloc(sizeof(recruitment));
-                if (!rec) abort();
-                rec->f = u->faction;
-                rec->total = 0;
-                rec->assigned = 0;
-                rec->requests = NULL;
-                rec->next = recruits;
-                recruits = rec;
-            }
-            *total += qty;
-            rec->total += qty;
-            ro->next = rec->requests;
-            rec->requests = ro;
-        }
-    }
-    return recruits;
-}
-
-void add_recruits(unit * u, int number, int wanted)
-{
-    region *r = u->region;
-    assert(number <= wanted);
-    if (number > 0) {
-        unit *unew;
-        char equipment[64];
-        int len;
-
-        if (u->number == 0) {
-            set_number(u, number);
-            u->hp = number * unit_max_hp(u);
-            unew = u;
-        }
-        else {
-            unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u);
-        }
-
-        len = snprintf(equipment, sizeof(equipment), "new_%s", u_race(u)->_name);
-        if (len > 0 && (size_t)len < sizeof(equipment)) {
-            equip_unit(unew, equipment);
-        }
-        if (unew != u) {
-            transfermen(unew, u, unew->number);
-            remove_unit(&r->units, unew);
-        }
-    }
-    if (number < wanted) {
-        ADDMSG(&u->faction->msgs, msg_message("recruit",
-            "unit region amount want", u, r, number, wanted));
-    }
-}
-
-static int any_recruiters(const struct race *rc, int qty)
-{
-    return (int)(qty * 2 * rc->recruit_multi);
-}
-
-static int do_recruiting(recruitment * recruits, int available)
-{
-    recruitment *rec;
-    int recruited = 0;
-
-    /* try to assign recruits to factions fairly */
-    while (available > 0) {
-        int n = 0;
-        int rest, mintotal = INT_MAX;
-
-        /* find smallest production */
-        for (rec = recruits; rec != NULL; rec = rec->next) {
-            int want = rec->total - rec->assigned;
-            if (want > 0) {
-                if (mintotal > want)
-                    mintotal = want;
-                ++n;
-            }
-        }
-        if (n == 0)
-            break;
-        if (mintotal * n > available) {
-            mintotal = available / n;
-        }
-        rest = available - mintotal * n;
-
-        /* assign size of smallest production for everyone if possible; in the end roll dice to assign
-         * small rest */
-        for (rec = recruits; rec != NULL; rec = rec->next) {
-            int want = rec->total - rec->assigned;
-
-            if (want > 0) {
-                int get = mintotal;
-                if (want > mintotal && rest < n && (rng_int() % n) < rest) {
-                    --rest;
-                    ++get;
-                }
-                assert(get <= want);
-                available -= get;
-                rec->assigned += get;
-            }
-        }
-    }
-
-    /* do actual recruiting */
-    for (rec = recruits; rec != NULL; rec = rec->next) {
-        econ_request *req;
-        int get = rec->assigned;
-
-        for (req = rec->requests; req; req = req->next) {
-            unit *u = req->unit;
-            const race *rc = u_race(u); /* race is set in recruit() */
-            int number;
-            double multi = 2.0 * rc->recruit_multi;
-
-            number = (int)(get / multi);
-            if (number > req->qty) number = req->qty;
-            if (rc->recruitcost) {
-                int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
-                    number * rc->recruitcost) / rc->recruitcost;
-                if (number > afford) number = afford;
-            }
-            if (u->number + number > UNIT_MAXSIZE) {
-                ADDMSG(&u->faction->msgs, msg_feedback(u, req->type.recruit.ord, "error_unit_size",
-                    "maxsize", UNIT_MAXSIZE));
-                number = UNIT_MAXSIZE - u->number;
-                assert(number >= 0);
-            }
-            if (rc->recruitcost) {
-                use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
-                    rc->recruitcost * number);
-            }
-            if (u->number == 0 && fval(u, UFL_DEAD)) {
-                /* unit is empty, dead, and cannot recruit */
-                number = 0;
-            }
-            add_recruits(u, number, req->qty);
-            if (number > 0) {
-                int dec = (int)(number * multi);
-                if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
-                    recruited += dec;
-                }
-
-                get -= dec;
-            }
-        }
-    }
-    return recruited;
-}
-
-void free_recruitments(recruitment * recruits)
-{
-    while (recruits) {
-        recruitment *rec = recruits;
-        recruits = rec->next;
-        while (rec->requests) {
-            econ_request *req = rec->requests;
-            rec->requests = req->next;
-            free(req);
-        }
-        free(rec);
-    }
-}
-
-/* Rekrutierung */
-static void expandrecruit(region * r, econ_request * recruitorders)
-{
-    recruitment *recruits;
-    int orc_total = 0;
-
-    /* peasant limited: */
-    recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
-    if (recruits) {
-        int orc_recruited, orc_peasants = rpeasants(r) * 2;
-        int orc_frac = orc_peasants / RECRUITFRACTION;      /* anzahl orks. 2 ork = 1 bauer */
-        if (orc_total < orc_frac)
-            orc_frac = orc_total;
-        orc_recruited = do_recruiting(recruits, orc_frac);
-        assert(orc_recruited <= orc_frac);
-        rsetpeasants(r, (orc_peasants - orc_recruited) / 2);
-        free_recruitments(recruits);
-    }
-
-    /* no limit: */
-    recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
-    if (recruits) {
-        int recruited, peasants = rpeasants(r) * 2;
-        recruited = do_recruiting(recruits, INT_MAX);
-        if (recruited > 0) {
-            rsetpeasants(r, (peasants - recruited) / 2);
-        }
-        free_recruitments(recruits);
-    }
-
-    assert(recruitorders == NULL);
-}
-
-static int recruit_cost(const faction * f, const race * rc)
-{
-    if (is_monsters(f) || valid_race(f, rc)) {
-        return rc->recruitcost;
-    }
-    return -1;
-}
-
-message *can_recruit(unit *u, const race *rc, order *ord, int now)
-{
-    region *r = u->region;
-
-    /* this is a very special case because the recruiting unit may be empty
-    * at this point and we have to look at the creating unit instead. This
-    * is done in cansee, which is called indirectly by is_guarded(). */
-    if (is_guarded(r, u)) {
-        return msg_error(u, ord, 70);
-    }
-
-    if (rc == get_race(RC_INSECT)) {
-        gamedate date;
-        get_gamedate(now, &date);
-        if (date.season == SEASON_WINTER && r->terrain != newterrain(T_DESERT)) {
-            bool usepotion = false;
-            unit *u2;
-
-            for (u2 = r->units; u2; u2 = u2->next) {
-                if (fval(u2, UFL_WARMTH)) {
-                    usepotion = true;
-                    break;
-                }
-            }
-            if (!usepotion) {
-                return msg_error(u, ord, 98);
-            }
-        }
-        /* in Gletschern, Eisbergen gar nicht rekrutieren */
-        if (r_insectstalled(r)) {
-            return msg_error(u, ord, 97);
-        }
-    }
-    if (is_cursed(r->attribs, &ct_riotzone)) {
-        /* Die Region befindet sich in Aufruhr */
-        return msg_error(u, ord, 237);
-    }
-
-    if (rc && !playerrace(rc)) {
-        return msg_error(u, ord, 139);
-    }
-
-    if (fval(u, UFL_HERO)) {
-        return msg_feedback(u, ord, "error_herorecruit", "");
-    }
-    if (has_skill(u, SK_MAGIC)) {
-        /* error158;de;{unit} in {region}: '{command}' - Magier arbeiten
-        * grundsaetzlich nur alleine! */
-        return msg_error(u, ord, 158);
-    }
-    return NULL;
-}
-
-static void recruit(unit * u, struct order *ord, econ_request ** recruitorders)
-{
-    region *r = u->region;
-    econ_request *o;
-    int recruitcost = -1;
-    const faction *f = u->faction;
-    const struct race *rc = u_race(u);
-    int n;
-    message *msg;
-
-    init_order_depr(ord);
-    n = getint();
-    if (n <= 0) {
-        syntax_error(u, ord);
-        return;
-    }
-
-    if (u->number == 0) {
-        char token[128];
-        const char *str;
-
-        str = gettoken(token, sizeof(token));
-        if (str && str[0]) {
-            /* Monsters can RECRUIT 15 DRACOID
-             * also: secondary race */
-            rc = findrace(str, f->locale);
-            if (rc != NULL) {
-                recruitcost = recruit_cost(f, rc);
-            }
-        }
-    }
-
-    if (recruitcost < 0) {
-        rc = u_race(u);
-        recruitcost = recruit_cost(f, rc);
-        if (recruitcost < 0) {
-            recruitcost = INT_MAX;
-        }
-    }
-
-    if (recruitcost > 0) {
-        int pool;
-        plane *pl = getplane(r);
-
-        if (pl && (pl->flags & PFL_NORECRUITS)) {
-            ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
-            return;
-        }
-
-        pool = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
-        if (pool < recruitcost) {
-            cmistake(u, ord, 142, MSG_EVENT);
-            return;
-        }
-        pool /= recruitcost;
-        if (n > pool) n = pool;
-    }
-
-    if (!n) {
-        cmistake(u, ord, 142, MSG_EVENT);
-        return;
-    }
-    if (has_skill(u, SK_ALCHEMY)) {
-        if (faction_count_skill(u->faction, SK_ALCHEMY) + n > faction_skill_limit(u->faction, SK_ALCHEMY)) {
-            cmistake(u, ord, 156, MSG_EVENT);
-            return;
-        }
-    }
-
-    assert(rc);
-    msg = can_recruit(u, rc, ord, turn);
-    if (msg) {
-        add_message(&u->faction->msgs, msg);
-        msg_release(msg);
-        return;
-    }
-
-    u_setrace(u, rc);
-    u->wants = n;
-    o = (econ_request *)calloc(1, sizeof(econ_request));
-    if (!o) abort();
-    o->qty = n;
-    o->unit = u;
-    o->type.recruit.ord = ord;
-    addlist(recruitorders, o);
-}
-
 static void friendly_takeover(region * r, faction * f)
 {
     region_set_owner(r, f, turn);
@@ -761,7 +381,6 @@ void maintain_buildings(region * r)
 void economics(region * r)
 {
     unit *u;
-    econ_request *recruitorders = NULL;
 
     /* Geben vor Selbstmord (doquit)! Hier alle unmittelbaren Befehle.
      * Rekrutieren vor allen Einnahmequellen. Bewachen JA vor Steuern
@@ -784,28 +403,10 @@ void economics(region * r)
             }
         }
     }
-    /* RECRUIT orders */
-
-    if (rules_recruit < 0)
-        recruit_init();
-    for (u = r->units; u; u = u->next) {
-        order *ord;
-
-        if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) {
-            for (ord = u->orders; ord; ord = ord->next) {
-                if (getkeyword(ord) == K_RECRUIT) {
-                    recruit(u, ord, &recruitorders);
-                    break;
-                }
-            }
-        }
-    }
-
-    if (recruitorders) {
-        expandrecruit(r, recruitorders);
-    }
-    remove_empty_units_in_region(r);
+}
 
+void destroy(region *r) {
+    unit *u;
     for (u = r->units; u; u = u->next) {
         order *ord = u->thisorder;
         keyword_t kwd = getkeyword(ord);
@@ -2418,6 +2019,7 @@ void entertain_cmd(unit * u, struct order *ord)
         if (u->wants > max_e) u->wants = max_e;
     }
     o = nextentertainer++;
+    assert(nextentertainer - entertainers < MAX_ENTERTAINERS);
     o->unit = u;
     o->qty = u->wants;
     entertaining += o->qty;
@@ -2709,9 +2311,6 @@ void loot_cmd(unit * u, struct order *ord, econ_request ** lootorders)
     return;
 }
 
-#define MAX_WORKERS 1024
-static struct econ_request workers[MAX_WORKERS];
-
 void auto_work(region * r)
 {
     econ_request *nextworker = workers;
diff --git a/src/economy.h b/src/economy.h
index 9456a4c93..130792e09 100644
--- a/src/economy.h
+++ b/src/economy.h
@@ -73,6 +73,7 @@ extern "C" {
     int entertainmoney(const struct region *r);
 
     void economics(struct region *r);
+    void destroy(struct region *r);
     void produce(struct region *r);
     void auto_work(struct region *r);
 
@@ -95,9 +96,6 @@ extern "C" {
     void steal_cmd(struct unit * u, struct order *ord, struct econ_request ** stealorders);
     void expandstealing(struct region * r, struct econ_request * stealorders);
 
-    struct message *can_recruit(struct unit *u, const struct race *rc, struct order *ord, int now);
-    void add_recruits(struct unit * u, int number, int wanted);
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/economy.test.c b/src/economy.test.c
index 65059ffb2..024ccadbd 100644
--- a/src/economy.test.c
+++ b/src/economy.test.c
@@ -3,6 +3,7 @@
 #endif
 #include <kernel/config.h>
 #include "economy.h"
+#include "recruit.h"
 
 #include <util/message.h>
 #include <kernel/building.h>
@@ -162,7 +163,7 @@ static void test_heroes_dont_recruit(CuTest * tc) {
     fset(u, UFL_HERO);
     unit_addorder(u, create_order(K_RECRUIT, default_locale, "1"));
 
-    economics(u->region);
+    recruit(u->region);
 
     CuAssertIntEquals(tc, 1, u->number);
     CuAssertPtrNotNull(tc, test_find_messagetype(u->faction->msgs, "error_herorecruit"));
@@ -178,7 +179,7 @@ static void test_normals_recruit(CuTest * tc) {
     u = create_recruiter();
     unit_addorder(u, create_order(K_RECRUIT, default_locale, "1"));
 
-    economics(u->region);
+    recruit(u->region);
 
     CuAssertIntEquals(tc, 2, u->number);
 
diff --git a/src/laws.c b/src/laws.c
index 519c268e4..7a61a8389 100644
--- a/src/laws.c
+++ b/src/laws.c
@@ -35,6 +35,7 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 #include "monsters.h"
 #include "move.h"
 #include "randenc.h"
+#include "recruit.h"
 #include "renumber.h"
 #include "spy.h"
 #include "study.h"
@@ -3942,7 +3943,9 @@ void init_processor(void)
     if (rule_force_leave(FORCE_LEAVE_ALL)) {
         add_proc_region(p, do_force_leave, "kick non-allies out of buildings/ships");
     }
-    add_proc_region(p, economics, "Zerstoeren, Geben, Rekrutieren, Vergessen");
+    add_proc_region(p, economics, "Geben, Vergessen");
+    add_proc_region(p+1, recruit, "Rekrutieren");
+    add_proc_region(p+2, destroy, "Zerstoeren");
 
     /* all recruitment must be finished before we can calculate 
      * promotion cost of ability */
diff --git a/src/recruit.c b/src/recruit.c
new file mode 100644
index 000000000..ff7f19ef4
--- /dev/null
+++ b/src/recruit.c
@@ -0,0 +1,505 @@
+/*
+Copyright (c) 1998-2019,
+Enno Rehling <enno@eressea.de>
+Katja Zedel <katze@felidae.kn-bremen.de
+Christian Schlittchen <corwin@amber.kn-bremen.de>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+**/
+
+#ifdef _MSC_VER
+#include <platform.h>
+#endif
+
+#include "recruit.h"
+
+#include "alchemy.h"
+#include "direction.h"
+#include "donations.h"
+#include "guard.h"
+#include "give.h"
+#include "laws.h"
+#include "randenc.h"
+#include "spy.h"
+#include "study.h"
+#include "move.h"
+#include "monsters.h"
+#include "morale.h"
+#include "reports.h"
+
+#include <attributes/reduceproduction.h>
+#include <attributes/racename.h>
+#include <spells/buildingcurse.h>
+#include <spells/regioncurse.h>
+#include <spells/unitcurse.h>
+
+/* kernel includes */
+#include "kernel/ally.h"
+#include "kernel/attrib.h"
+#include "kernel/building.h"
+#include "kernel/calendar.h"
+#include "kernel/config.h"
+#include "kernel/curse.h"
+#include "kernel/equipment.h"
+#include "kernel/event.h"
+#include "kernel/faction.h"
+#include "kernel/item.h"
+#include "kernel/messages.h"
+#include "kernel/order.h"
+#include "kernel/plane.h"
+#include "kernel/pool.h"
+#include "kernel/race.h"
+#include "kernel/region.h"
+#include "kernel/resources.h"
+#include "kernel/ship.h"
+#include "kernel/terrain.h"
+#include "kernel/terrainid.h"
+#include "kernel/unit.h"
+
+/* util includes */
+#include <util/base36.h>
+#include <util/goodies.h>
+#include <util/language.h>
+#include <util/lists.h>
+#include <util/log.h>
+#include "util/param.h"
+#include <util/parser.h>
+#include <util/rng.h>
+
+/* libs includes */
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <limits.h>
+
+#define RECRUIT_MERGE 1
+static int rules_recruit = -1;
+
+typedef struct recruit_request {
+    struct recruit_request *next;
+    struct unit *unit;
+    struct order *ord;
+    int qty;
+} recruit_request;
+
+typedef struct recruitment {
+    struct recruitment *next;
+    faction *f;
+    recruit_request *requests;
+    int total, assigned;
+} recruitment;
+
+static void recruit_init(void)
+{
+    if (rules_recruit < 0) {
+        rules_recruit = 0;
+        if (config_get_int("recruit.allow_merge", 1)) {
+            rules_recruit |= RECRUIT_MERGE;
+        }
+    }
+}
+
+static void free_requests(recruit_request *requests) {
+    while (requests) {
+        recruit_request *req = requests->next;
+        free(requests);
+        requests = req;
+    }
+}
+
+void free_recruitments(recruitment * recruits)
+{
+    while (recruits) {
+        recruitment *rec = recruits;
+        recruits = rec->next;
+        free_requests(rec->requests);
+    }
+}
+
+/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable production
+ * to the faction's struct and to total.
+ */
+static recruitment *select_recruitment(recruit_request ** rop,
+    int(*quantify) (const struct race *, int), int *total)
+{
+    recruitment *recruits = NULL;
+
+    while (*rop) {
+        recruitment *rec = recruits;
+        recruit_request *ro = *rop;
+        unit *u = ro->unit;
+        const race *rc = u_race(u);
+        int qty = quantify(rc, ro->qty);
+
+        if (qty < 0) {
+            rop = &ro->next;          /* skip this one */
+        }
+        else {
+            *rop = ro->next;          /* remove this one */
+            while (rec && rec->f != u->faction)
+                rec = rec->next;
+            if (rec == NULL) {
+                rec = (recruitment *)malloc(sizeof(recruitment));
+                if (!rec) abort();
+                rec->f = u->faction;
+                rec->total = 0;
+                rec->assigned = 0;
+                rec->requests = NULL;
+                rec->next = recruits;
+                recruits = rec;
+            }
+            *total += qty;
+            rec->total += qty;
+            ro->next = rec->requests;
+            rec->requests = ro;
+        }
+    }
+    return recruits;
+}
+
+void add_recruits(unit * u, int number, int wanted)
+{
+    region *r = u->region;
+    assert(number <= wanted);
+    if (number > 0) {
+        unit *unew;
+        char equipment[64];
+        int len;
+
+        if (u->number == 0) {
+            set_number(u, number);
+            u->hp = number * unit_max_hp(u);
+            unew = u;
+        }
+        else {
+            unew = create_unit(r, u->faction, number, u_race(u), 0, NULL, u);
+        }
+
+        len = snprintf(equipment, sizeof(equipment), "new_%s", u_race(u)->_name);
+        if (len > 0 && (size_t)len < sizeof(equipment)) {
+            equip_unit(unew, equipment);
+        }
+        if (unew != u) {
+            transfermen(unew, u, unew->number);
+            remove_unit(&r->units, unew);
+        }
+    }
+    if (number < wanted) {
+        ADDMSG(&u->faction->msgs, msg_message("recruit",
+            "unit region amount want", u, r, number, wanted));
+    }
+}
+
+static int any_recruiters(const struct race *rc, int qty)
+{
+    return (int)(qty * 2 * rc->recruit_multi);
+}
+
+static int do_recruiting(recruitment * recruits, int available)
+{
+    recruitment *rec;
+    int recruited = 0;
+
+    /* try to assign recruits to factions fairly */
+    while (available > 0) {
+        int n = 0;
+        int rest, mintotal = INT_MAX;
+
+        /* find smallest production */
+        for (rec = recruits; rec != NULL; rec = rec->next) {
+            int want = rec->total - rec->assigned;
+            if (want > 0) {
+                if (mintotal > want)
+                    mintotal = want;
+                ++n;
+            }
+        }
+        if (n == 0)
+            break;
+        if (mintotal * n > available) {
+            mintotal = available / n;
+        }
+        rest = available - mintotal * n;
+
+        /* assign size of smallest production for everyone if possible; in the end roll dice to assign
+         * small rest */
+        for (rec = recruits; rec != NULL; rec = rec->next) {
+            int want = rec->total - rec->assigned;
+
+            if (want > 0) {
+                int get = mintotal;
+                if (want > mintotal && rest < n && (rng_int() % n) < rest) {
+                    --rest;
+                    ++get;
+                }
+                assert(get <= want);
+                available -= get;
+                rec->assigned += get;
+            }
+        }
+    }
+
+    /* do actual recruiting */
+    for (rec = recruits; rec != NULL; rec = rec->next) {
+        recruit_request *req;
+        int get = rec->assigned;
+
+        for (req = rec->requests; req; req = req->next) {
+            unit *u = req->unit;
+            const race *rc = u_race(u); /* race is set in recruit() */
+            int number;
+            double multi = 2.0 * rc->recruit_multi;
+
+            number = (int)(get / multi);
+            if (number > req->qty) number = req->qty;
+            if (rc->recruitcost) {
+                int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
+                    number * rc->recruitcost) / rc->recruitcost;
+                if (number > afford) number = afford;
+            }
+            if (u->number + number > UNIT_MAXSIZE) {
+                ADDMSG(&u->faction->msgs, msg_feedback(u, req->ord, "error_unit_size",
+                    "maxsize", UNIT_MAXSIZE));
+                number = UNIT_MAXSIZE - u->number;
+                assert(number >= 0);
+            }
+            if (rc->recruitcost) {
+                use_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
+                    rc->recruitcost * number);
+            }
+            if (u->number == 0 && fval(u, UFL_DEAD)) {
+                /* unit is empty, dead, and cannot recruit */
+                number = 0;
+            }
+            add_recruits(u, number, req->qty);
+            if (number > 0) {
+                int dec = (int)(number * multi);
+                if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
+                    recruited += dec;
+                }
+
+                get -= dec;
+            }
+        }
+    }
+    return recruited;
+}
+
+/* Rekrutierung */
+static void expandrecruit(region * r, recruit_request * recruitorders)
+{
+    recruitment *recruits;
+    int orc_total = 0;
+
+    /* peasant limited: */
+    recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
+    if (recruits) {
+        int orc_recruited, orc_peasants = rpeasants(r) * 2;
+        int orc_frac = orc_peasants / RECRUITFRACTION;      /* anzahl orks. 2 ork = 1 bauer */
+        if (orc_total < orc_frac)
+            orc_frac = orc_total;
+        orc_recruited = do_recruiting(recruits, orc_frac);
+        assert(orc_recruited <= orc_frac);
+        rsetpeasants(r, (orc_peasants - orc_recruited) / 2);
+        free_recruitments(recruits);
+    }
+
+    /* no limit: */
+    recruits = select_recruitment(&recruitorders, any_recruiters, &orc_total);
+    if (recruits) {
+        int recruited, peasants = rpeasants(r) * 2;
+        recruited = do_recruiting(recruits, INT_MAX);
+        if (recruited > 0) {
+            rsetpeasants(r, (peasants - recruited) / 2);
+        }
+        free_recruitments(recruits);
+    }
+
+    assert(recruitorders == NULL);
+}
+
+static int recruit_cost(const faction * f, const race * rc)
+{
+    if (is_monsters(f) || valid_race(f, rc)) {
+        return rc->recruitcost;
+    }
+    return -1;
+}
+
+message *can_recruit(unit *u, const race *rc, order *ord, int now)
+{
+    region *r = u->region;
+
+    /* this is a very special case because the recruiting unit may be empty
+    * at this point and we have to look at the creating unit instead. This
+    * is done in cansee, which is called indirectly by is_guarded(). */
+    if (is_guarded(r, u)) {
+        return msg_error(u, ord, 70);
+    }
+
+    if (rc == get_race(RC_INSECT)) {
+        gamedate date;
+        get_gamedate(now, &date);
+        if (date.season == SEASON_WINTER && r->terrain != newterrain(T_DESERT)) {
+            bool usepotion = false;
+            unit *u2;
+
+            for (u2 = r->units; u2; u2 = u2->next) {
+                if (fval(u2, UFL_WARMTH)) {
+                    usepotion = true;
+                    break;
+                }
+            }
+            if (!usepotion) {
+                return msg_error(u, ord, 98);
+            }
+        }
+        /* in Gletschern, Eisbergen gar nicht rekrutieren */
+        if (r_insectstalled(r)) {
+            return msg_error(u, ord, 97);
+        }
+    }
+    if (is_cursed(r->attribs, &ct_riotzone)) {
+        /* Die Region befindet sich in Aufruhr */
+        return msg_error(u, ord, 237);
+    }
+
+    if (rc && !playerrace(rc)) {
+        return msg_error(u, ord, 139);
+    }
+
+    if (fval(u, UFL_HERO)) {
+        return msg_feedback(u, ord, "error_herorecruit", "");
+    }
+    if (has_skill(u, SK_MAGIC)) {
+        /* error158;de;{unit} in {region}: '{command}' - Magier arbeiten
+        * grundsaetzlich nur alleine! */
+        return msg_error(u, ord, 158);
+    }
+    return NULL;
+}
+
+static void recruit_cmd(unit * u, struct order *ord, recruit_request ** recruitorders)
+{
+    region *r = u->region;
+    recruit_request *o;
+    int recruitcost = -1;
+    const faction *f = u->faction;
+    const struct race *rc = u_race(u);
+    int n;
+    message *msg;
+
+    init_order_depr(ord);
+    n = getint();
+    if (n <= 0) {
+        syntax_error(u, ord);
+        return;
+    }
+
+    if (u->number == 0) {
+        char token[128];
+        const char *str;
+
+        str = gettoken(token, sizeof(token));
+        if (str && str[0]) {
+            /* Monsters can RECRUIT 15 DRACOID
+             * also: secondary race */
+            rc = findrace(str, f->locale);
+            if (rc != NULL) {
+                recruitcost = recruit_cost(f, rc);
+            }
+        }
+    }
+
+    if (recruitcost < 0) {
+        rc = u_race(u);
+        recruitcost = recruit_cost(f, rc);
+        if (recruitcost < 0) {
+            recruitcost = INT_MAX;
+        }
+    }
+
+    if (recruitcost > 0) {
+        int pool;
+        plane *pl = getplane(r);
+
+        if (pl && (pl->flags & PFL_NORECRUITS)) {
+            ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
+            return;
+        }
+
+        pool = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
+        if (pool < recruitcost) {
+            cmistake(u, ord, 142, MSG_EVENT);
+            return;
+        }
+        pool /= recruitcost;
+        if (n > pool) n = pool;
+    }
+
+    if (!n) {
+        cmistake(u, ord, 142, MSG_EVENT);
+        return;
+    }
+    if (has_skill(u, SK_ALCHEMY)) {
+        if (faction_count_skill(u->faction, SK_ALCHEMY) + n > faction_skill_limit(u->faction, SK_ALCHEMY)) {
+            cmistake(u, ord, 156, MSG_EVENT);
+            return;
+        }
+    }
+
+    assert(rc);
+    msg = can_recruit(u, rc, ord, turn);
+    if (msg) {
+        add_message(&u->faction->msgs, msg);
+        msg_release(msg);
+        return;
+    }
+
+    u_setrace(u, rc);
+    u->wants = n;
+    o = (recruit_request *)calloc(1, sizeof(recruit_request));
+    if (!o) abort();
+    o->qty = n;
+    o->unit = u;
+    o->ord = ord;
+    addlist(recruitorders, o);
+}
+
+void recruit(region * r)
+{
+    unit *u;
+    recruit_request *recruitorders = NULL;
+
+    if (rules_recruit < 0)
+        recruit_init();
+    for (u = r->units; u; u = u->next) {
+        order *ord;
+
+        if ((rules_recruit & RECRUIT_MERGE) || u->number == 0) {
+            for (ord = u->orders; ord; ord = ord->next) {
+                if (getkeyword(ord) == K_RECRUIT) {
+                    recruit_cmd(u, ord, &recruitorders);
+                    break;
+                }
+            }
+        }
+    }
+
+    if (recruitorders) {
+        expandrecruit(r, recruitorders);
+    }
+    remove_empty_units_in_region(r);
+}
diff --git a/src/recruit.h b/src/recruit.h
new file mode 100644
index 000000000..347ea99f9
--- /dev/null
+++ b/src/recruit.h
@@ -0,0 +1,40 @@
+/*
+Copyright (c) 1998-2015, Enno Rehling <enno@eressea.de>
+Katja Zedel <katze@felidae.kn-bremen.de
+Christian Schlittchen <corwin@amber.kn-bremen.de>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+**/
+
+#pragma once
+#ifndef H_GC_RECRUIT
+#define H_GC_RECRUIT
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+    struct message;
+    struct order;
+    struct race;
+    struct region;
+    struct unit;
+
+    struct message *can_recruit(struct unit *u, const struct race *rc, struct order *ord, int now);
+    void add_recruits(struct unit * u, int number, int wanted);
+    void recruit(struct region * r);
+
+#ifdef __cplusplus
+}
+#endif
+#endif