#include #include "faction.h" #include "calendar.h" #include "config.h" #include "database.h" #include "alliance.h" #include "ally.h" #include "curse.h" #include "equipment.h" #include "group.h" #include "item.h" #include "messages.h" #include "order.h" #include "plane.h" #include "race.h" #include "region.h" #include "spellbook.h" #include "terrain.h" #include "unit.h" #include #include #include /* util includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* libc includes */ #include #include #include #include #include #include faction *factions; /** remove the faction from memory. * this frees all memory that's only accessible through the faction, * but you should still call funhash and remove the faction from the * global list. */ static void free_faction(faction * f) { funhash(f); if (f->msgs) { free_messagelist(f->msgs->begin); free(f->msgs); } while (f->battles) { struct bmsg *bm = f->battles; f->battles = bm->next; if (bm->msgs) { free_messagelist(bm->msgs->begin); free(bm->msgs); } free(bm); } if (f->spellbook) { free_spellbook(f->spellbook); } while (f->groups) { group *g = f->groups; f->groups = g->next; free_group(g); } freelist(f->allies); free(f->email); free(f->name); if (f->seen_factions) { selist_free(f->seen_factions); f->seen_factions = 0; } while (f->attribs) { a_remove(&f->attribs, f->attribs); } i_freeall(&f->items); freelist(f->origin); } #define FMAXHASH 2039 faction *factionhash[FMAXHASH]; void fhash(faction * f) { int index = f->no % FMAXHASH; f->nexthash = factionhash[index]; factionhash[index] = f; } void funhash(faction * f) { int index = f->no % FMAXHASH; faction **fp = factionhash + index; while (*fp && (*fp) != f) { fp = &(*fp)->nexthash; } if (*fp == f) { *fp = f->nexthash; } } static faction *ffindhash(int no) { int index = no % FMAXHASH; faction *f = factionhash[index]; while (f && f->no != no) f = f->nexthash; return f; } faction *findfaction(int n) { faction *f = ffindhash(n); return f; } faction *getfaction(void) { return findfaction(getid()); } void set_show_item(faction * f, const struct item_type *itype) { attrib *a = a_add(&f->attribs, a_new(&at_showitem)); a->data.v = (void *)itype; } const char *factionname(const faction * f) { typedef char name[OBJECTIDSIZE + 1]; static name idbuf[8]; static int nextbuf = 0; char *ibuf = idbuf[(++nextbuf) % 8]; if (f && f->name) { slprintf(ibuf, sizeof(idbuf[0]), "%s (%s)", f->name, itoa36(f->no)); } else { strcpy(ibuf, "Unbekannte Partei (?)"); } return ibuf; } bool faction_id_is_unused(int id) { return findfaction(id) == NULL; } #define MAX_FACTION_ID (36*36*36*36) static int unused_faction_id(void) { int id = rng_int() % MAX_FACTION_ID; while (!faction_id_is_unused(id)) { if (++id == MAX_FACTION_ID) { id = 0; } } return id; } char *faction_genpassword(faction *f, char *buffer) { password_generate(buffer, 8); faction_setpassword(f, password_hash(buffer, PASSWORD_DEFAULT)); ADDMSG(&f->msgs, msg_message("changepasswd", "value", buffer)); return buffer; } faction *addfaction(const char *email, const char *password, const struct race * frace, const struct locale * loc) { faction *f = calloc(1, sizeof(faction)); const char *fname; char buf[128]; if (!f) abort(); if (check_email(email) == 0) { faction_setemail(f, email); } else { log_info("Invalid email address for faction %s: %s\n", itoa36(f->no), email?email:""); faction_setemail(f, NULL); } f->alliance_joindate = turn; f->lastorders = 0; f->_alive = true; f->password_id = 0; f->age = 0; f->race = frace; f->magiegebiet = 0; f->locale = loc; f->uid = 0; f->flags = FFL_ISNEW|FFL_PWMSG; if (password) { faction_setpassword(f, password_hash(password, PASSWORD_DEFAULT)); ADDMSG(&f->msgs, msg_message("changepasswd", "value", password)); } f->options = WANT_OPTION(O_REPORT) | WANT_OPTION(O_ZUGVORLAGE) | WANT_OPTION(O_COMPUTER) | WANT_OPTION(O_COMPRESS) | WANT_OPTION(O_ADRESSEN) | WANT_OPTION(O_STATISTICS); f->no = unused_faction_id(); if (rule_region_owners()) { alliance *al = makealliance(f->no, NULL); setalliance(f, al); } addlist(&factions, f); fhash(f); fname = LOC(loc, "factiondefault"); slprintf(buf, sizeof(buf), "%s %s", fname ? fname : "faction", itoa36(f->no)); f->name = str_strdup(buf); if (!f->race) { log_warning("creating a faction that has no race", itoa36(f->no)); } return f; } #define PEASANT_MIN (10 * RECRUITFRACTION) #define PEASANT_MAX (20 * RECRUITFRACTION) unit *addplayer(region * r, faction * f) { unit *u; const char * name; assert(r->land); if (rpeasants(r) < PEASANT_MIN) { rsetpeasants(r, PEASANT_MIN + rng_int() % (PEASANT_MAX - PEASANT_MIN)); } assert(f->units == NULL); faction_setorigin(f, 0, r->x, r->y); u = create_unit(r, f, 1, f->race, 0, NULL, NULL); u->status = ST_FLEE; u->thisorder = default_order(f->locale); unit_addorder(u, copy_order(u->thisorder)); name = config_get("rules.equip_first"); if (!equip_unit(u, name ? name : "first_unit")) { /* give every unit enough money to survive the first turn */ i_change(&u->items, get_resourcetype(R_SILVER)->itype, maintenance_cost(u)); } u->hp = unit_max_hp(u) * u->number; fset(u, UFL_ISNEW); if (f->race == get_race(RC_DAEMON)) { race_t urc; const race *rc; do { urc = (race_t)(rng_int() % MAXRACES); rc = get_race(urc); } while (rc == NULL || urc == RC_DAEMON || !playerrace(rc)); u->irace = rc; } f->lastorders = turn; return u; } bool checkpasswd(const faction * f, const char *passwd) { const char *pwhash; if (!passwd) return false; pwhash = faction_getpassword(f); if (pwhash && password_verify(pwhash, passwd) == VERIFY_FAIL) { log_info("password check failed: %s", factionname(f)); return false; } return true; } int read_faction_reference(gamedata * data, faction **fp) { int id; READ_INT(data->store, &id); if (id > 0) { *fp = findfaction(id); if (*fp == NULL) { *fp = faction_create(id); } } else { *fp = NULL; } return id; } void write_faction_reference(const faction * f, struct storage *store) { assert(!f || f->_alive); WRITE_INT(store, f ? f->no : 0); } void free_flist(faction **fp) { faction * flist = *fp; while (flist) { faction *f = flist; flist = f->next; free_faction(f); free(f); } *fp = 0; } static faction *dead_factions; static void give_special_items(unit *u, item **items) { item **iter = items; while (*iter) { item *itm = *iter; if (itm->number > 0 && itm->type->flags & ITF_NOTLOST) { i_change(&u->items, itm->type, itm->number); *iter = itm->next; if (iter == items) { *items = *iter; } i_free(itm); } else { iter = &itm->next; } } } faction *get_or_create_monsters(void) { faction *f = findfaction(MONSTER_ID); if (!f) { const race *rc = rc_get_or_create("dragon"); const char *email = config_get("monster.email"); f = addfaction(email, NULL, rc, default_locale); renumber_faction(f, MONSTER_ID); faction_setname(f, "Monster"); fset(f, FFL_NPC | FFL_NOIDLEOUT); } return f; } faction *get_monsters(void) { return get_or_create_monsters(); } void save_special_items(unit *usrc) { unit *u; region *r = usrc->region; faction *fm = get_monsters(); static const race *rc_ghost; static int cache; if (rc_changed(&cache)) { rc_ghost = get_race(RC_TEMPLATE); } for (u = r->units; u; u = u->next) { if (u->faction == fm) { give_special_items(u, &usrc->items); return; } } u = create_unit(r, fm, 1, rc_ghost, 0, NULL, NULL); unit_setname(u, unit_getname(usrc)); if (usrc->number > 1) { /* some units have plural names, it would be neat if they aren't single: */ scale_number(u, 2); } unit_convert_race(u, rc_ghost, "ghost"); give_special_items(u, &usrc->items); } void destroyfaction(faction ** fp) { faction * f = *fp; unit *u = f->units; *fp = f->next; f->next = dead_factions; dead_factions = f; if (f->spellbook) { spellbook_clear(f->spellbook); free(f->spellbook); f->spellbook = 0; } if (f->seen_factions) { selist_free(f->seen_factions); f->seen_factions = 0; } while (u) { /* give away your stuff, to ghosts if you cannot (quest items) */ if (u->items) { region *r = u->region; int result = gift_items(u, GIFT_FRIENDS | GIFT_PEASANTS); if (result != 0) { save_special_items(u); } if (r->land && !!playerrace(u_race(u))) { const race *rc = u_race(u); int m = rmoney(r); /* Personen gehen nur an die Bauern, wenn sie auch von dort * stammen */ if ((rc->ec_flags & ECF_REC_ETHEREAL) == 0) { int p = rpeasants(u->region); int h = rhorses(u->region); item *itm; p += (int)(u->number * rc->recruit_multi); for (itm = u->items; itm; itm = itm->next) { if (itm->type->flags & ITF_ANIMAL) { h += itm->number; } } rsetpeasants(r, p); rsethorses(r, h); } m += get_money(u); rsetmoney(r, m); } } set_number(u, 0); u = u->nextF; } handle_event(f->attribs, "destroy", f); if (f->alliance) { setalliance(f, NULL); } fset(f, FFL_QUIT); f->_alive = false; funhash(f); /* units of other factions that were disguised as this faction * have their disguise replaced by ordinary faction hiding. */ if (rule_stealth_other()) { region *rc; for (rc = regions; rc; rc = rc->next) { for (u = rc->units; u; u = u->next) { if (u->attribs && get_otherfaction(u) == f) { a_removeall(&u->attribs, &at_otherfaction); if (rule_stealth_anon()) { fset(u, UFL_ANON_FACTION); } } } } } } void renumber_faction(faction * f, int no) { funhash(f); f->no = no; fhash(f); } void update_interval(struct faction *f, struct region *r) { if (r == NULL || f == NULL) return; if (f->first == NULL || f->first->index > r->index) { f->first = r; } if (f->last == NULL || f->last->index <= r->index) { f->last = r; } } const char *faction_getname(const faction * self) { return self->name ? self->name : ""; } void faction_setname(faction * self, const char *name) { free(self->name); if (name) self->name = str_strdup(name); } const char *faction_getemail(const faction * self) { return self->email ? self->email : ""; } void faction_setemail(faction * self, const char *email) { free(self->email); if (email) self->email = str_strdup(email); else self->email = NULL; } const char *faction_getbanner(const faction * f) { if (f->banner_id > 0) { return dbstring_load(f->banner_id, NULL); } return NULL; } void faction_setbanner(faction * f, const char *banner) { f->banner_id = dbstring_save(banner); } const char *faction_getpassword(const faction *f) { if (f->password_id > 0) { return dbstring_load(f->password_id, NULL); } return NULL; } void faction_setpassword(faction * f, const char *pwhash) { if (pwhash) { f->password_id = dbstring_save(pwhash); } else { f->password_id = 0; } } bool valid_race(const struct faction *f, const struct race *rc) { if (f->race == rc) return true; else { return rc_otherrace(f->race) == rc; } } struct alliance *f_get_alliance(const struct faction *f) { if (f->alliance && !(f->alliance->flags & ALF_NON_ALLIED)) { return f->alliance; } return NULL; } struct spellbook * faction_get_spellbook(struct faction *f) { if (f->spellbook) { return f->spellbook; } if (f->magiegebiet != M_GRAY) { return get_spellbook(magic_school[f->magiegebiet]); } return NULL; } static int allied_skillcount(const faction * f, skill_t sk) { int num = 0; alliance *a = f_get_alliance(f); selist *members = a->members; int qi; for (qi = 0; members; selist_advance(&members, &qi, 1)) { faction *m = (faction *)selist_get(members, qi); num += faction_count_skill(m, sk); } return num; } static int allied_skilllimit(const faction * f, skill_t sk) { static int value = -1; if (value < 0) { value = config_get_int("alliance.skilllimit", 0); } return value; } int faction_count_skill(faction * f, skill_t sk) { int n = 0; unit *u; for (u = f->units; u; u = u->nextF) { if (has_skill(u, sk)) { if (!is_familiar(u)) { n += u->number; } } } return n; } int faction_skill_limit(const faction * f, skill_t sk) { int m = INT_MAX; int al = allied_skilllimit(f, sk); if (al > 0) { if (sk != SK_ALCHEMY && sk != SK_MAGIC) return INT_MAX; if (f_get_alliance(f)) { int sc, fl, ac = listlen(f->alliance->members); /* number of factions */ assert(ac > 0); fl = (al + ac - 1) / ac; /* faction limit, rounded up */ /* the faction limit may not be achievable because it would break the alliance-limit */ sc = al - allied_skillcount(f, sk); if (sc <= 0) return 0; return fl; } } if (sk == SK_MAGIC) { m = max_magicians(f); } else if (sk == SK_ALCHEMY) { m = config_get_int("rules.maxskills.alchemy", 3); } return m; } void remove_empty_factions(void) { faction **fp; for (fp = &factions; *fp;) { faction *f = *fp; if (!(f->_alive && f->units!=NULL) && !fval(f, FFL_NOIDLEOUT)) { destroyfaction(fp); } else { fp = &(*fp)->next; } } } bool faction_alive(const faction *f) { assert(f); return f->_alive || (f->flags&FFL_NPC); } void faction_getorigin(const faction * f, int id, int *x, int *y) { origin *ur; assert(f && x && y); for (ur = f->origin; ur; ur = ur->next) { if (ur->id == id) { *x = ur->x; *y = ur->y; break; } } } static origin *new_origin(int id, int x, int y) { origin *ur = (origin *)calloc(1, sizeof(origin)); if (!ur) abort(); ur->id = id; ur->x = x; ur->y = y; return ur; } void faction_setorigin(faction * f, int id, int x, int y) { origin **urp; assert(f != NULL); for (urp = &f->origin; *urp; urp = &(*urp)->next) { origin *ur = *urp; if (ur->id == id) { ur->x += x; ur->y += y; return; } } *urp = new_origin(id, x, y); } int count_faction(const faction * f, int flags) { unit *u; int n = 0; for (u = f->units; u; u = u->nextF) { const race *rc = u_race(u); int x = u->number; if (f->race != rc) { if (!playerrace(rc)) { if (flags&COUNT_MONSTERS) { n += x; } } else if (flags&COUNT_MIGRANTS) { if (!is_cursed(u->attribs, &ct_slavery)) { n += x; } } } else if (flags&COUNT_DEFAULT) { n += x; } } return n; } int count_migrants(const faction * f) { return count_faction(f, COUNT_MIGRANTS); } int count_maxmigrants(const faction * f) { int formula = rc_migrants_formula(f->race); if (formula == MIGRANTS_LOG10) { int nsize = f->num_people; if (nsize > 0) { int x = (int)(log10(nsize / 50.0) * 20); if (x < 0) x = 0; return x; } } return 0; } int max_magicians(const faction * f) { static int rule, config, rc_cache; static const race *rc_elf; int m; if (config_changed(&config)) { rule = config_get_int("rules.maxskills.magic", 3); } m = rule; if (rc_changed(&rc_cache)) { rc_elf = get_race(RC_ELF); } if (f->race == rc_elf) { ++m; } return m; } int writepasswd(void) { FILE *F; char zText[128]; path_join(basepath(), "passwd", zText, sizeof(zText)); F = fopen(zText, "w"); if (!F) { perror(zText); } else { faction *f; log_info("writing passwords..."); for (f = factions; f; f = f->next) { fprintf(F, "%s:%s:%s:%d\n", itoa36(f->no), faction_getemail(f), faction_getpassword(f), f->uid); } fclose(F); return 0; } return 1; } void log_dead_factions(void) { if (dead_factions) { const char *logname = config_get("game.deadlog"); if (logname) { FILE *F; char path[PATH_MAX]; join_path(basepath(), logname, path, sizeof(path)); F = fopen(path, "at"); if (F) { faction *f; for (f = dead_factions; f; f = f->next) { fprintf(F, "%d\t%d\t%d\t%s\t%s\t%s\n", turn, f->lastorders, f->age, itoa36(f->no), f->email, f->name); } fclose(F); } } } } void free_factions(void) { free_flist(&factions); free_flist(&dead_factions); } faction *faction_create(int no) { faction *f = (faction *)calloc(1, sizeof(faction)); if (!f) abort(); f->no = no; fhash(f); return f; }