server/src/economy.c

2973 lines
84 KiB
C
Raw Normal View History

2010-08-08 10:06:34 +02:00
/*
2014-08-08 01:03:46 +02:00
Copyright (c) 1998-2014,
Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
2010-08-08 10:06:34 +02:00
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.
**/
2017-12-30 07:34:17 +01:00
#ifdef _MSC_VER
2010-08-08 10:06:34 +02:00
#include <platform.h>
2017-12-30 07:34:17 +01:00
#endif
2018-09-29 19:32:39 +02:00
2010-08-08 10:06:34 +02:00
#include "economy.h"
#include "alchemy.h"
#include "direction.h"
#include "donations.h"
#include "guard.h"
2010-08-08 10:06:34 +02:00
#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"
2010-08-08 10:06:34 +02:00
#include <attributes/reduceproduction.h>
#include <attributes/racename.h>
#include <spells/buildingcurse.h>
#include <spells/regioncurse.h>
#include <spells/unitcurse.h>
2010-08-08 10:06:34 +02:00
/* kernel includes */
#include "kernel/ally.h"
2018-09-29 19:32:39 +02:00
#include "kernel/attrib.h"
#include "kernel/building.h"
#include "kernel/calendar.h"
2018-09-29 19:32:39 +02:00
#include "kernel/config.h"
#include "kernel/curse.h"
#include "kernel/equipment.h"
2018-09-29 19:32:39 +02:00
#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"
2010-08-08 10:06:34 +02:00
/* util includes */
#include <util/base36.h>
#include <util/goodies.h>
#include <util/language.h>
#include <util/lists.h>
#include <util/log.h>
2018-09-29 19:32:39 +02:00
#include "util/param.h"
2010-08-08 10:06:34 +02:00
#include <util/parser.h>
#include <util/rng.h>
/* libs includes */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
2010-08-08 10:06:34 +02:00
#include <string.h>
#include <assert.h>
#include <limits.h>
static int working;
static econ_request entertainers[1024];
static econ_request *nextentertainer;
static int entertaining;
2010-08-08 10:06:34 +02:00
static econ_request **g_requests; /* TODO: no need for this to be module-global */
2010-08-08 10:06:34 +02:00
#define RECRUIT_MERGE 1
static int rules_recruit = -1;
static void recruit_init(void)
{
2014-08-08 01:03:46 +02:00
if (rules_recruit < 0) {
rules_recruit = 0;
if (config_get_int("recruit.allow_merge", 1)) {
2014-08-08 01:03:46 +02:00
rules_recruit |= RECRUIT_MERGE;
}
2010-08-08 10:06:34 +02:00
}
}
#define ENTERTAINFRACTION 20
2016-11-22 12:22:07 +01:00
int entertainmoney(const region * r)
{
double n;
if (is_cursed(r->attribs, &ct_depression)) {
2016-11-22 12:22:07 +01:00
return 0;
}
n = rmoney(r) / (double)ENTERTAINFRACTION;
if (is_cursed(r->attribs, &ct_generous)) {
n *= get_curseeffect(r->attribs, &ct_generous);
2016-11-22 12:22:07 +01:00
}
return (int)n;
}
2011-03-07 08:02:35 +01:00
int income(const unit * u)
2010-08-08 10:06:34 +02:00
{
const race *rc = u_race(u);
return rc->income * u->number;
2010-08-08 10:06:34 +02:00
}
static void scramble(void *data, unsigned int n, size_t width)
2010-08-08 10:06:34 +02:00
{
unsigned int j;
2014-08-08 01:03:46 +02:00
char temp[64];
assert(width <= sizeof(temp));
for (j = 0; j != n; ++j) {
unsigned int k = rng_uint() % n;
2014-08-08 01:03:46 +02:00
if (k == j)
continue;
memcpy(temp, (char *)data + j * width, width);
memcpy((char *)data + j * width, (char *)data + k * width, width);
memcpy((char *)data + k * width, temp, width);
}
2010-08-08 10:06:34 +02:00
}
2018-02-25 17:28:42 +01:00
int expand_production(region * r, econ_request * requests, econ_request ***results)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
unit *u;
econ_request *o;
2018-02-25 17:28:42 +01:00
int norders = 0;
2010-08-08 10:06:34 +02:00
/* Alle Units ohne production haben ein -1, alle units mit orders haben ein
2014-08-08 01:03:46 +02:00
* 0 hier stehen */
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (u = r->units; u; u = u->next)
u->n = -1;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (o = requests; o; o = o->next) {
if (o->qty > 0) {
norders += o->qty;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
if (norders > 0) {
int i = 0;
econ_request **split;
split = (econ_request **)calloc(norders, sizeof(econ_request *));
if (!split) abort();
2014-08-08 01:03:46 +02:00
for (o = requests; o; o = o->next) {
if (o->qty > 0) {
unsigned int j;
2014-08-08 01:03:46 +02:00
for (j = o->qty; j; j--) {
split[i] = o;
o->unit->n = 0;
2014-08-08 01:03:46 +02:00
i++;
}
}
}
scramble(split, norders, sizeof(econ_request *));
*results = split;
2014-08-08 01:03:46 +02:00
}
else {
*results = NULL;
2014-08-08 01:03:46 +02:00
}
return norders;
}
static void free_requests(econ_request *requests) {
2014-08-08 01:03:46 +02:00
while (requests) {
econ_request *req = requests->next;
2014-08-08 01:03:46 +02:00
free(requests);
requests = req;
2014-08-08 01:03:46 +02:00
}
}
2018-02-25 17:28:42 +01:00
static int expandorders(region * r, econ_request * requests) {
return expand_production(r, requests, &g_requests);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
typedef struct recruitment {
2014-08-08 01:03:46 +02:00
struct recruitment *next;
faction *f;
econ_request *requests;
2014-08-08 01:03:46 +02:00
int total, assigned;
2010-08-08 10:06:34 +02:00
} recruitment;
/** Creates a list of recruitment structs, one for each faction. Adds every quantifyable production
2012-07-26 10:35:09 +02:00
* to the faction's struct and to total.
*/
static recruitment *select_recruitment(econ_request ** rop,
2014-08-08 01:03:46 +02:00
int(*quantify) (const struct race *, int), int *total)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
recruitment *recruits = NULL;
while (*rop) {
recruitment *rec = recruits;
econ_request *ro = *rop;
2014-08-08 01:03:46 +02:00
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();
2014-08-08 01:03:46 +02:00
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;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
return recruits;
2010-08-08 10:06:34 +02:00
}
void add_recruits(unit * u, int number, int wanted)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region *r = u->region;
assert(number <= wanted);
if (number > 0) {
unit *unew;
char equipment[64];
2017-12-10 20:54:02 +01:00
int len;
2014-08-08 01:03:46 +02:00
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);
}
2010-08-08 10:06:34 +02:00
2017-12-10 20:54:02 +01:00
len = snprintf(equipment, sizeof(equipment), "new_%s", u_race(u)->_name);
if (len > 0 && (size_t)len < sizeof(equipment)) {
equip_unit(unew, equipment);
2017-12-10 20:54:02 +01:00
}
2014-08-08 01:03:46 +02:00
if (unew != u) {
transfermen(unew, u, unew->number);
remove_unit(&r->units, unew);
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
if (number < wanted) {
ADDMSG(&u->faction->msgs, msg_message("recruit",
"unit region amount want", u, r, number, wanted));
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
static int any_recruiters(const struct race *rc, int qty)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
return (int)(qty * 2 * rc->recruit_multi);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int do_recruiting(recruitment * recruits, int available)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
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 */
2014-08-08 01:03:46 +02:00
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
2014-08-08 01:03:46 +02:00
* 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;
}
}
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* do actual recruiting */
2011-03-07 08:02:35 +01:00
for (rec = recruits; rec != NULL; rec = rec->next) {
econ_request *req;
2014-08-08 01:03:46 +02:00
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() */
2018-02-11 15:57:31 +01:00
int number;
double multi = 2.0 * rc->recruit_multi;
2014-08-08 01:03:46 +02:00
2017-12-30 07:34:17 +01:00
number = (int)(get / multi);
if (number > req->qty) number = req->qty;
2014-08-08 01:03:46 +02:00
if (rc->recruitcost) {
int afford = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT,
number * rc->recruitcost) / rc->recruitcost;
2017-12-30 07:34:17 +01:00
if (number > afford) number = afford;
2014-08-08 01:03:46 +02:00
}
if (u->number + number > UNIT_MAXSIZE) {
ADDMSG(&u->faction->msgs, msg_feedback(u, req->type.recruit.ord, "error_unit_size",
2014-08-08 01:03:46 +02:00
"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);
2014-08-08 01:03:46 +02:00
if (number > 0) {
2018-02-11 15:57:31 +01:00
int dec = (int)(number * multi);
2014-08-08 01:03:46 +02:00
if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) {
recruited += dec;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
get -= dec;
}
2010-08-08 10:06:34 +02:00
}
}
2014-08-08 01:03:46 +02:00
return recruited;
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
void free_recruitments(recruitment * recruits)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
while (recruits) {
recruitment *rec = recruits;
recruits = rec->next;
while (rec->requests) {
econ_request *req = rec->requests;
2014-08-08 01:03:46 +02:00
rec->requests = req->next;
free(req);
}
free(rec);
}
2010-08-08 10:06:34 +02:00
}
/* Rekrutierung */
static void expandrecruit(region * r, econ_request * recruitorders)
2010-08-08 10:06:34 +02:00
{
2018-02-25 17:28:42 +01:00
recruitment *recruits;
2014-08-08 01:03:46 +02:00
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);
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
assert(recruitorders == NULL);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static int recruit_cost(const faction * f, const race * rc)
2010-08-08 10:06:34 +02:00
{
if (is_monsters(f) || valid_race(f, rc)) {
2014-08-08 01:03:46 +02:00
return rc->recruitcost;
}
return -1;
2010-08-08 10:06:34 +02:00
}
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
2019-02-09 12:50:12 +01:00
* grundsaetzlich nur alleine! */
return msg_error(u, ord, 158);
}
return NULL;
}
static void recruit(unit * u, struct order *ord, econ_request ** recruitorders)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region *r = u->region;
econ_request *o;
2014-08-08 01:03:46 +02:00
int recruitcost = -1;
const faction *f = u->faction;
const struct race *rc = u_race(u);
int n;
message *msg;
2014-08-08 01:03:46 +02:00
2017-10-09 20:33:47 +02:00
init_order_depr(ord);
n = getint();
if (n <= 0) {
syntax_error(u, ord);
return;
}
2014-08-08 01:03:46 +02:00
if (u->number == 0) {
char token[128];
2018-02-25 17:28:42 +01:00
const char *str;
str = gettoken(token, sizeof(token));
2014-08-08 01:03:46 +02:00
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);
}
}
2010-08-08 10:06:34 +02:00
}
if (recruitcost < 0) {
2014-08-08 01:03:46 +02:00
rc = u_race(u);
recruitcost = recruit_cost(f, rc);
if (recruitcost < 0) {
recruitcost = INT_MAX;
}
}
2010-08-08 10:06:34 +02:00
if (recruitcost > 0) {
int pool;
2018-02-25 17:28:42 +01:00
plane *pl = getplane(r);
if (pl && (pl->flags & PFL_NORECRUITS)) {
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_pflnorecruit", ""));
return;
}
2010-08-08 10:06:34 +02:00
pool = get_pooled(u, get_resourcetype(R_SILVER), GET_DEFAULT, recruitcost * n);
if (pool < recruitcost) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 142, MSG_EVENT);
return;
}
pool /= recruitcost;
if (n > pool) n = pool;
2010-08-08 10:06:34 +02:00
}
if (!n) {
cmistake(u, ord, 142, MSG_EVENT);
2014-08-08 01:03:46 +02:00
return;
}
if (has_skill(u, SK_ALCHEMY)) {
if (count_skill(u->faction, SK_ALCHEMY) + n > skill_limit(u->faction, SK_ALCHEMY)) {
cmistake(u, ord, 156, MSG_EVENT);
return;
}
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
assert(rc);
msg = can_recruit(u, rc, ord, turn);
if (msg) {
add_message(&u->faction->msgs, msg);
msg_release(msg);
2014-08-08 01:03:46 +02:00
return;
}
2010-08-08 10:06:34 +02:00
u_setrace(u, rc);
u->wants = n;
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
2014-08-08 01:03:46 +02:00
o->qty = n;
o->unit = u;
o->type.recruit.ord = ord;
2014-08-08 01:03:46 +02:00
addlist(recruitorders, o);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void friendly_takeover(region * r, faction * f)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region_set_owner(r, f, turn);
morale_change(r, MORALE_TRANSFER);
2010-08-08 10:06:34 +02:00
}
void give_control(unit * u, unit * u2)
2011-03-07 08:02:35 +01:00
{
2014-08-08 01:03:46 +02:00
if (u->building) {
if (u->faction != u2->faction && rule_region_owners()) {
region *r = u->region;
faction *f = region_get_owner(r);
assert(u->building == u2->building);
if (f == u->faction) {
building *b = largestbuilding(r, cmp_current_owner, false);
2014-08-08 01:03:46 +02:00
if (b == u->building) {
friendly_takeover(r, u2->faction);
}
}
}
2014-08-08 01:03:46 +02:00
building_set_owner(u2);
}
if (u->ship) {
assert(u->ship == u2->ship);
ship_set_owner(u2);
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
int give_control_cmd(unit * u, order * ord)
2010-08-08 10:06:34 +02:00
{
char token[128];
2014-08-08 01:03:46 +02:00
region *r = u->region;
unit *u2;
const char *s;
2017-10-09 20:33:47 +02:00
init_order_depr(ord);
getunit(r, u->faction, &u2);
2014-08-08 01:03:46 +02:00
s = gettoken(token, sizeof(token));
if (s && isparam(s, u->faction->locale, P_CONTROL)) {
2017-06-24 22:02:07 +02:00
bool okay = false;
if (!can_give_to(u, u2)) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "feedback_unit_not_found", ""));
2014-08-08 01:03:46 +02:00
}
else if (!u->building && !u->ship) {
2017-06-24 22:02:07 +02:00
cmistake(u, ord, 140, MSG_EVENT);
2014-08-08 01:03:46 +02:00
}
else if (u->building) {
if (u != building_owner(u->building)) {
2017-06-24 22:02:07 +02:00
cmistake(u, ord, 49, MSG_EVENT);
2014-08-08 01:03:46 +02:00
}
else if (u2->building != u->building) {
2017-06-24 22:02:07 +02:00
cmistake(u, ord, 33, MSG_EVENT);
}
else {
okay = true;
2014-08-08 01:03:46 +02:00
}
}
else if (u->ship) {
if (u != ship_owner(u->ship)) {
2017-06-24 22:02:07 +02:00
cmistake(u, ord, 49, MSG_EVENT);
2014-08-08 01:03:46 +02:00
}
else if (u2->ship != u->ship) {
2017-06-24 22:02:07 +02:00
cmistake(u, ord, 32, MSG_EVENT);
}
else {
okay = true;
2014-08-08 01:03:46 +02:00
}
}
2017-06-24 22:02:07 +02:00
if (okay) {
message *msg = msg_message("givecommand", "unit recipient", u, u2);
2014-08-08 01:03:46 +02:00
add_message(&u->faction->msgs, msg);
if (u->faction != u2->faction) {
add_message(&u2->faction->msgs, msg);
}
msg_release(msg);
2017-06-24 22:02:07 +02:00
give_control(u, u2);
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
return 0;
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
static int forget_cmd(unit * u, order * ord)
{
char token[128];
2014-08-08 01:03:46 +02:00
skill_t sk;
const char *s;
if (is_cursed(u->attribs, &ct_slavery)) {
2014-08-08 01:03:46 +02:00
/* charmed units shouldn't be losing their skills */
return 0;
}
2017-10-09 20:33:47 +02:00
init_order_depr(ord);
s = gettoken(token, sizeof(token));
2014-08-08 01:03:46 +02:00
sk = get_skill(s, u->faction->locale);
if (sk != NOSKILL) {
2017-08-31 21:19:25 +02:00
if (sk == SK_MAGIC && is_familiar(u)) {
/* some units cannot forget their innate magical abilities */
return 0;
}
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_message("forget", "unit skill", u, sk));
set_level(u, sk, 0);
}
return 0;
2010-08-08 10:06:34 +02:00
}
static int maintain(building * b)
2010-08-08 10:06:34 +02:00
{
int c;
region *r = b->region;
bool paid = true;
unit *u;
if (fval(b, BLD_MAINTAINED) || b->type == NULL || b->type->maintenance == NULL) {
return BLD_MAINTAINED;
2014-08-08 01:03:46 +02:00
}
if (fval(b, BLD_DONTPAY)) {
return 0;
2014-08-08 01:03:46 +02:00
}
u = building_owner(b);
if (u == NULL) {
/* no owner - send a message to the entire region */
ADDMSG(&r->msgs, msg_message("maintenance_noowner", "building", b));
return 0;
}
/* If the owner is the region owner, check if dontpay flag is set for the building where he is in */
if (config_token("rules.region_owner_pay_building", b->type->_name)) {
if (fval(u->building, BLD_DONTPAY)) {
return 0;
}
}
for (c = 0; b->type->maintenance[c].number && paid; ++c) {
const maintenance *m = b->type->maintenance + c;
int need = m->number;
if (fval(m, MTF_VARIABLE))
need = need * b->size;
need -= get_pooled(u, m->rtype, GET_DEFAULT, need);
if (need > 0) {
paid = false;
2010-08-08 10:06:34 +02:00
}
}
if (fval(b, BLD_DONTPAY)) {
ADDMSG(&r->msgs, msg_message("maintenance_nowork", "building", b));
return 0;
2010-08-08 10:06:34 +02:00
}
if (!paid) {
ADDMSG(&u->faction->msgs, msg_message("maintenancefail", "unit building", u, b));
ADDMSG(&r->msgs, msg_message("maintenance_nowork", "building", b));
return 0;
}
2011-03-07 08:02:35 +01:00
for (c = 0; b->type->maintenance[c].number; ++c) {
2014-08-08 01:03:46 +02:00
const maintenance *m = b->type->maintenance + c;
int cost = m->number;
2014-08-08 01:03:46 +02:00
if (fval(m, MTF_VARIABLE)) {
cost = cost * b->size;
}
cost -=
use_pooled(u, m->rtype, GET_SLACK | GET_RESERVE | GET_POOLED_SLACK,
cost);
assert(cost == 0);
2014-08-08 01:03:46 +02:00
}
ADDMSG(&u->faction->msgs, msg_message("maintenance", "unit building", u, b));
return BLD_MAINTAINED;
2010-08-08 10:06:34 +02:00
}
void maintain_buildings(region * r)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
building **bp = &r->buildings;
while (*bp) {
building *b = *bp;
int flags = BLD_MAINTAINED;
if (!curse_active(get_curse(b->attribs, &ct_nocostbuilding))) {
flags = maintain(b);
}
fset(b, flags);
2014-08-08 01:03:46 +02:00
bp = &b->next;
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
void economics(region * r)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
unit *u;
econ_request *recruitorders = NULL;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* Geben vor Selbstmord (doquit)! Hier alle unmittelbaren Befehle.
* Rekrutieren vor allen Einnahmequellen. Bewachen JA vor Steuern
* eintreiben. */
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (u = r->units; u; u = u->next) {
order *ord;
if (u->number > 0) {
for (ord = u->orders; ord; ord = ord->next) {
keyword_t kwd = getkeyword(ord);
if (kwd == K_GIVE) {
give_cmd(u, ord);
}
else if (kwd == K_FORGET) {
forget_cmd(u, ord);
}
if (u->orders == NULL) {
break;
}
}
2011-03-08 08:44:20 +01:00
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
/* RECRUIT orders */
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (rules_recruit < 0)
recruit_init();
for (u = r->units; u; u = u->next) {
order *ord;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
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;
}
}
2010-08-08 10:06:34 +02:00
}
}
if (recruitorders) {
2014-08-08 01:03:46 +02:00
expandrecruit(r, recruitorders);
}
2014-08-08 01:03:46 +02:00
remove_empty_units_in_region(r);
for (u = r->units; u; u = u->next) {
order *ord = u->thisorder;
keyword_t kwd = getkeyword(ord);
if (kwd == K_DESTROY) {
if (destroy_cmd(u, ord) == 0) {
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
2014-08-08 01:03:46 +02:00
}
}
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void mod_skill(const resource_mod *mod, skill_t sk, int *value) {
skill_t msk;
assert(mod->type == RMT_PROD_SKILL);
msk = (skill_t)mod->value.sa[0];
if (msk == NOSKILL || msk == sk) {
*value += mod->value.sa[1];
}
}
static struct message * get_modifiers(unit *u, skill_t sk, const resource_type *rtype, variant *savep, int *skillp) {
struct building *b = inside_building(u);
const struct building_type *btype = building_is_active(b) ? b->type : NULL;
int save_n = 1, save_d = 1;
int skill = 0;
int need_race = 0, need_bldg = 0;
resource_mod *mod;
const struct building_type *btype_needed = NULL;
if (btype && btype->modifiers) {
for (mod = btype->modifiers; mod && mod->type != RMT_END; ++mod) {
if (mod->type == RMT_PROD_SKILL) {
mod_skill(mod, sk, &skill);
}
}
}
for (mod = rtype->modifiers; mod && mod->type != RMT_END; ++mod) {
if (mod->btype == NULL || mod->btype == btype) {
const race * rc = u_race(u);
if (mod->race_mask == 0 || (mod->race_mask & rc->mask_item)) {
switch (mod->type) {
case RMT_PROD_SAVE:
if (savep) {
save_n *= mod->value.sa[0];
save_d *= mod->value.sa[1];
}
break;
case RMT_PROD_SKILL:
mod_skill(mod, sk, &skill);
break;
case RMT_PROD_REQUIRE:
if (mod->race_mask) need_race |= 1;
if (mod->btype) {
need_bldg |= 1;
}
break;
default:
/* is not a production modifier, ignore it */
break;
}
}
}
if (mod->type == RMT_PROD_REQUIRE) {
if (mod->race_mask) need_race |= 2;
if (mod->btype) {
btype_needed = mod->btype;
need_bldg |= 2;
}
}
}
if (need_race == 2) {
return msg_error(u, u->thisorder, 117);
}
if (btype_needed && need_bldg == 2) {
return msg_feedback(u, u->thisorder, "building_needed", "building", btype_needed->_name);
}
*skillp = skill;
if (savep) *savep = frac_make(save_n, save_d);
return NULL;
}
2010-08-08 10:06:34 +02:00
2011-03-07 08:02:35 +01:00
static void manufacture(unit * u, const item_type * itype, int want)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int n;
int minskill = itype->construction->minskill;
skill_t sk = itype->construction->skill;
message *msg;
int skill_mod;
2010-08-08 10:06:34 +02:00
msg = get_modifiers(u, sk, itype->rtype, NULL, &skill_mod);
if (msg) {
ADDMSG(&u->faction->msgs, msg);
2014-08-08 01:03:46 +02:00
return;
}
if (want == 0) {
want = maxbuild(u, itype->construction);
}
n = build(u, itype->construction, 0, want, skill_mod);
2014-08-08 01:03:46 +02:00
switch (n) {
case ENEEDSKILL:
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "skill_needed", "skill", sk));
return;
case ELOWSKILL:
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "manufacture_skills",
"skill minskill product", sk, minskill, itype->rtype, 1));
2014-08-08 01:03:46 +02:00
return;
case ENOMATERIALS:
ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder,
itype->construction, want));
return;
}
if (n > 0) {
i_change(&u->items, itype, n);
if (want == INT_MAX)
want = n;
ADDMSG(&u->faction->msgs, msg_message("produce",
2014-08-08 01:03:46 +02:00
"unit region amount wanted resource", u, u->region, n, want,
itype->rtype));
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder, "error_cannotmake",
""));
}
2010-08-08 10:06:34 +02:00
}
typedef struct allocation {
2014-08-08 01:03:46 +02:00
struct allocation *next;
int want, get;
variant save;
2014-08-08 01:03:46 +02:00
unsigned int flags;
unit *unit;
2010-08-08 10:06:34 +02:00
} allocation;
#define new_allocation() (allocation *)calloc(1, sizeof(allocation))
2010-08-08 10:06:34 +02:00
#define free_allocation(a) free(a)
typedef struct allocation_list {
2014-08-08 01:03:46 +02:00
struct allocation_list *next;
allocation *data;
const resource_type *type;
2010-08-08 10:06:34 +02:00
} allocation_list;
2011-03-07 08:02:35 +01:00
static allocation_list *allocations;
2010-08-08 10:06:34 +02:00
enum {
2014-08-08 01:03:46 +02:00
AFL_DONE = 1 << 0,
AFL_LOWSKILL = 1 << 1
2010-08-08 10:06:34 +02:00
};
2011-03-07 08:02:35 +01:00
static void allocate_resource(unit * u, const resource_type * rtype, int want)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
const item_type *itype = resource2item(rtype);
region *r = u->region;
int dm = 0;
allocation_list *alist;
allocation *al;
const resource_type *rring;
2017-02-16 19:38:09 +01:00
int amount, skill, skill_mod = 0;
variant save_mod;
skill_t sk;
2010-08-08 10:06:34 +02:00
2019-02-09 12:50:12 +01:00
/* momentan kann man keine ressourcen abbauen, wenn man dafuer
2014-08-08 01:03:46 +02:00
* Materialverbrauch hat: */
assert(itype != NULL && (itype->construction == NULL
|| itype->construction->materials == NULL));
sk = itype->construction->skill;
if (!rtype->raw) {
int avail = limit_resource(r, rtype);
2014-08-08 01:03:46 +02:00
if (avail <= 0) {
cmistake(u, u->thisorder, 121, MSG_PRODUCE);
return;
}
}
2011-03-07 08:02:35 +01:00
if (rtype->modifiers) {
message *msg = get_modifiers(u, sk, rtype, &save_mod, &skill_mod);
if (msg) {
ADDMSG(&u->faction->msgs, msg);
return;
2014-08-08 01:03:46 +02:00
}
}
else {
save_mod.sa[0] = 1;
save_mod.sa[1] = 1;
}
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Bergwaechter koennen Abbau von Eisen/Laen durch Bewachen verhindern.
* Als magische Wesen 'sehen' Bergwaechter alles und werden durch
2014-08-08 01:03:46 +02:00
* Belagerung nicht aufgehalten. (Ansonsten wie oben bei Elfen anpassen).
*/
if (itype->rtype && (itype->rtype == get_resourcetype(R_IRON) || itype->rtype == rt_find("laen"))) {
unit *u2;
for (u2 = r->units; u2; u2 = u2->next) {
if (is_guard(u)
2014-08-08 01:03:46 +02:00
&& !fval(u2, UFL_ISNEW)
&& u2->number && !alliedunit(u2, u->faction, HELP_GUARD)) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "region_guarded", "guard", u2));
return;
}
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2018-02-11 15:57:31 +01:00
assert(sk != NOSKILL || !"limited resource needs a required skill for making it");
skill = effskill(u, sk, NULL);
2014-08-08 01:03:46 +02:00
if (skill == 0) {
add_message(&u->faction->msgs,
msg_feedback(u, u->thisorder, "skill_needed", "skill", sk));
2010-08-08 10:06:34 +02:00
return;
}
2014-08-08 01:03:46 +02:00
if (skill < itype->construction->minskill) {
add_message(&u->faction->msgs,
msg_feedback(u, u->thisorder, "manufacture_skills",
"skill minskill product", sk, itype->construction->minskill,
itype->rtype));
2014-08-08 01:03:46 +02:00
return;
}
skill += skill_mod;
2014-08-08 01:03:46 +02:00
amount = skill * u->number;
/* nun ist amount die Gesamtproduktion der Einheit (in punkten) */
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* mit Flinkfingerring verzehnfacht sich die Produktion */
rring = get_resourcetype(R_RING_OF_NIMBLEFINGER);
if (rring) {
int more = i_get(u->items, rring->itype);
if (more > u->number) more = u->number;
amount += skill * more * (roqf_factor() - 1);
}
2011-03-07 08:02:35 +01:00
/* Schaffenstrunk: */
if ((dm = get_effect(u, oldpotiontype[P_DOMORE])) != 0) {
2017-12-30 07:34:17 +01:00
if (dm > u->number) dm = u->number;
change_effect(u, oldpotiontype[P_DOMORE], -dm);
amount += dm * skill; /* dm Personen produzieren doppelt */
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
amount /= itype->construction->minskill;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* Limitierung durch Parameter m. */
if (want > 0 && want < amount)
amount = want;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
alist = allocations;
while (alist && alist->type != rtype)
alist = alist->next;
if (!alist) {
alist = calloc(1, sizeof(struct allocation_list));
2018-12-15 20:01:51 +01:00
if (!alist) abort();
2014-08-08 01:03:46 +02:00
alist->next = allocations;
alist->type = rtype;
allocations = alist;
}
al = new_allocation();
if (!al) abort();
2014-08-08 01:03:46 +02:00
al->want = amount;
al->save = save_mod;
2014-08-08 01:03:46 +02:00
al->next = alist->data;
al->unit = u;
alist->data = al;
2010-08-08 10:06:34 +02:00
}
static int required(int want, variant save)
2010-08-08 10:06:34 +02:00
{
int req = (int)(want * save.sa[0] / save.sa[1]);
int r = want * save.sa[0] % save.sa[1];
2017-11-05 19:15:43 +01:00
if (r > 0) ++req;
2017-01-10 18:07:36 +01:00
return req;
2010-08-08 10:06:34 +02:00
}
static void
leveled_allocation(const resource_type * rtype, region * r, allocation * alist)
{
2014-08-08 01:03:46 +02:00
const item_type *itype = resource2item(rtype);
rawmaterial *rm = rm_get(r, rtype);
if (rm != NULL) {
2018-02-11 15:57:31 +01:00
int need;
2018-02-25 17:28:42 +01:00
bool first = true;
2014-08-08 01:03:46 +02:00
do {
2018-02-11 15:57:31 +01:00
int avail = rm->amount, nreq = 0;
2014-08-08 01:03:46 +02:00
allocation *al;
if (avail <= 0) {
for (al = alist; al; al = al->next) {
al->get = 0;
}
break;
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
assert(avail > 0);
2018-02-11 15:57:31 +01:00
for (al = alist; al; al = al->next) {
2014-08-08 01:03:46 +02:00
if (!fval(al, AFL_DONE)) {
int req = required(al->want - al->get, al->save);
assert(al->get <= al->want && al->get >= 0);
if (effskill(al->unit, itype->construction->skill, NULL)
>= rm->level + itype->construction->minskill - 1) {
if (req) {
2017-01-10 18:07:36 +01:00
nreq += req;
}
else {
fset(al, AFL_DONE);
}
2014-08-08 01:03:46 +02:00
}
else {
fset(al, AFL_DONE);
if (first)
fset(al, AFL_LOWSKILL);
2014-08-08 01:03:46 +02:00
}
}
2018-02-11 15:57:31 +01:00
}
2017-01-10 18:07:36 +01:00
need = nreq;
2014-08-08 01:03:46 +02:00
2017-12-30 07:34:17 +01:00
if (avail > nreq) avail = nreq;
2014-08-08 01:03:46 +02:00
if (need > 0) {
int use = 0;
for (al = alist; al; al = al->next) {
2014-08-08 01:03:46 +02:00
if (!fval(al, AFL_DONE)) {
if (avail > 0) {
int want = required(al->want - al->get, al->save);
2017-01-10 18:07:36 +01:00
int x = avail * want / nreq;
int req = (avail * want) % nreq;
/* Wenn Rest, dann wuerfeln, ob ich etwas bekomme: */
if (req > 0 && rng_int() % nreq < req) ++x;
avail -= x;
use += x;
2017-01-10 18:07:36 +01:00
nreq -= want;
need -= x;
2017-12-30 07:34:17 +01:00
al->get = al->get + x * al->save.sa[1] / al->save.sa[0];
if (al->get > al->want) al->get = al->want;
}
2014-08-08 01:03:46 +02:00
}
}
2014-08-08 01:03:46 +02:00
if (use) {
rawmaterial_type *raw = rmt_get(rm->rtype);
if (raw && raw->use) {
assert(use <= rm->amount);
raw->use(rm, r, use);
}
2014-08-08 01:03:46 +02:00
}
2017-01-10 18:07:36 +01:00
assert(avail == 0 || nreq == 0);
2014-08-08 01:03:46 +02:00
}
first = false;
} while (need > 0);
}
2010-08-08 10:06:34 +02:00
}
static void
attrib_allocation(const resource_type * rtype, region * r, allocation * alist)
{
2014-08-08 01:03:46 +02:00
allocation *al;
2017-01-10 18:07:36 +01:00
int nreq = 0;
int avail = INT_MAX;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (al = alist; al; al = al->next) {
2017-01-10 18:07:36 +01:00
nreq += required(al->want, al->save);
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
if (!rtype->raw) {
avail = limit_resource(r, rtype);
if (avail < 0) {
2014-08-08 01:03:46 +02:00
avail = 0;
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
2017-12-30 07:34:17 +01:00
if (avail > nreq) avail = nreq;
2014-08-08 01:03:46 +02:00
for (al = alist; al; al = al->next) {
if (avail > 0) {
int want = required(al->want, al->save);
2017-01-10 18:07:36 +01:00
int x = avail * want / nreq;
int rx = (avail * want) % nreq;
/* Wenn Rest, dann wuerfeln, ob ich was bekomme: */
2017-11-05 19:15:43 +01:00
if (rx > 0 && rng_int() % nreq < rx) ++x;
2014-08-08 01:03:46 +02:00
avail -= x;
2017-01-10 18:07:36 +01:00
nreq -= want;
al->get = x * al->save.sa[1] / al->save.sa[0];
2017-12-30 07:34:17 +01:00
if (al->want < al->get) al->get = al->want;
if (!rtype->raw) {
2014-08-08 01:03:46 +02:00
int use = required(al->get, al->save);
if (use) {
produce_resource(r, rtype, use);
}
2014-08-08 01:03:46 +02:00
}
}
}
2017-01-10 18:07:36 +01:00
assert(avail == 0 || nreq == 0);
2010-08-08 10:06:34 +02:00
}
static void allocate(const resource_type *rtype, region *r, allocation *data) {
if (rtype->raw) {
leveled_allocation(rtype, r, data);
}
else {
attrib_allocation(rtype, r, data);
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
void split_allocations(region * r)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
allocation_list **p_alist = &allocations;
while (*p_alist) {
allocation_list *alist = *p_alist;
const resource_type *rtype = alist->type;
const item_type *itype = resource2item(rtype);
allocation **p_al = &alist->data;
allocate(rtype, r, alist->data);
2014-08-08 01:03:46 +02:00
while (*p_al) {
allocation *al = *p_al;
if (al->get) {
assert(itype || !"not implemented for non-items");
i_change(&al->unit->items, itype, al->get);
produceexp(al->unit, itype->construction->skill, al->unit->number);
}
if (al->want == INT_MAX)
al->want = al->get;
ADDMSG(&al->unit->faction->msgs, msg_message("produce",
"unit region amount wanted resource",
al->unit, al->unit->region, al->get, al->want, rtype));
*p_al = al->next;
free_allocation(al);
}
*p_alist = alist->next;
free(alist);
}
allocations = NULL;
2010-08-08 10:06:34 +02:00
}
static void create_potion(unit * u, const item_type * itype, int want)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int built;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (want == 0) {
want = maxbuild(u, itype->construction);
2014-08-08 01:03:46 +02:00
}
built = build(u, itype->construction, 0, want, 0);
2014-08-08 01:03:46 +02:00
switch (built) {
case ELOWSKILL:
case ENEEDSKILL:
/* no skill, or not enough skill points to build */
cmistake(u, u->thisorder, 50, MSG_PRODUCE);
break;
case ECOMPLETE:
assert(0);
break;
case ENOMATERIALS:
/* something missing from the list of materials */
ADDMSG(&u->faction->msgs, msg_materials_required(u, u->thisorder,
itype->construction, want));
2014-08-08 01:03:46 +02:00
break;
default:
i_change(&u->items, itype, built);
2014-08-08 01:03:46 +02:00
if (want == INT_MAX)
want = built;
ADDMSG(&u->faction->msgs, msg_message("produce",
2014-08-08 01:03:46 +02:00
"unit region amount wanted resource", u, u->region, built, want,
itype->rtype));
2014-08-08 01:03:46 +02:00
break;
}
2010-08-08 10:06:34 +02:00
}
2017-02-15 17:09:23 +01:00
void make_item(unit * u, const item_type * itype, int want)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
if (itype->construction && fval(itype->rtype, RTF_LIMITED)) {
if (is_guarded(u->region, u)) {
2014-08-08 01:03:46 +02:00
cmistake(u, u->thisorder, 70, MSG_EVENT);
return;
}
allocate_resource(u, itype->rtype, want);
}
2010-08-08 10:06:34 +02:00
else {
if (itype->flags & ITF_POTION) {
create_potion(u, itype, want);
}
else if (itype->construction && itype->construction->materials) {
2014-08-08 01:03:46 +02:00
manufacture(u, itype, want);
}
2014-08-08 01:03:46 +02:00
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
"error_cannotmake", ""));
}
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
int make_cmd(unit * u, struct order *ord)
2010-08-08 10:06:34 +02:00
{
char token[32];
2014-08-08 01:03:46 +02:00
region *r = u->region;
2014-11-16 12:46:55 +01:00
const building_type *btype = 0;
const ship_type *stype = 0;
const item_type *itype = 0;
param_t p = NOPARAM;
int m = INT_MAX;
2014-08-08 01:03:46 +02:00
const char *s;
const struct locale *lang = u->faction->locale;
keyword_t kwd;
2014-08-08 01:03:46 +02:00
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_MAKE);
s = gettoken(token, sizeof(token));
2010-08-08 10:06:34 +02:00
2014-11-16 12:46:55 +01:00
if (s) {
2018-02-25 17:28:42 +01:00
char ibuf[16];
m = atoip(s);
2014-11-16 12:46:55 +01:00
sprintf(ibuf, "%d", m);
if (!strcmp(ibuf, (const char *)s)) {
/* a quantity was given */
s = gettoken(token, sizeof(token));
}
else {
2014-11-16 12:46:55 +01:00
m = INT_MAX;
}
if (s) {
p = findparam(s, u->faction->locale);
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
if (p == P_ROAD) {
plane *pl = rplane(r);
if (pl && fval(pl, PFL_NOBUILD)) {
cmistake(u, ord, 275, MSG_PRODUCE);
}
else {
s = gettoken(token, sizeof(token));
direction_t d = s ? get_direction(s, u->faction->locale) : NODIRECTION;
2014-08-08 01:03:46 +02:00
if (d != NODIRECTION) {
2015-08-27 16:59:39 +02:00
build_road(u, m, d);
2014-08-08 01:03:46 +02:00
}
else {
/* Die Richtung wurde nicht erkannt */
cmistake(u, ord, 71, MSG_PRODUCE);
}
}
return 0;
}
else if (p == P_SHIP) {
plane *pl = rplane(r);
if (pl && fval(pl, PFL_NOBUILD)) {
cmistake(u, ord, 276, MSG_PRODUCE);
}
else {
2015-08-27 16:59:39 +02:00
continue_ship(u, m);
2014-08-08 01:03:46 +02:00
}
return 0;
}
else if (p == P_HERBS) {
herbsearch(u, m);
2014-08-08 01:03:46 +02:00
return 0;
}
/* since the string can match several objects, like in 'academy' and
* 'academy of arts', we need to figure out what the player meant.
* This is not 100% safe.
*/
2014-11-16 12:46:55 +01:00
if (s) {
stype = findshiptype(s, lang);
btype = findbuildingtype(s, lang);
itype = finditemtype(s, lang);
}
2014-08-08 01:03:46 +02:00
if (itype != NULL && (btype != NULL || stype != NULL)) {
if (itype->construction == NULL) {
/* if the item cannot be made, we probably didn't mean to make it */
itype = NULL;
}
else if (stype != NULL) {
const char *sname = LOC(lang, stype->_name);
const char *iname = LOC(lang, resourcename(itype->rtype, 0));
if (strlen(iname) < strlen(sname))
stype = NULL;
else
itype = NULL;
}
else {
const char *bname = LOC(lang, btype->_name);
const char *iname = LOC(lang, resourcename(itype->rtype, 0));
if (strlen(iname) < strlen(bname))
btype = NULL;
else
itype = NULL;
}
}
if (btype != NULL && stype != NULL) {
const char *bname = LOC(lang, btype->_name);
const char *sname = LOC(lang, stype->_name);
if (strlen(sname) < strlen(bname))
btype = NULL;
else
stype = NULL;
}
if (stype != NOSHIP) {
plane *pl = rplane(r);
if (pl && fval(pl, PFL_NOBUILD)) {
cmistake(u, ord, 276, MSG_PRODUCE);
}
else {
2015-08-27 16:59:39 +02:00
create_ship(u, stype, m, ord);
2014-08-08 01:03:46 +02:00
}
}
else if (btype != NOBUILDING) {
plane *pl = rplane(r);
if (pl && fval(pl, PFL_NOBUILD)) {
cmistake(u, ord, 275, MSG_PRODUCE);
2014-08-08 01:03:46 +02:00
}
else if (btype->stages && btype->stages->construction) {
2014-08-08 01:03:46 +02:00
int id = getid();
build_building(u, btype, id, m, ord);
}
else {
cmistake(u, ord, 275, MSG_PRODUCE);
}
2014-08-08 01:03:46 +02:00
}
else if (itype != NULL) {
2017-02-15 17:09:23 +01:00
make_item(u, itype, m);
2014-08-08 01:03:46 +02:00
}
else {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", ""));
}
return 0;
}
2011-03-07 08:02:35 +01:00
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
static void free_luxuries(variant *var)
2010-08-08 10:06:34 +02:00
{
item *itm = (item *)var->v;
var->v = NULL;
2014-08-08 01:03:46 +02:00
i_freeall(&itm);
2010-08-08 10:06:34 +02:00
}
const attrib_type at_luxuries = {
2014-08-08 01:03:46 +02:00
"luxuries", NULL, free_luxuries, NULL, NULL, NULL
2010-08-08 10:06:34 +02:00
};
static void expandbuying(region * r, econ_request * buyorders)
2010-08-08 10:06:34 +02:00
{
const resource_type *rsilver = get_resourcetype(R_SILVER);
int max_products;
unit *u;
2017-11-05 15:11:02 +01:00
struct trade {
const luxury_type *type;
int number;
int multi;
} trades[MAXLUXURIES], *trade;
2017-11-05 15:11:02 +01:00
int ntrades = 0;
const luxury_type *ltype;
2011-03-07 08:02:35 +01:00
2017-11-05 15:11:02 +01:00
for (ntrades = 0, ltype = luxurytypes; ltype; ltype = ltype->next) {
assert(ntrades < MAXLUXURIES);
trades[ntrades].number = 0;
trades[ntrades].multi = 1;
trades[ntrades++].type = ltype;
2014-08-08 01:03:46 +02:00
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (!buyorders)
return;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* Initialisation. multiplier ist der Multiplikator auf den
2019-02-09 12:50:12 +01:00
* Verkaufspreis. Fuer max_products Produkte kauft man das Produkt zum
* einfachen Verkaufspreis, danach erhoeht sich der Multiplikator um 1.
* counter ist ein Zaehler, der die gekauften Produkte zaehlt. money
* wird fuer die debug message gebraucht. */
2014-08-08 01:03:46 +02:00
max_products = rpeasants(r) / TRADE_FRACTION;
2019-02-09 12:50:12 +01:00
/* Kauf - auch so programmiert, dass er leicht erweiterbar auf mehrere
* Gueter pro Monat ist. j sind die Befehle, i der Index des
2014-08-08 01:03:46 +02:00
* gehandelten Produktes. */
if (max_products > 0) {
unsigned int norders = expandorders(r, buyorders);
if (norders) {
unsigned int j;
for (j = 0; j != norders; j++) {
int price, multi;
ltype = g_requests[j]->type.trade.ltype;
trade = trades;
while (trade->type && trade->type != ltype)
++trade;
multi = trade->multi;
price = ltype->price * multi;
if (get_pooled(g_requests[j]->unit, rsilver, GET_DEFAULT,
price) >= price) {
item *items;
2019-02-09 12:50:12 +01:00
/* litems zaehlt die Gueter, die verkauft wurden, u->n das Geld, das
* verdient wurde. Dies muss gemacht werden, weil der Preis staendig sinkt,
* man sich also das verdiente Geld und die verkauften Produkte separat
2019-02-09 12:50:12 +01:00
* merken muss. */
attrib *a;
u = g_requests[j]->unit;
a = a_find(u->attribs, &at_luxuries);
if (a == NULL) {
a = a_add(&u->attribs, a_new(&at_luxuries));
}
items = a->data.v;
i_change(&items, ltype->itype, 1);
a->data.v = items;
i_change(&g_requests[j]->unit->items, ltype->itype, 1);
use_pooled(u, rsilver, GET_DEFAULT, price);
if (u->n < 0)
u->n = 0;
u->n += price;
rsetmoney(r, rmoney(r) + price);
/* Falls mehr als max_products Bauern ein Produkt verkauft haben, steigt
2019-02-09 12:50:12 +01:00
* der Preis Multiplikator fuer das Produkt um den Faktor 1. Der Zaehler
* wird wieder auf 0 gesetzt. */
if (++trade->number == max_products) {
trade->number = 0;
++trade->multi;
}
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
2014-08-08 01:03:46 +02:00
}
}
2011-03-07 08:02:35 +01:00
}
free(g_requests);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* Ausgabe an Einheiten */
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
for (u = r->units; u; u = u->next) {
attrib *a = a_find(u->attribs, &at_luxuries);
item *itm;
if (a == NULL)
continue;
ADDMSG(&u->faction->msgs, msg_message("buy", "unit money", u, u->n));
for (itm = (item *)a->data.v; itm; itm = itm->next) {
if (itm->number) {
ADDMSG(&u->faction->msgs, msg_message("buyamount",
"unit amount resource", u, itm->number, itm->type->rtype));
}
}
a_remove(&u->attribs, a);
}
2010-08-08 10:06:34 +02:00
}
}
attrib_type at_trades = {
2014-08-08 01:03:46 +02:00
"trades",
DEFAULT_INIT,
DEFAULT_FINALIZE,
DEFAULT_AGE,
NO_WRITE,
NO_READ
2010-08-08 10:06:34 +02:00
};
static bool trade_needs_castle(const region *r, const race *rc) {
static int rc_change, terrain_change;
static const race *rc_insect;
static const terrain_type *t_desert, *t_swamp;
if (rc_changed(&rc_change)) {
rc_insect = get_race(RC_INSECT);
}
if (terrain_changed(&terrain_change)) {
t_swamp = newterrain(T_SWAMP);
t_desert = newterrain(T_DESERT);
}
return rc != rc_insect && (r->terrain == t_swamp || r->terrain == t_desert);
}
static void buy(unit * u, econ_request ** buyorders, struct order *ord)
2010-08-08 10:06:34 +02:00
{
char token[128];
2014-08-08 01:03:46 +02:00
region *r = u->region;
int n, k;
econ_request *o;
2014-08-08 01:03:46 +02:00
attrib *a;
const item_type *itype = NULL;
const luxury_type *ltype = NULL;
keyword_t kwd;
const char *s;
if (u->ship && is_guarded(r, u)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 69, MSG_INCOME);
return;
}
if (u->ship && is_guarded(r, u)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 69, MSG_INCOME);
return;
}
/* Im Augenblick kann man nur 1 Produkt kaufen. expandbuying ist aber
2019-02-09 12:50:12 +01:00
* schon dafuer ausgeruestet, mehrere Produkte zu kaufen. */
2010-08-08 10:06:34 +02:00
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_BUY);
n = getint();
if (n <= 0) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 26, MSG_COMMERCE);
return;
}
2017-11-05 19:15:43 +01:00
/* Entweder man ist Insekt in Sumpf/Wueste, oder es muss
* einen Handelsposten in der Region geben: */
if (trade_needs_castle(r, u_race(u))) {
2017-11-05 19:15:43 +01:00
building *b = NULL;
2014-08-08 01:03:46 +02:00
if (r->buildings) {
2016-09-19 04:15:12 +02:00
static int cache;
static const struct building_type *bt_castle;
if (bt_changed(&cache)) {
bt_castle = bt_find("castle");
}
2014-08-08 01:03:46 +02:00
for (b = r->buildings; b; b = b->next) {
if (b->type == bt_castle && b->size >= 2) {
break;
}
}
}
if (b == NULL) {
cmistake(u, ord, 119, MSG_COMMERCE);
return;
}
}
2010-08-08 10:06:34 +02:00
2019-02-09 12:50:12 +01:00
/* Ein Haendler kann nur 10 Gueter pro Talentpunkt handeln. */
k = u->number * 10 * effskill(u, SK_TRADE, NULL);
2010-08-08 10:06:34 +02:00
2019-02-09 12:50:12 +01:00
/* hat der Haendler bereits gehandelt, muss die Menge der bereits
* verkauften/gekauften Gueter abgezogen werden */
2014-08-08 01:03:46 +02:00
a = a_find(u->attribs, &at_trades);
if (!a) {
a = a_add(&u->attribs, a_new(&at_trades));
}
else {
k -= a->data.i;
}
2010-08-08 10:06:34 +02:00
2017-12-30 07:34:17 +01:00
if (n > k) n = k;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (!n) {
cmistake(u, ord, 102, MSG_COMMERCE);
return;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
assert(n >= 0);
2019-02-09 12:50:12 +01:00
/* die Menge der verkauften Gueter merken */
2014-08-08 01:03:46 +02:00
a->data.i += n;
2010-08-08 10:06:34 +02:00
s = gettoken(token, sizeof(token));
itype = s ? finditemtype(s, u->faction->locale) : 0;
2014-08-08 01:03:46 +02:00
if (itype != NULL) {
ltype = resource2luxury(itype->rtype);
if (ltype == NULL) {
cmistake(u, ord, 124, MSG_COMMERCE);
return;
}
2010-08-08 10:06:34 +02:00
}
if (!r->land || r_demand(r, ltype)) {
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "luxury_notsold", ""));
return;
}
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->type.trade.ltype = ltype; /* sollte immer gleich sein */
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
o->unit = u;
o->qty = n;
addlist(buyorders, o);
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
void add_income(unit * u, income_t type, int want, int qty)
{
if (want == INT_MAX)
want = qty;
ADDMSG(&u->faction->msgs, msg_message("income",
"unit region mode wanted amount", u, u->region, (int)type, want, qty));
}
2010-08-08 10:06:34 +02:00
2019-02-09 12:50:12 +01:00
/* Steuersaetze in % bei Burggroesse */
2011-03-07 08:02:35 +01:00
static int tax_per_size[7] = { 0, 6, 12, 18, 24, 30, 36 };
2010-08-08 10:06:34 +02:00
static void expandselling(region * r, econ_request * sellorders, int limit)
2010-08-08 10:06:34 +02:00
{
2018-02-25 17:28:42 +01:00
int money, max_products;
int norders;
2014-08-08 01:03:46 +02:00
/* int m, n = 0; */
int maxsize = 0, maxeffsize = 0;
int taxcollected = 0;
int hafencollected = 0;
unit *maxowner = (unit *)NULL;
building *maxb = (building *)NULL;
building *b;
unit *u;
unit *hafenowner;
static int counter[MAXLUXURIES];
2014-08-08 01:03:46 +02:00
static int ncounter = 0;
2016-09-19 04:15:12 +02:00
static int bt_cache;
static const struct building_type *castle_bt, *harbour_bt, *caravan_bt;
2011-03-07 08:02:35 +01:00
assert(r->land);
2016-09-19 04:15:12 +02:00
if (bt_changed(&bt_cache)) {
castle_bt = bt_find("castle");
harbour_bt = bt_find("harbour");
caravan_bt = bt_find("caravan");
}
2014-08-08 01:03:46 +02:00
if (ncounter == 0) {
const luxury_type *ltype;
for (ltype = luxurytypes; ltype; ltype = ltype->next) {
assert(ncounter < MAXLUXURIES);
2014-08-08 01:03:46 +02:00
++ncounter;
}
2010-08-08 10:06:34 +02:00
}
memset(counter, 0, sizeof(int) * ncounter);
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
if (!sellorders) { /* NEIN, denn Insekten koennen in || !r->buildings) */
return; /* Suempfen und Wuesten auch so handeln */
2014-08-08 01:03:46 +02:00
}
2019-02-09 12:50:12 +01:00
/* Stelle Eigentuemer der groessten Burg fest. Bekommt Steuern aus jedem
* Verkauf. Wenn zwei Burgen gleicher Groesse bekommt gar keiner etwas. */
2014-08-08 01:03:46 +02:00
for (b = rbuildings(r); b; b = b->next) {
if (b->size > maxsize && building_owner(b) != NULL
&& b->type == castle_bt) {
2014-08-08 01:03:46 +02:00
maxb = b;
maxsize = b->size;
maxowner = building_owner(b);
}
else if (b->size == maxsize && b->type == castle_bt) {
2014-08-08 01:03:46 +02:00
maxb = (building *)NULL;
maxowner = (unit *)NULL;
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2016-09-19 04:15:12 +02:00
hafenowner = owner_buildingtyp(r, harbour_bt);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (maxb != (building *)NULL && maxowner != (unit *)NULL) {
maxeffsize = buildingeffsize(maxb, false);
if (maxeffsize == 0) {
maxb = (building *)NULL;
maxowner = (unit *)NULL;
}
}
2019-02-09 12:50:12 +01:00
/* Die Region muss genug Geld haben, um die Produkte kaufen zu koennen. */
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
money = rmoney(r);
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* max_products sind 1/100 der Bevoelkerung, falls soviele Produkte
2014-08-08 01:03:46 +02:00
* verkauft werden - counter[] - sinkt die Nachfrage um 1 Punkt.
2019-02-09 12:50:12 +01:00
* multiplier speichert r->demand fuer die debug message ab. */
2014-08-08 01:03:46 +02:00
max_products = rpeasants(r) / TRADE_FRACTION;
if (max_products <= 0)
return;
if (r->terrain == newterrain(T_DESERT)
2016-09-19 04:15:12 +02:00
&& buildingtype_exists(r, caravan_bt, true)) {
2014-08-08 01:03:46 +02:00
max_products = rpeasants(r) * 2 / TRADE_FRACTION;
}
/* Verkauf: so programmiert, dass er leicht auf mehrere Gueter pro
* Runde erweitert werden kann. */
2011-03-07 08:02:35 +01:00
norders = expandorders(r, sellorders);
if (norders > 0) {
2018-02-25 17:28:42 +01:00
int j;
for (j = 0; j != norders; j++) {
const luxury_type *search = NULL;
const luxury_type *ltype = g_requests[j]->type.trade.ltype;
int multi = r_demand(r, ltype);
2018-02-25 17:28:42 +01:00
int i, price;
int use = 0;
for (i = 0, search = luxurytypes; search != ltype; search = search->next) {
/* TODO: this is slow and lame! */
++i;
}
if (counter[i] >= limit)
continue;
if (counter[i] + 1 > max_products && multi > 1)
--multi;
price = ltype->price * multi;
2014-08-08 01:03:46 +02:00
if (money >= price) {
item *itm;
attrib *a;
u = g_requests[j]->unit;
a = a_find(u->attribs, &at_luxuries);
if (!a) {
a = a_add(&u->attribs, a_new(&at_luxuries));
}
itm = (item *)a->data.v;
i_change(&itm, ltype->itype, 1);
a->data.v = itm;
++use;
if (u->n < 0) {
u->n = 0;
}
2014-08-08 01:03:46 +02:00
if (hafenowner) {
if (hafenowner->faction != u->faction) {
int abgezogenhafen = price / 10;
hafencollected += abgezogenhafen;
price -= abgezogenhafen;
money -= abgezogenhafen;
}
}
if (maxb) {
if (maxowner->faction != u->faction) {
int abgezogensteuer = price * tax_per_size[maxeffsize] / 100;
taxcollected += abgezogensteuer;
price -= abgezogensteuer;
money -= abgezogensteuer;
}
}
u->n += price;
change_money(u, price);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
2019-02-09 12:50:12 +01:00
/* r->money -= price; --- dies wird eben nicht ausgefuehrt, denn die
* Produkte koennen auch als Steuern eingetrieben werden. In der Region
* wurden Silberstuecke gegen Luxusgueter des selben Wertes eingetauscht!
* Falls mehr als max_products Kunden ein Produkt gekauft haben, sinkt
2019-02-09 12:50:12 +01:00
* die Nachfrage fuer das Produkt um 1. Der Zaehler wird wieder auf 0
* gesetzt. */
if (++counter[i] > max_products) {
int d = r_demand(r, ltype);
if (d > 1) {
r_setdemand(r, ltype, d - 1);
}
counter[i] = 0;
}
}
if (use > 0) {
use_pooled(g_requests[j]->unit, ltype->itype->rtype, GET_DEFAULT, use);
}
}
}
free(g_requests);
2014-08-08 01:03:46 +02:00
2019-02-09 12:50:12 +01:00
/* Steuern. Hier werden die Steuern dem Besitzer der groessten Burg gegeben. */
if (maxowner) {
if (taxcollected > 0) {
change_money(maxowner, (int)taxcollected);
add_income(maxowner, IC_TRADETAX, taxcollected, taxcollected);
/* TODO: Meldung
* "%s verdient %d Silber durch den Handel in %s.",
* unitname(maxowner), (int) taxcollected, regionname(r)); */
}
2010-08-08 10:06:34 +02:00
}
if (hafenowner) {
if (hafencollected > 0) {
change_money(hafenowner, (int)hafencollected);
add_income(hafenowner, IC_TRADETAX, hafencollected, hafencollected);
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
/* Berichte an die Einheiten */
for (u = r->units; u; u = u->next) {
2014-08-08 01:03:46 +02:00
attrib *a = a_find(u->attribs, &at_luxuries);
item *itm;
if (a == NULL)
continue;
2014-08-08 01:03:46 +02:00
for (itm = (item *)a->data.v; itm; itm = itm->next) {
if (itm->number) {
ADDMSG(&u->faction->msgs, msg_message("sellamount",
2014-08-08 01:03:46 +02:00
"unit amount resource", u, itm->number, itm->type->rtype));
}
}
a_remove(&u->attribs, a);
add_income(u, IC_TRADE, u->n, u->n);
2010-08-08 10:06:34 +02:00
}
}
2010-08-08 10:06:34 +02:00
static bool sell(unit * u, econ_request ** sellorders, struct order *ord)
2010-08-08 10:06:34 +02:00
{
char token[128];
2014-08-08 01:03:46 +02:00
bool unlimited = true;
const item_type *itype;
const luxury_type *ltype;
2017-12-30 07:34:17 +01:00
int n, i;
2014-08-08 01:03:46 +02:00
region *r = u->region;
const char *s;
keyword_t kwd;
2016-09-19 04:15:12 +02:00
static int bt_cache;
static const struct building_type *castle_bt, *caravan_bt;
if (bt_changed(&bt_cache)) {
castle_bt = bt_find("castle");
caravan_bt = bt_find("caravan");
}
2014-08-08 01:03:46 +02:00
if (u->ship && is_guarded(r, u)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 69, MSG_INCOME);
return false;
}
2019-02-09 12:50:12 +01:00
/* sellorders sind KEIN array, weil fuer alle items DIE SELBE resource
2014-08-08 01:03:46 +02:00
* (das geld der region) aufgebraucht wird. */
2010-08-08 10:06:34 +02:00
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_SELL);
s = gettoken(token, sizeof(token));
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (findparam(s, u->faction->locale) == P_ANY) {
unlimited = false;
n = rpeasants(r) / TRADE_FRACTION;
if (r->terrain == newterrain(T_DESERT)
2016-09-19 04:15:12 +02:00
&& buildingtype_exists(r, caravan_bt, true))
2014-08-08 01:03:46 +02:00
n *= 2;
if (n == 0) {
cmistake(u, ord, 303, MSG_COMMERCE);
return false;
}
2011-03-07 08:02:35 +01:00
}
2014-08-08 01:03:46 +02:00
else {
n = s ? atoip(s) : 0;
2014-08-08 01:03:46 +02:00
if (n == 0) {
cmistake(u, ord, 27, MSG_COMMERCE);
return false;
}
2011-03-07 08:02:35 +01:00
}
2019-02-09 12:50:12 +01:00
/* In der Region muss es eine Burg geben. */
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (u_race(u) == get_race(RC_INSECT)) {
if (r->terrain != newterrain(T_SWAMP) && r->terrain != newterrain(T_DESERT)
&& !rbuildings(r)) {
cmistake(u, ord, 119, MSG_COMMERCE);
return false;
}
}
else {
2019-02-09 12:50:12 +01:00
/* ...oder in der Region muss es eine Burg geben. */
2014-08-08 01:03:46 +02:00
building *b = 0;
if (r->buildings) {
for (b = r->buildings; b; b = b->next) {
2016-09-19 04:15:12 +02:00
if (b->type == castle_bt && b->size >= 2) break;
2014-08-08 01:03:46 +02:00
}
}
if (!b) {
cmistake(u, ord, 119, MSG_COMMERCE);
return false;
}
}
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Ein Haendler kann nur 10 Gueter pro Talentpunkt verkaufen. */
2011-03-07 08:02:35 +01:00
i = u->number * 10 * effskill(u, SK_TRADE, NULL);
2017-12-30 07:34:17 +01:00
if (n > i) n = i;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (!n) {
cmistake(u, ord, 54, MSG_COMMERCE);
return false;
2010-08-08 10:06:34 +02:00
}
s = gettoken(token, sizeof(token));
itype = s ? finditemtype(s, u->faction->locale) : 0;
ltype = itype ? resource2luxury(itype->rtype) : 0;
if (ltype == NULL || itype == NULL) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 126, MSG_COMMERCE);
return false;
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
else {
attrib *a;
econ_request *o;
2014-08-08 01:03:46 +02:00
int k, available;
2011-03-07 08:02:35 +01:00
if (!r->land || !r_demand(r, ltype)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 263, MSG_COMMERCE);
return false;
}
available = get_pooled(u, itype->rtype, GET_DEFAULT, INT_MAX);
2019-02-09 12:50:12 +01:00
/* Wenn andere Einheiten das selbe verkaufen, muss ihr Zeug abgezogen
2014-08-08 01:03:46 +02:00
* werden damit es nicht zweimal verkauft wird: */
for (o = *sellorders; o; o = o->next) {
if (o->type.trade.ltype == ltype && o->unit->faction == u->faction) {
2014-08-08 01:03:46 +02:00
int fpool =
o->qty - get_pooled(o->unit, itype->rtype, GET_RESERVE, INT_MAX);
2017-12-30 07:34:17 +01:00
if (fpool < 0) fpool = 0;
available -= fpool;
2014-08-08 01:03:46 +02:00
}
}
2011-03-07 08:02:35 +01:00
2017-12-30 07:34:17 +01:00
if (n > available) n = available;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (n <= 0) {
cmistake(u, ord, 264, MSG_COMMERCE);
return false;
}
/* Hier wird production->type verwendet, weil die obere limit durch
2019-02-09 12:50:12 +01:00
* das silber gegeben wird (region->money), welches fuer alle
2014-08-08 01:03:46 +02:00
* (!) produkte als summe gilt, als nicht wie bei der
2019-02-09 12:50:12 +01:00
* produktion, wo fuer jedes produkt einzeln eine obere limite
2014-08-08 01:03:46 +02:00
* existiert, so dass man arrays von orders machen kann. */
2019-02-09 12:50:12 +01:00
/* Ein Haendler kann nur 10 Gueter pro Talentpunkt handeln. */
k = u->number * 10 * effskill(u, SK_TRADE, NULL);
2014-08-08 01:03:46 +02:00
2019-02-09 12:50:12 +01:00
/* hat der Haendler bereits gehandelt, muss die Menge der bereits
* verkauften/gekauften Gueter abgezogen werden */
2014-08-08 01:03:46 +02:00
a = a_find(u->attribs, &at_trades);
if (!a) {
a = a_add(&u->attribs, a_new(&at_trades));
}
else {
k -= a->data.i;
}
2011-03-07 08:02:35 +01:00
2017-12-30 07:34:17 +01:00
if (n > k) n = k;
2014-08-08 01:03:46 +02:00
assert(n >= 0);
2019-02-09 12:50:12 +01:00
/* die Menge der verkauften Gueter merken */
2014-08-08 01:03:46 +02:00
a->data.i += n;
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
2014-08-08 01:03:46 +02:00
o->unit = u;
o->qty = n;
o->type.trade.ltype = ltype;
2014-08-08 01:03:46 +02:00
addlist(sellorders, o);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
return unlimited;
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
2010-08-08 10:06:34 +02:00
/* ------------------------------------------------------------- */
static void plant(unit * u, int raw)
2010-08-08 10:06:34 +02:00
{
int n, i, skill, planted = 0;
const item_type *itype;
const resource_type *rt_water = get_resourcetype(R_WATER_OF_LIFE);
region *r = u->region;
2010-08-08 10:06:34 +02:00
assert(rt_water != NULL);
if (!fval(r->terrain, LAND_REGION)) {
return;
}
itype = rherbtype(r);
if (itype == NULL) {
cmistake(u, u->thisorder, 108, MSG_PRODUCE);
return;
}
2014-08-08 01:03:46 +02:00
2019-02-09 12:50:12 +01:00
/* Skill pruefen */
skill = effskill(u, SK_HERBALISM, NULL);
if (skill < 6) {
ADDMSG(&u->faction->msgs,
2014-08-08 01:03:46 +02:00
msg_feedback(u, u->thisorder, "plant_skills",
"skill minskill product", SK_HERBALISM, 6, itype->rtype, 1));
return;
}
2019-02-09 12:50:12 +01:00
/* Wasser des Lebens pruefen */
if (get_pooled(u, rt_water, GET_DEFAULT, 1) == 0) {
ADDMSG(&u->faction->msgs,
2014-08-08 01:03:46 +02:00
msg_feedback(u, u->thisorder, "resource_missing", "missing", rt_water));
return;
}
n = get_pooled(u, itype->rtype, GET_DEFAULT, skill * u->number);
2019-02-09 12:50:12 +01:00
/* Kraeuter pruefen */
if (n == 0) {
ADDMSG(&u->faction->msgs,
2014-08-08 01:03:46 +02:00
msg_feedback(u, u->thisorder, "resource_missing", "missing",
itype->rtype));
return;
}
2014-08-08 01:03:46 +02:00
2017-12-30 07:34:17 +01:00
i = skill * u->number;
if (i > raw) i = raw;
if (n > i) n = i;
2019-02-09 12:50:12 +01:00
/* Fuer jedes Kraut Talent*10% Erfolgschance. */
for (i = n; i > 0; i--) {
if (rng_int() % 10 < skill)
planted++;
}
produceexp(u, SK_HERBALISM, u->number);
2014-08-08 01:03:46 +02:00
/* Alles ok. Abziehen. */
use_pooled(u, rt_water, GET_DEFAULT, 1);
use_pooled(u, itype->rtype, GET_DEFAULT, n);
2018-01-21 11:14:55 +01:00
rsetherbs(r, rherbs(r) + planted);
ADDMSG(&u->faction->msgs, msg_message("plant", "unit region amount herb",
2014-08-08 01:03:46 +02:00
u, r, planted, itype->rtype));
2010-08-08 10:06:34 +02:00
}
static void planttrees(unit * u, int raw)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int n, i, skill, planted = 0;
const resource_type *rtype;
region * r = u->region;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (!fval(r->terrain, LAND_REGION)) {
return;
}
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Mallornbaeume kann man nur in Mallornregionen zuechten */
rtype = get_resourcetype(fval(r, RF_MALLORN) ? R_MALLORN_SEED : R_SEED);
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Skill pruefen */
skill = effskill(u, SK_HERBALISM, NULL);
2014-08-08 01:03:46 +02:00
if (skill < 6) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "plant_skills",
"skill minskill product", SK_HERBALISM, 6, rtype, 1));
2014-08-08 01:03:46 +02:00
return;
}
if (fval(r, RF_MALLORN) && skill < 7) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "plant_skills",
"skill minskill product", SK_HERBALISM, 7, rtype, 1));
2014-08-08 01:03:46 +02:00
return;
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */
2017-12-30 07:34:17 +01:00
if (raw > skill * u->number) raw = skill * u->number;
2014-08-08 01:03:46 +02:00
n = get_pooled(u, rtype, GET_DEFAULT, raw);
if (n == 0) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype));
return;
}
2017-12-30 07:34:17 +01:00
if (n > raw) n = raw;
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Fuer jeden Samen Talent*10% Erfolgschance. */
2014-08-08 01:03:46 +02:00
for (i = n; i > 0; i--) {
if (rng_int() % 10 < skill)
planted++;
}
rsettrees(r, 0, rtrees(r, 0) + planted);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* Alles ok. Abziehen. */
produceexp(u, SK_HERBALISM, u->number);
use_pooled(u, rtype, GET_DEFAULT, n);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_message("plant",
"unit region amount herb", u, r, planted, rtype));
2010-08-08 10:06:34 +02:00
}
2019-02-09 12:50:12 +01:00
/* zuechte baeume */
static void breedtrees(unit * u, int raw)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int n, i, skill, planted = 0;
const resource_type *rtype;
int current_season;
region *r = u->region;
gamedate date;
get_gamedate(turn, &date);
current_season = date.season;
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Baeume zuechten geht nur im Fruehling */
2014-08-08 01:03:46 +02:00
if (current_season != SEASON_SPRING) {
planttrees(u, raw);
2014-08-08 01:03:46 +02:00
return;
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
if (!fval(r->terrain, LAND_REGION)) {
return;
}
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Mallornbaeume kann man nur in Mallornregionen zuechten */
rtype = get_resourcetype(fval(r, RF_MALLORN) ? R_MALLORN_SEED : R_SEED);
2011-03-07 08:02:35 +01:00
2019-02-09 12:50:12 +01:00
/* Skill pruefen */
skill = effskill(u, SK_HERBALISM, NULL);
2014-08-08 01:03:46 +02:00
if (skill < 12) {
planttrees(u, raw);
2014-08-08 01:03:46 +02:00
return;
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* wenn eine Anzahl angegeben wurde, nur soviel verbrauchen */
2017-12-30 07:34:17 +01:00
i = skill * u->number;
if (raw > i) raw = i;
2014-08-08 01:03:46 +02:00
n = get_pooled(u, rtype, GET_DEFAULT, raw);
2019-02-09 12:50:12 +01:00
/* Samen pruefen */
2014-08-08 01:03:46 +02:00
if (n == 0) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "resource_missing", "missing", rtype));
return;
}
2017-12-30 07:34:17 +01:00
if (n > raw) n = raw;
2014-08-08 01:03:46 +02:00
2019-02-09 12:50:12 +01:00
/* Fuer jeden Samen Talent*5% Erfolgschance. */
2014-08-08 01:03:46 +02:00
for (i = n; i > 0; i--) {
if (rng_int() % 100 < skill * 5)
planted++;
}
rsettrees(r, 1, rtrees(r, 1) + planted);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
/* Alles ok. Abziehen. */
produceexp(u, SK_HERBALISM, u->number);
use_pooled(u, rtype, GET_DEFAULT, n);
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
ADDMSG(&u->faction->msgs, msg_message("plant",
"unit region amount herb", u, r, planted, rtype));
2010-08-08 10:06:34 +02:00
}
2019-02-09 12:50:12 +01:00
/* zuechte pferde */
2015-08-27 16:59:39 +02:00
static void breedhorses(unit * u)
2010-08-08 10:06:34 +02:00
{
int n, c, breed = 0;
const struct resource_type *rhorse = get_resourcetype(R_HORSE);
int horses, effsk;
2016-09-19 04:15:12 +02:00
static int bt_cache;
static const struct building_type *stables_bt;
if (bt_changed(&bt_cache)) {
stables_bt = bt_find("stables");
}
2015-08-27 16:59:39 +02:00
assert(rhorse && rhorse->itype);
2016-09-19 04:15:12 +02:00
if (!active_building(u, stables_bt)) {
cmistake(u, u->thisorder, 122, MSG_PRODUCE);
return;
}
horses = i_get(u->items, rhorse->itype);
if (horses < 2) {
cmistake(u, u->thisorder, 107, MSG_PRODUCE);
return;
}
effsk = effskill(u, SK_HORSE_TRAINING, NULL);
n = u->number * effsk;
2017-12-30 07:34:17 +01:00
if (n > horses) n = horses;
2010-08-08 10:06:34 +02:00
for (c = 0; c < n; c++) {
if (rng_int() % 100 < effsk) {
i_change(&u->items, rhorse->itype, 1);
++breed;
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
produceexp(u, SK_HORSE_TRAINING, u->number);
2010-08-08 10:06:34 +02:00
ADDMSG(&u->faction->msgs, msg_message("raised", "unit amount", u, breed));
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void breed_cmd(unit * u, struct order *ord)
2010-08-08 10:06:34 +02:00
{
char token[128];
2014-08-08 01:03:46 +02:00
int m;
const char *s;
param_t p;
region *r = u->region;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (r->land == NULL) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_onlandonly", ""));
return;
}
2010-08-08 10:06:34 +02:00
2019-02-09 12:50:12 +01:00
/* zuechte [<anzahl>] <parameter> */
2017-10-09 20:33:47 +02:00
(void)init_order_depr(ord);
s = gettoken(token, sizeof(token));
2011-03-07 08:02:35 +01:00
m = s ? atoip(s) : 0;
2014-08-08 01:03:46 +02:00
if (m != 0) {
/* first came a want-paramter */
s = gettoken(token, sizeof(token));
2014-08-08 01:03:46 +02:00
}
else {
m = INT_MAX;
}
2014-08-24 12:41:39 +02:00
if (!s || !s[0]) {
2014-08-08 01:03:46 +02:00
p = P_ANY;
}
else {
p = findparam(s, u->faction->locale);
}
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
switch (p) {
case P_HERBS:
plant(u, m);
2014-08-08 01:03:46 +02:00
break;
case P_TREES:
breedtrees(u, m);
2011-03-08 08:44:20 +01:00
break;
2014-08-08 01:03:46 +02:00
default:
if (p != P_ANY) {
const resource_type *rtype = findresourcetype(s, u->faction->locale);
if (rtype == get_resourcetype(R_SEED) || rtype == get_resourcetype(R_MALLORN_SEED)) {
breedtrees(u, m);
2014-08-08 01:03:46 +02:00
break;
}
else if (rtype != get_resourcetype(R_HORSE)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_cannotmake", ""));
break;
}
}
2015-08-27 16:59:39 +02:00
breedhorses(u);
2011-03-08 08:44:20 +01:00
break;
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static const char *rough_amount(int a, int m)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int p = (a * 100) / m;
if (p < 10) {
return "sehr wenige";
}
else if (p < 30) {
return "wenige";
}
else if (p < 60) {
return "relativ viele";
}
else if (p < 90) {
return "viele";
}
return "sehr viele";
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void research_cmd(unit * u, struct order *ord)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
region *r = u->region;
keyword_t kwd;
2010-08-08 10:06:34 +02:00
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_RESEARCH);
2010-08-08 10:06:34 +02:00
if (effskill(u, SK_HERBALISM, NULL) < 7) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 227, MSG_EVENT);
return;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
produceexp(u, SK_HERBALISM, u->number);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (rherbs(r) > 0) {
const item_type *itype = rherbtype(r);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (itype != NULL) {
ADDMSG(&u->faction->msgs, msg_message("researchherb",
"unit region amount herb",
u, r, rough_amount(rherbs(r), 100), itype->rtype));
}
else {
ADDMSG(&u->faction->msgs, msg_message("researchherb_none",
"unit region", u, r));
}
}
else {
ADDMSG(&u->faction->msgs, msg_message("researchherb_none",
"unit region", u, r));
}
2010-08-08 10:06:34 +02:00
}
2011-03-07 08:02:35 +01:00
static void expandentertainment(region * r)
{
2014-08-08 01:03:46 +02:00
int m = entertainmoney(r);
econ_request *o;
2014-08-08 01:03:46 +02:00
for (o = &entertainers[0]; o != nextentertainer; ++o) {
double part = m / (double)entertaining;
2018-02-25 17:28:42 +01:00
unit *u = o->unit;
2014-08-08 01:03:46 +02:00
if (entertaining <= m)
u->n = o->qty;
else
u->n = (int)(o->qty * part);
change_money(u, u->n);
rsetmoney(r, rmoney(r) - u->n);
m -= u->n;
entertaining -= o->qty;
2019-02-09 12:50:12 +01:00
/* Nur soviel PRODUCEEXP wie auch tatsaechlich gemacht wurde */
2017-12-30 07:34:17 +01:00
produceexp(u, SK_ENTERTAINMENT, (u->n < u->number) ? u->n : u->number);
2014-08-08 01:03:46 +02:00
add_income(u, IC_ENTERTAIN, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
2011-03-07 08:02:35 +01:00
}
void entertain_cmd(unit * u, struct order *ord)
{
2014-08-08 01:03:46 +02:00
region *r = u->region;
int max_e;
econ_request *o;
2014-08-08 01:03:46 +02:00
static int entertainbase = 0;
static int entertainperlevel = 0;
keyword_t kwd;
2014-08-08 01:03:46 +02:00
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_ENTERTAIN);
2014-08-08 01:03:46 +02:00
if (!entertainbase) {
const char *str = config_get("entertain.base");
2014-08-08 01:03:46 +02:00
entertainbase = str ? atoi(str) : 0;
}
if (!entertainperlevel) {
const char *str = config_get("entertain.perlevel");
2014-08-08 01:03:46 +02:00
entertainperlevel = str ? atoi(str) : 0;
}
if (fval(u, UFL_WERE)) {
cmistake(u, ord, 58, MSG_INCOME);
return;
}
if (!effskill(u, SK_ENTERTAINMENT, NULL)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 58, MSG_INCOME);
return;
}
if (u->ship && is_guarded(r, u)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 69, MSG_INCOME);
return;
}
if (is_cursed(r->attribs, &ct_depression)) {
2014-08-08 01:03:46 +02:00
cmistake(u, ord, 28, MSG_INCOME);
return;
}
2010-08-08 10:06:34 +02:00
u->wants = u->number * (entertainbase + effskill(u, SK_ENTERTAINMENT, NULL)
2014-08-08 01:03:46 +02:00
* entertainperlevel);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
max_e = getuint();
if (max_e != 0) {
2017-12-30 07:34:17 +01:00
if (u->wants > max_e) u->wants = max_e;
2014-08-08 01:03:46 +02:00
}
o = nextentertainer++;
o->unit = u;
o->qty = u->wants;
entertaining += o->qty;
2010-08-08 10:06:34 +02:00
}
/**
2014-08-08 01:03:46 +02:00
* \return number of working spaces taken by players
2010-08-08 10:06:34 +02:00
*/
static void
expandwork(region * r, econ_request * work_begin, econ_request * work_end, int maxwork)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
int earnings;
/* n: verbleibende Einnahmen */
/* fishes: maximale Arbeiter */
int jobs = maxwork;
int p_wage = wage(r, NULL, NULL, turn);
int money = rmoney(r);
econ_request *o;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
for (o = work_begin; o != work_end; ++o) {
unit *u = o->unit;
int workers;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (u->number == 0)
continue;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (jobs >= working)
workers = u->number;
else {
int req = (u->number * jobs) % working;
2014-08-08 01:03:46 +02:00
workers = u->number * jobs / working;
if (req > 0 && rng_int() % working < req)
2014-08-08 01:03:46 +02:00
workers++;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
assert(workers >= 0);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
u->n = workers * wage(u->region, u->faction, u_race(u), turn);
jobs -= workers;
assert(jobs >= 0);
change_money(u, u->n);
working -= o->unit->number;
add_income(u, IC_WORK, o->qty, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
if (jobs > rpeasants(r)) {
jobs = rpeasants(r);
}
earnings = jobs * p_wage;
if (jobs > 0 && r->attribs && rule_blessed_harvest() == HARVEST_TAXES) {
2014-08-08 01:03:46 +02:00
/* E3 rules */
int happy = harvest_effect(r);
earnings += happy * jobs;
2014-08-08 01:03:46 +02:00
}
rsetmoney(r, money + earnings);
2010-08-08 10:06:34 +02:00
}
static int do_work(unit * u, order * ord, econ_request * o)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
if (playerrace(u_race(u))) {
region *r = u->region;
int w;
if (fval(u, UFL_WERE)) {
if (ord)
cmistake(u, ord, 313, MSG_INCOME);
return -1;
}
if (u->ship && is_guarded(r, u)) {
2014-08-08 01:03:46 +02:00
if (ord)
cmistake(u, ord, 69, MSG_INCOME);
return -1;
}
w = wage(r, u->faction, u_race(u), turn);
u->wants = u->number * w;
o->unit = u;
o->qty = u->number * w;
working += u->number;
return 0;
}
else if (ord && !is_monsters(u->faction)) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "race_cantwork", "race", u_race(u)));
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
return -1;
2010-08-08 10:06:34 +02:00
}
static void expandloot(region * r, econ_request * lootorders)
{
unsigned int norders;
norders = expandorders(r, lootorders);
if (norders > 0) {
unit *u;
unsigned int i;
int m, looted = 0;
int startmoney = rmoney(r);
for (i = 0; i != norders && startmoney > looted + TAXFRACTION * 2; i++) {
change_money(g_requests[i]->unit, TAXFRACTION);
g_requests[i]->unit->n += TAXFRACTION;
/*Looting destroys double the money*/
looted += TAXFRACTION * 2;
}
rsetmoney(r, startmoney - looted);
free(g_requests);
/* Lowering morale by 1 depending on the looted money (+20%) */
m = region_get_morale(r);
if (m && startmoney > 0) {
if (rng_int() % 100 < 20 + (looted * 80) / startmoney) {
/*Nur Moral -1, turns is not changed, so the first time nothing happens if the morale is good*/
region_set_morale(r, m - 1, -1);
}
}
for (u = r->units; u; u = u->next) {
if (u->n >= 0) {
add_income(u, IC_LOOT, u->wants, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
}
}
}
void expandtax(region * r, econ_request * taxorders)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
unit *u;
unsigned int norders;
2010-08-08 10:06:34 +02:00
norders = expandorders(r, taxorders);
if (norders > 0) {
unsigned int i;
for (i = 0; i != norders && rmoney(r) > TAXFRACTION; i++) {
change_money(g_requests[i]->unit, TAXFRACTION);
g_requests[i]->unit->n += TAXFRACTION;
rsetmoney(r, rmoney(r) - TAXFRACTION);
}
free(g_requests);
2010-08-08 10:06:34 +02:00
for (u = r->units; u; u = u->next) {
if (u->n >= 0) {
add_income(u, IC_TAX, u->wants, u->n);
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
}
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
}
}
void tax_cmd(unit * u, struct order *ord, econ_request ** taxorders)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
/* Steuern werden noch vor der Forschung eingetrieben */
region *r = u->region;
unit *u2;
int n;
econ_request *o;
2014-08-08 01:03:46 +02:00
int max;
keyword_t kwd;
static int taxperlevel = 0;
if (!taxperlevel) {
taxperlevel = config_get_int("taxing.perlevel", 0);
}
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_TAX);
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (!humanoidrace(u_race(u)) && !is_monsters(u->faction)) {
cmistake(u, ord, 228, MSG_INCOME);
return;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (fval(u, UFL_WERE)) {
cmistake(u, ord, 228, MSG_INCOME);
return;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
n = armedmen(u, false);
if (!n) {
cmistake(u, ord, 48, MSG_INCOME);
return;
}
2010-08-08 10:06:34 +02:00
if (effskill(u, SK_TAXING, NULL) <= 0) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "error_no_tax_skill", ""));
return;
}
max = getint();
2010-08-08 10:06:34 +02:00
if (max <= 0) {
2014-08-08 01:03:46 +02:00
max = INT_MAX;
}
2014-08-08 01:03:46 +02:00
if (!playerrace(u_race(u))) {
2017-12-30 07:34:17 +01:00
u->wants = income(u);
2014-08-08 01:03:46 +02:00
}
else {
u->wants = n * effskill(u, SK_TAXING, NULL) * taxperlevel;
2014-08-08 01:03:46 +02:00
}
2017-12-30 07:34:17 +01:00
if (u->wants > max) u->wants = max;
2010-08-08 10:06:34 +02:00
u2 = is_guarded(r, u);
2014-08-08 01:03:46 +02:00
if (u2) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "region_guarded", "guard", u2));
return;
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* die einnahmen werden in fraktionen von 10 silber eingeteilt: diese
* fraktionen werden dann bei eintreiben unter allen eintreibenden
* einheiten aufgeteilt. */
2010-08-08 10:06:34 +02:00
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
2014-08-08 01:03:46 +02:00
o->qty = u->wants / TAXFRACTION;
o->unit = u;
addlist(taxorders, o);
return;
2010-08-08 10:06:34 +02:00
}
void loot_cmd(unit * u, struct order *ord, econ_request ** lootorders)
{
region *r = u->region;
unit *u2;
int n;
int max;
econ_request *o;
keyword_t kwd;
2017-10-09 20:33:47 +02:00
kwd = init_order_depr(ord);
assert(kwd == K_LOOT);
if (config_get_int("rules.enable_loot", 0) == 0 && !is_monsters(u->faction)) {
return;
}
if (!humanoidrace(u_race(u)) && !is_monsters(u->faction)) {
cmistake(u, ord, 228, MSG_INCOME);
return;
}
if (fval(u, UFL_WERE)) {
cmistake(u, ord, 228, MSG_INCOME);
return;
}
n = armedmen(u, false);
if (!n) {
cmistake(u, ord, 48, MSG_INCOME);
return;
}
u2 = is_guarded(r, u);
if (u2) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, ord, "region_guarded", "guard", u2));
return;
}
max = getint();
if (max <= 0) {
max = INT_MAX;
}
if (!playerrace(u_race(u))) {
2017-12-30 07:34:17 +01:00
u->wants = income(u);
if (u->wants > max) u->wants = max;
}
else {
/* For player start with 20 Silver +10 every 5 level of close combat skill*/
int skm = effskill(u, SK_MELEE, NULL);
int sks = effskill(u, SK_SPEAR, NULL);
2017-12-30 07:34:17 +01:00
int skbonus = ((skm > sks ? skm : sks) * 2 / 10) + 2;
u->wants = n * skbonus * 10;
if (u->wants > max) u->wants = max;
}
o = (econ_request *)calloc(1, sizeof(econ_request));
if (!o) abort();
o->qty = u->wants / TAXFRACTION;
o->unit = u;
addlist(lootorders, o);
return;
}
#define MAX_WORKERS 512
2011-03-07 08:02:35 +01:00
void auto_work(region * r)
2010-08-08 10:06:34 +02:00
{
econ_request workers[MAX_WORKERS];
econ_request *nextworker = workers;
2014-08-08 01:03:46 +02:00
unit *u;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
for (u = r->units; u; u = u->next) {
if (!(u->flags & UFL_LONGACTION) && !is_monsters(u->faction)) {
if (do_work(u, NULL, nextworker) == 0) {
assert(nextworker - workers < MAX_WORKERS);
++nextworker;
}
}
}
if (nextworker != workers) {
2016-11-15 20:43:36 +01:00
expandwork(r, workers, nextworker, region_maxworkers(r));
2010-08-08 10:06:34 +02:00
}
}
2011-03-07 08:02:35 +01:00
static void peasant_taxes(region * r)
2010-08-08 10:06:34 +02:00
{
2014-08-08 01:03:46 +02:00
faction *f;
unit *u;
building *b;
int money;
int level;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
f = region_get_owner(r);
if (f == NULL || is_mourning(r, turn)) {
return;
}
money = rmoney(r);
if (money <= 0)
return;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
b = largestbuilding(r, cmp_taxes, false);
if (b == NULL)
return;
u = building_owner(b);
if (u == NULL || u->faction != f)
return;
2010-08-08 10:06:34 +02:00
level = buildingeffsize(b, false);
if (level > 0) {
2017-05-06 16:48:32 +02:00
double taxfactor = (double)money * level / building_taxes(b);
double morale = (double)money * region_get_morale(r) / MORALE_TAX_FACTOR;
if (taxfactor > morale) {
2014-08-08 01:03:46 +02:00
taxfactor = morale;
}
2014-08-08 01:03:46 +02:00
if (taxfactor > 0) {
int taxmoney = (int)taxfactor;
change_money(u, taxmoney);
rsetmoney(r, money - taxmoney);
ADDMSG(&u->faction->msgs, msg_message("income_tax",
"unit region amount", u, r, taxmoney));
}
2010-08-08 10:06:34 +02:00
}
}
static bool rule_auto_taxation(void)
{
return config_get_int("rules.economy.taxation", 0) != 0;
}
static bool rule_autowork(void) {
return config_get_int("work.auto", 0) != 0;
}
2011-03-07 08:02:35 +01:00
void produce(struct region *r)
2010-08-08 10:06:34 +02:00
{
econ_request workers[MAX_WORKERS];
econ_request *taxorders, *lootorders, *sellorders, *stealorders, *buyorders;
2014-08-08 01:03:46 +02:00
unit *u;
bool limited = true;
econ_request *nextworker = workers;
2016-09-19 04:15:12 +02:00
static int bt_cache;
static const struct building_type *caravan_bt;
2016-09-22 15:51:11 +02:00
static int rc_cache;
2017-05-24 08:52:19 +02:00
static const race *rc_insect, *rc_aquarian;
2017-11-05 19:15:43 +01:00
2016-09-19 04:15:12 +02:00
if (bt_changed(&bt_cache)) {
caravan_bt = bt_find("caravan");
}
2016-09-22 15:51:11 +02:00
if (rc_changed(&rc_cache)) {
rc_insect = get_race(RC_INSECT);
rc_aquarian = get_race(RC_AQUARIAN);
}
2014-08-08 01:03:46 +02:00
assert(r);
/* das sind alles befehle, die 30 tage brauchen, und die in thisorder
* stehen! von allen 30-tage befehlen wird einfach der letzte verwendet
* (dosetdefaults).
*
* kaufen vor einnahmequellen. da man in einer region dasselbe produkt
* nicht kaufen und verkaufen kann, ist die reihenfolge wegen der
* produkte egal. nicht so wegen dem geld.
*
* lehren vor lernen. */
assert(rmoney(r) >= 0);
assert(rpeasants(r) >= 0);
if (r->land && rule_auto_taxation()) {
/* new taxation rules, region owners make money based on morale and building */
peasant_taxes(r);
}
buyorders = 0;
sellorders = 0;
working = 0;
nextentertainer = &entertainers[0];
entertaining = 0;
taxorders = 0;
lootorders = 0;
2014-08-08 01:03:46 +02:00
stealorders = 0;
2011-03-07 08:02:35 +01:00
2014-08-08 01:03:46 +02:00
for (u = r->units; u; u = u->next) {
order *ord;
bool trader = false;
keyword_t todo;
2010-08-08 10:06:34 +02:00
2017-05-24 08:52:19 +02:00
if (fval(u, UFL_LONGACTION))
2014-08-08 01:03:46 +02:00
continue;
2016-09-22 15:51:11 +02:00
if (u_race(u) == rc_insect && r_insectstalled(r) &&
!is_cursed(u->attribs, &ct_insectfur))
2014-08-08 01:03:46 +02:00
continue;
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (fval(u, UFL_LONGACTION) && u->thisorder == NULL) {
/* this message was already given in laws.c:update_long_order
2014-08-08 01:03:46 +02:00
cmistake(u, u->thisorder, 52, MSG_PRODUCE);
*/
continue;
}
for (ord = u->orders; ord; ord = ord->next) {
keyword_t kwd = getkeyword(ord);
if (kwd == K_BUY) {
buy(u, &buyorders, ord);
trader = true;
}
else if (kwd == K_SELL) {
/* sell returns true if the sale is not limited
* by the region limit */
limited &= !sell(u, &sellorders, ord);
trader = true;
}
}
if (trader) {
attrib *a = a_find(u->attribs, &at_trades);
if (a && a->data.i) {
produceexp(u, SK_TRADE, u->number);
}
fset(u, UFL_LONGACTION | UFL_NOTMOVING);
continue;
}
todo = getkeyword(u->thisorder);
if (todo == NOKEYWORD)
continue;
2016-09-22 15:51:11 +02:00
if (fval(r->terrain, SEA_REGION) && u_race(u) != rc_aquarian
2014-08-08 01:03:46 +02:00
&& !(u_race(u)->flags & RCF_SWIM)
&& todo != K_STEAL && todo != K_SPY && todo != K_SABOTAGE)
continue;
switch (todo) {
case K_ENTERTAIN:
entertain_cmd(u, u->thisorder);
break;
case K_WORK:
if (!rule_autowork() && do_work(u, u->thisorder, nextworker) == 0) {
2014-08-08 01:03:46 +02:00
assert(nextworker - workers < MAX_WORKERS);
++nextworker;
}
break;
case K_TAX:
tax_cmd(u, u->thisorder, &taxorders);
break;
case K_LOOT:
loot_cmd(u, u->thisorder, &lootorders);
break;
2014-08-08 01:03:46 +02:00
case K_STEAL:
steal_cmd(u, u->thisorder, &stealorders);
break;
case K_SPY:
spy_cmd(u, u->thisorder);
break;
case K_SABOTAGE:
sabotage_cmd(u, u->thisorder);
break;
case K_PLANT:
case K_GROW:
2014-08-08 01:03:46 +02:00
breed_cmd(u, u->thisorder);
break;
case K_RESEARCH:
research_cmd(u, u->thisorder);
break;
2014-11-02 12:45:20 +01:00
default:
2014-11-02 12:54:49 +01:00
/* not handled here */
break;
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
}
2014-08-08 01:03:46 +02:00
/* Entertainment (expandentertainment) und Besteuerung (expandtax) vor den
* Befehlen, die den Bauern mehr Geld geben, damit man aus den Zahlen der
2019-02-09 12:50:12 +01:00
* letzten Runde berechnen kann, wieviel die Bauern fuer Unterhaltung
2014-08-08 01:03:46 +02:00
* auszugeben bereit sind. */
if (entertaining)
expandentertainment(r);
if (!rule_autowork()) {
2016-11-15 20:43:36 +01:00
expandwork(r, workers, nextworker, region_maxworkers(r));
2014-08-08 01:03:46 +02:00
}
if (taxorders) {
2014-08-08 01:03:46 +02:00
expandtax(r, taxorders);
free_requests(taxorders);
}
2010-08-08 10:06:34 +02:00
if (lootorders) {
expandloot(r, lootorders);
free_requests(lootorders);
}
2014-08-08 01:03:46 +02:00
/* An erster Stelle Kaufen (expandbuying), die Bauern so Geld bekommen, um
* nachher zu beim Verkaufen (expandselling) den Spielern abkaufen zu
2019-02-09 12:50:12 +01:00
* koennen. */
2010-08-08 10:06:34 +02:00
if (buyorders) {
2014-08-08 01:03:46 +02:00
expandbuying(r, buyorders);
free_requests(buyorders);
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
if (sellorders) {
int limit = rpeasants(r) / TRADE_FRACTION;
if (r->terrain == newterrain(T_DESERT)
2016-09-19 04:15:12 +02:00
&& buildingtype_exists(r, caravan_bt, true))
2014-08-08 01:03:46 +02:00
limit *= 2;
expandselling(r, sellorders, limited ? limit : INT_MAX);
free_requests(sellorders);
2014-08-08 01:03:46 +02:00
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
/* Die Spieler sollen alles Geld verdienen, bevor sie beklaut werden
* (expandstealing). */
2010-08-08 10:06:34 +02:00
if (stealorders) {
2014-08-08 01:03:46 +02:00
expandstealing(r, stealorders);
free_requests(stealorders);
}
2010-08-08 10:06:34 +02:00
2014-08-08 01:03:46 +02:00
assert(rmoney(r) >= 0);
assert(rpeasants(r) >= 0);
2010-08-08 10:06:34 +02:00
}