server/src/spy.c

473 lines
14 KiB
C

/*
Copyright (c) 1998-2019, Enno Rehling <enno@eressea.de>
Katja Zedel <katze@felidae.kn-bremen.de
Christian Schlittchen <corwin@amber.kn-bremen.de>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
**/
#ifdef _MSC_VER
#include <platform.h>
#endif
#include "spy.h"
#include "guard.h"
#include "laws.h"
#include "move.h"
#include "reports.h"
#include "study.h"
/* kernel includes */
#include <kernel/attrib.h>
#include <kernel/config.h>
#include <kernel/item.h>
#include <kernel/faction.h>
#include <kernel/messages.h>
#include <kernel/order.h>
#include <kernel/race.h>
#include <kernel/region.h>
#include <kernel/ship.h>
#include <kernel/terrain.h>
#include <kernel/unit.h>
/* attributes includes */
#include <attributes/otherfaction.h>
#include <attributes/racename.h>
#include <attributes/stealth.h>
/* util includes */
#include <util/base36.h>
#include <util/param.h>
#include <util/parser.h>
#include <util/rand.h>
#include <util/rng.h>
#include <util/strings.h>
/* libc includes */
#include <assert.h>
#include <ctype.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.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)
{
char status[32];
report_status_depr(target, u->faction->locale, status, sizeof(status));
ADDMSG(&u->faction->msgs, msg_message("spyreport", "spy target status", u,
target, status));
if (spy > 20) {
magic_t mtype = unit_get_magic(target);
/* for mages, spells and magic school */
if (mtype != M_GRAY) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_mage", "spy target type", u,
target, magic_school[mtype]));
}
}
if (spy > 6) {
faction *fv = visible_faction(u->faction, target);
if (fv && fv != target->faction) {
/* true faction */
ADDMSG(&u->faction->msgs, msg_message("spyreport_faction",
"spy target faction", u, target, target->faction));
add_seen_faction(u->faction, 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 {
str_strlcat(buf, ", ", sizeof(buf));
}
str_strlcat(buf, (const char *)skillname((skill_t)sv->id, u->faction->locale),
sizeof(buf));
str_strlcat(buf, " ", sizeof(buf));
str_strlcat(buf, itoa10(eff_skill(target, sv, target->region)),
sizeof(buf));
}
}
if (found) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_skills", "spy target skills", u,
target, buf));
}
if (target->items) {
ADDMSG(&u->faction->msgs, msg_message("spyreport_items", "spy target items", u,
target, target->items));
}
}
}
int spy_cmd(unit * u, struct order *ord)
{
unit *target;
int spy, observe;
double spychance, observechance;
region *r = u->region;
init_order_depr(ord);
getunit(r, u->faction, &target);
if (!target) {
ADDMSG(&u->faction->msgs,
msg_feedback(u, u->thisorder, "feedback_unit_not_found", ""));
return 0;
}
if (effskill(u, SK_SPY, NULL) < 1) {
cmistake(u, u->thisorder, 39, MSG_EVENT);
return 0;
}
/* Die Grundchance fuer einen erfolgreichen Spionage-Versuch ist 10%.
* Fuer jeden Talentpunkt, den das Spionagetalent das Tarnungstalent
* des Opfers uebersteigt, erhoeht sich dieses um 5%*/
spy = effskill(u, SK_SPY, NULL) - effskill(target, SK_STEALTH, r);
spychance = 0.1 + fmax(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 = effskill(target, SK_PERCEPTION, r)
- (effskill(u, SK_STEALTH, NULL) + effskill(u, SK_SPY, NULL) / 2);
if (invisible(u, target) >= u->number) {
if (observe > 0) observe = 0;
}
/* Anschliessend wird - unabhaengig vom Erfolg - gewuerfelt, ob der
* Spionageversuch bemerkt wurde. Die Wahrscheinlich dafuer ist (100 -
* SpionageSpion*5 + WahrnehmungOpfer*2)%. */
observechance = 1.0 - (effskill(u, SK_SPY, NULL) * 0.05)
+ (effskill(target, SK_PERCEPTION, NULL) * 0.02);
if (chance(observechance)) {
ADDMSG(&target->faction->msgs, msg_message("spydetect",
"spy target", observe > 0 ? u : NULL, target));
}
return 0;
}
static bool can_set_factionstealth(const unit * u, const 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))
return true;
}
}
ru = ru->next;
}
}
mu = mu->nextF;
}
return true;
}
void set_factionstealth(unit *u, faction *f) {
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)
{
char token[64];
const char *s;
init_order_depr(ord);
s = gettoken(token, sizeof(token));
/* Tarne ohne Parameter: Setzt maximale Tarnung */
if (s == NULL || *s == 0) {
u_seteffstealth(u, -1);
return 0;
}
if (isdigit(*(const unsigned char *)s)) {
/* Tarnungslevel setzen */
int level = atoi((const char *)s);
if (level > effskill(u, SK_STEALTH, NULL)) {
ADDMSG(&u->faction->msgs, msg_feedback(u, ord, "error_lowstealth", ""));
return 0;
}
u_seteffstealth(u, level);
return 0;
}
if (skill_enabled(SK_STEALTH)) { /* hack! E3 erlaubt keine Tarnung */
const race *trace;
trace = findrace(s, u->faction->locale);
if (trace) {
/* demons can cloak as other player-races */
if (u_race(u) == get_race(RC_DAEMON)) {
if (playerrace(trace)) {
u->irace = trace;
if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs)) {
set_racename(&u->attribs, NULL);
}
}
return 0;
}
/* Singdrachen koennen sich nur als Drachen tarnen */
else if (u_race(u) == get_race(RC_SONGDRAGON)
|| u_race(u) == get_race(RC_BIRTHDAYDRAGON)) {
if (trace == get_race(RC_SONGDRAGON) || trace == get_race(RC_FIREDRAGON)
|| trace == get_race(RC_DRAGON) || trace == get_race(RC_WYRM)) {
u->irace = trace;
if (u_race(u)->flags & RCF_SHAPESHIFTANY && get_racename(u->attribs))
set_racename(&u->attribs, NULL);
}
return 0;
}
/* Daemomen und Illusionsparteien koennen 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] */
s = gettoken(token, sizeof(token));
if (rule_stealth_anon()) {
if (!s || *s == 0) {
u->flags |= UFL_ANON_FACTION;
break;
}
else if (findparam(s, u->faction->locale) == P_NOT) {
u->flags &= ~UFL_ANON_FACTION;
break;
}
}
if (rule_stealth_other()) {
if (get_keyword(s, u->faction->locale) == K_NUMBER) {
int nr = -1;
s = gettoken(token, sizeof(token));
if (s) {
nr = atoi36(s);
}
if (!s || *s == 0 || nr == u->faction->no) {
a_removeall(&u->attribs, &at_otherfaction);
break;
}
else {
struct faction *f = findfaction(nr);
if (f == NULL || !can_set_factionstealth(u, f)) {
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 top_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 = effskill(u, sk, NULL);
if (value < s) value = s;
}
}
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(&u->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 */
}
void sink_ship(ship * sh)
{
unit *u;
region *r;
message *sink_msg = NULL;
faction *f;
assert(sh && sh->region);
r = sh->region;
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;
f->flags &= ~FFL_SELECT;
}
}
for (f = NULL, u = r->units; u; u = u->next) {
/* inform this faction about the sinking ship: */
if (u->ship == sh) {
if (f != u->faction) {
f = u->faction;
if (!(f->flags & FFL_SELECT)) {
f->flags |= FFL_SELECT;
if (sink_msg == NULL) {
sink_msg = msg_message("sink_msg", "ship region", sh, r);
}
add_message(&f->msgs, sink_msg);
}
}
}
else if (f != NULL) {
break;
}
}
if (sink_msg) {
msg_release(sink_msg);
}
}
int sabotage_cmd(unit * u, struct order *ord)
{
const char *s;
param_t p;
ship *sh;
unit *u2;
int skdiff = INT_MAX;
assert(u);
assert(ord);
init_order_depr(ord);
s = getstrtoken();
p = findparam(s, u->faction->locale);
init_order_depr(NULL);
switch (p) {
case P_SHIP:
sh = u->ship;
if (!sh) {
cmistake(u, u->thisorder, 144, MSG_EVENT);
return 0;
}
u2 = ship_owner(sh);
if (u2->faction != u->faction) {
skdiff =
effskill(u, SK_SPY, NULL) - top_skill(u->region, u2->faction, sh, SK_PERCEPTION);
}
if (try_destruction(u, u2, sh, skdiff)) {
sink_ship(sh);
/* finally, get rid of the ship */
remove_ship(&sh->region->ships, sh);
}
break;
default:
cmistake(u, u->thisorder, 9, MSG_EVENT);
return 0;
}
return 0;
}