server/src/spy.c

515 lines
14 KiB
C
Raw Blame History

/*
Copyright (c) 1998-2010, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#include <platform.h>
#include <kernel/config.h>
#include "spy.h"
#include "laws.h"
/* kernel includes */
#include <kernel/reports.h>
#include <kernel/item.h>
#include <kernel/faction.h>
#include <kernel/magic.h>
#include <kernel/message.h>
#include <kernel/move.h>
#include <kernel/order.h>
#include <kernel/race.h>
#include <kernel/region.h>
#include <kernel/ship.h>
#include <kernel/skill.h>
#include <kernel/terrain.h>
#include <kernel/unit.h>
/* attributes includes */
#include <attributes/racename.h>
#include <attributes/otherfaction.h>
/* util includes */
#include <util/attrib.h>
#include <util/base36.h>
#include <util/parser.h>
#include <quicklist.h>
#include <util/rand.h>
#include <util/rng.h>
/* libc includes */
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* in spy steht der Unterschied zwischen Wahrnehmung des Opfers und
* Spionage des Spions */
void spy_message(int spy, const unit * u, const unit * target)
{
const char *str = report_kampfstatus(target, u->faction->locale);
ADDMSG(&u->faction->msgs, msg_message("spyreport", "spy target status", u,
target, str));
if (spy > 20) {
sc_mage *mage = get_mage(target);
/* bei Magiern Zauberspr<70>che und Magiegebiet */
if (mage) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_mage", "target type",
target, magic_school[mage->magietyp]));
}
}
if (spy > 6) {
faction *fv = visible_faction(u->faction, target);
if (fv && fv != target->faction) {
/* wahre Partei */
ADDMSG(&u->faction->msgs, msg_message("spyreport_faction",
"target faction", target, target->faction));
ql_set_insert(&u->faction->seen_factions, target->faction);
}
}
if (spy > 0) {
int first = 1;
int found = 0;
skill *sv;
char buf[4096];
buf[0] = 0;
for (sv = target->skills; sv != target->skills + target->skill_size; ++sv) {
if (sv->level > 0) {
found++;
if (first == 1) {
first = 0;
} else {
strncat(buf, ", ", sizeof(buf));
}
strncat(buf, (const char *)skillname((skill_t)sv->id, u->faction->locale),
sizeof(buf));
strncat(buf, " ", sizeof(buf));
strncat(buf, itoa10(eff_skill(target, (skill_t)sv->id, target->region)),
sizeof(buf));
}
}
if (found) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_skills", "target skills",
target, buf));
}
if (target->items) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_items", "target items",
target, target->items));
}
}
}
int spy_cmd(unit * u, struct order *ord)
{
unit *target;
int spy, observe;
double spychance, observechance;
region *r = u->region;
init_tokens(ord);
skip_token();
target = getunit(r, u->faction);
if (!target) {
ADDMSG(&u->faction->msgs, msg_feedback(u, u->thisorder,
"feedback_unit_not_found", ""));
return 0;
}
if (!can_contact(r, u, target)) {
cmistake(u, u->thisorder, 24, MSG_EVENT);
return 0;
}
if (eff_skill(u, SK_SPY, r) < 1) {
cmistake(u, u->thisorder, 39, MSG_EVENT);
return 0;
}
/* Die Grundchance f<>r einen erfolgreichen Spionage-Versuch ist 10%.
* F<>r jeden Talentpunkt, den das Spionagetalent das Tarnungstalent
* des Opfers <20>bersteigt, erh<72>ht sich dieses um 5%*/
spy = eff_skill(u, SK_SPY, r) - eff_skill(target, SK_STEALTH, r);
spychance = 0.1 + MAX(spy * 0.05, 0.0);
if (chance(spychance)) {
produceexp(u, SK_SPY, u->number);
spy_message(spy, u, target);
} else {
ADDMSG(&u->faction->msgs, msg_message("spyfail", "spy target", u, target));
}
/* der Spion kann identifiziert werden, wenn das Opfer bessere
* Wahrnehmung als das Ziel Tarnung + Spionage/2 hat */
observe = eff_skill(target, SK_PERCEPTION, r)
- (effskill(u, SK_STEALTH) + eff_skill(u, SK_SPY, r) / 2);
if (invisible(u, target) >= u->number) {
observe = MIN(observe, 0);
}
/* Anschlie<69>end wird - unabh<62>ngig vom Erfolg - gew<65>rfelt, ob der
* Spionageversuch bemerkt wurde. Die Wahrscheinlich daf<61>r ist (100 -
* SpionageSpion*5 + WahrnehmungOpfer*2)%. */
observechance = 1.0 - (eff_skill(u, SK_SPY, r) * 0.05)
+ (eff_skill(target, SK_PERCEPTION, r) * 0.02);
if (chance(observechance)) {
ADDMSG(&target->faction->msgs, msg_message("spydetect",
"spy target", observe > 0 ? u : NULL, target));
}
return 0;
}
void set_factionstealth(unit * u, faction * f)
{
region *lastr = NULL;
/* for all units mu of our faction, check all the units in the region
* they are in, if their visible faction is f, it's ok. use lastr to
* avoid testing the same region twice in a row. */
unit *mu = u->faction->units;
while (mu != NULL) {
if (mu->number && mu->region != lastr) {
unit *ru = mu->region->units;
lastr = mu->region;
while (ru != NULL) {
if (ru->number) {
faction *fv = visible_faction(f, ru);
if (fv == f) {
if (cansee(f, lastr, ru, 0))
break;
}
}
ru = ru->next;
}
if (ru != NULL)
break;
}
mu = mu->nextF;
}
if (mu != NULL) {
attrib *a = a_find(u->attribs, &at_otherfaction);
if (!a)
a = a_add(&u->attribs, make_otherfaction(f));
else
a->data.v = f;
}
}
int setstealth_cmd(unit * u, struct order *ord)
{
const char *s;
int level, rule;
const race *trace;
init_tokens(ord);
skip_token();
s = getstrtoken();
/* Tarne ohne Parameter: Setzt maximale Tarnung */
if (s == NULL || *s == 0) {
u_seteffstealth(u, -1);
return 0;
}
if (isdigit(s[0])) {
/* Tarnungslevel setzen */
level = atoi((const char *)s);
if (level > effskill(u, SK_STEALTH)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_lowstealth", ""));
return 0;
}
u_seteffstealth(u, level);
return 0;
}
trace = findrace(s, u->faction->locale);
if (trace) {
/* D<>monen k<>nnen sich nur als andere Spielerrassen tarnen */
if (u_race(u) == new_race[RC_DAEMON]) {
race_t allowed[] = { RC_DWARF, RC_ELF, RC_ORC, RC_GOBLIN, RC_HUMAN,
RC_TROLL, RC_DAEMON, RC_INSECT, RC_HALFLING, RC_CAT, RC_AQUARIAN,
NORACE
};
int i;
for (i = 0; allowed[i] != NORACE; ++i)
if (new_race[allowed[i]] == trace)
break;
if (new_race[allowed[i]] == trace) {
u->irace = trace;
if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs))
set_racename(&u->attribs, NULL);
}
return 0;
}
/* Singdrachen k<>nnen sich nur als Drachen tarnen */
if (u_race(u) == new_race[RC_SONGDRAGON]
|| u_race(u) == new_race[RC_BIRTHDAYDRAGON]) {
if (trace == new_race[RC_SONGDRAGON] || trace == new_race[RC_FIREDRAGON]
|| trace == new_race[RC_DRAGON] || trace == new_race[RC_WYRM]) {
u->irace = trace;
if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs))
set_racename(&u->attribs, NULL);
}
return 0;
}
/* D<>momen und Illusionsparteien k<>nnen sich als andere race tarnen */
if (u_race(u)->flags & RCF_SHAPESHIFT) {
if (playerrace(trace)) {
u->irace = trace;
if ((u_race(u)->flags & RCF_SHAPESHIFTANY) && get_racename(u->attribs))
set_racename(&u->attribs, NULL);
}
}
return 0;
}
switch (findparam(s, u->faction->locale)) {
case P_FACTION:
/* TARNE PARTEI [NICHT|NUMMER abcd] */
rule = rule_stealth_faction();
if (!rule) {
/* TARNE PARTEI is disabled */
break;
}
s = getstrtoken();
if (rule&1) {
if (!s || *s == 0) {
fset(u, UFL_ANON_FACTION);
break;
} else if (findparam(s, u->faction->locale) == P_NOT) {
freset(u, UFL_ANON_FACTION);
break;
}
}
if (rule&2) {
if (findkeyword(s, u->faction->locale) == K_NUMBER) {
const char *s2 = (const char *)getstrtoken();
int nr = -1;
if (s2) {
nr = atoi36(s2);
}
if (!s2 || *s2 == 0 || nr == u->faction->no) {
a_removeall(&u->attribs, &at_otherfaction);
break;
} else {
struct faction *f = findfaction(nr);
if (f == NULL) {
cmistake(u, ord, 66, MSG_EVENT);
break;
} else {
set_factionstealth(u, f);
break;
}
}
}
}
cmistake(u, ord, 289, MSG_EVENT);
break;
case P_ANY:
case P_NOT:
/* TARNE ALLES (was nicht so alles geht?) */
u_seteffstealth(u, -1);
break;
default:
if (u_race(u)->flags & RCF_SHAPESHIFTANY) {
set_racename(&u->attribs, s);
} else {
cmistake(u, ord, 289, MSG_EVENT);
}
}
return 0;
}
static int crew_skill(region * r, faction * f, ship * sh, skill_t sk)
{
int value = 0;
unit *u;
for (u = r->units; u; u = u->next) {
if (u->ship == sh && u->faction == f) {
int s = eff_skill(u, sk, r);
value = MAX(s, value);
}
}
return value;
}
static int try_destruction(unit * u, unit * u2, const ship * sh, int skilldiff)
{
const char *destruction_success_msg = "destroy_ship_0";
const char *destruction_failed_msg = "destroy_ship_1";
const char *destruction_detected_msg = "destroy_ship_2";
const char *detect_failure_msg = "destroy_ship_3";
const char *object_destroyed_msg = "destroy_ship_4";
if (skilldiff == 0) {
/* tell the unit that the attempt failed: */
ADDMSG(&u->faction->msgs, msg_message(destruction_failed_msg, "ship unit",
sh, u));
/* tell the enemy about the attempt: */
if (u2) {
ADDMSG(&u2->faction->msgs, msg_message(detect_failure_msg, "ship", sh));
}
return 0;
} else if (skilldiff < 0) {
/* tell the unit that the attempt was detected: */
ADDMSG(&u2->faction->msgs, msg_message(destruction_detected_msg,
"ship unit", sh, u));
/* tell the enemy whodunit: */
if (u2) {
ADDMSG(&u2->faction->msgs, msg_message(detect_failure_msg, "ship", sh));
}
return 0;
} else {
/* tell the unit that the attempt succeeded */
ADDMSG(&u->faction->msgs, msg_message(destruction_success_msg, "ship unit",
sh, u));
if (u2) {
ADDMSG(&u2->faction->msgs, msg_message(object_destroyed_msg, "ship", sh));
}
}
return 1; /* success */
}
static void sink_ship(region * r, ship * sh, const char *name, unit * saboteur)
{
unit **ui, *u;
region *safety = r;
int i;
direction_t d;
double probability = 0.0;
message *sink_msg = NULL;
faction *f;
for (f = NULL, u = r->units; u; u = u->next) {
/* slight optimization to avoid dereferencing u->faction each time */
if (f != u->faction) {
f = u->faction;
freset(f, FFL_SELECT);
}
}
/* figure out what a unit's chances of survival are: */
if (!fval(r->terrain, SEA_REGION)) {
probability = CANAL_SWIMMER_CHANCE;
} else {
for (d = 0; d != MAXDIRECTIONS; ++d) {
region *rn = rconnect(r, d);
if (!fval(rn->terrain, SEA_REGION) && !move_blocked(NULL, r, rn)) {
safety = rn;
probability = OCEAN_SWIMMER_CHANCE;
break;
}
}
}
for (ui = &r->units; *ui; ui = &(*ui)->next) {
unit *u = *ui;
/* inform this faction about the sinking ship: */
if (!fval(u->faction, FFL_SELECT)) {
fset(u->faction, FFL_SELECT);
if (sink_msg == NULL) {
sink_msg = msg_message("sink_msg", "ship region", sh, r);
}
add_message(&f->msgs, sink_msg);
}
if (u->ship == sh) {
int dead = 0;
message *msg;
/* if this fails, I misunderstood something: */
for (i = 0; i != u->number; ++i)
if (chance(probability))
++dead;
if (dead != u->number) {
/* she will live. but her items get stripped */
if (dead > 0) {
msg =
msg_message("sink_lost_msg", "dead region unit", dead, safety, u);
} else {
msg = msg_message("sink_saved_msg", "region unit", safety, u);
}
leave_ship(u);
if (r != safety) {
setguard(u, GUARD_NONE);
}
while (u->items) {
i_remove(&u->items, u->items);
}
move_unit(u, safety, NULL);
} else {
msg = msg_message("sink_lost_msg", "dead region unit", dead, NULL, u);
}
add_message(&u->faction->msgs, msg);
msg_release(msg);
if (dead == u->number) {
/* the poor creature, she dies */
if (remove_unit(ui, u) != 0) {
ui = &u->next;
}
}
}
}
if (sink_msg)
msg_release(sink_msg);
/* finally, get rid of the ship */
remove_ship(&sh->region->ships, sh);
}
int sabotage_cmd(unit * u, struct order *ord)
{
const char *s;
int i;
ship *sh;
unit *u2;
char buffer[DISPLAYSIZE];
region *r = u->region;
int skdiff;
init_tokens(ord);
skip_token();
s = getstrtoken();
i = findparam(s, u->faction->locale);
switch (i) {
case P_SHIP:
sh = u->ship;
if (!sh) {
cmistake(u, u->thisorder, 144, MSG_EVENT);
return 0;
}
u2 = ship_owner(sh);
skdiff =
eff_skill(u, SK_SPY, r) - crew_skill(r, u2->faction, sh, SK_PERCEPTION);
if (try_destruction(u, u2, sh, skdiff)) {
sink_ship(r, sh, buffer, u);
}
break;
default:
cmistake(u, u->thisorder, 9, MSG_EVENT);
return 0;
}
return 0;
}