blob: 747714b792152ecebcf32289f863cb2b866fc3f9 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// npc/combat.c -- NPC-spezifische Kampffunktionen
//
// $Id: combat.c 9488 2016-02-19 21:24:04Z Arathorn $
#pragma strong_types
#pragma save_types
#pragma range_check
#pragma no_clone
#pragma pedantic
inherit "std/living/combat";
#include <combat.h>
#include <language.h>
#include <properties.h>
#include <wizlevels.h>
#include <health.h>
#include <new_skills.h>
#define NEED_PROTOTYPES 1
#include <living/life.h>
#undef NEED_PROTOTYPES
#define HB_CHECK 7
#define ME this_object()
#define STATMASTER "/p/service/rochus/guildstat/master"
nosave int heartbeat, beatcount;
private void catch_up_hbs();
protected void create() {
::create();
beatcount=1;
heartbeat=1;
}
protected void create_super() {
set_next_reset(-1);
}
// aktuelles Lebewesen, fuer das dieser NPC gerade taetig ist. Default:
// Spieler, bei dem er als Helfer-NPC registriert ist.
public object QueryUser()
{
mixed helperdata = QueryProp(P_HELPER_NPC);
if (pointerp(helperdata) && objectp(helperdata[0]))
return helperdata[0];
return 0;
}
// ggf. Feinde expiren. Soll das Problem verringern, dass Spieler nach Tagen
// erst die Meldung kriegen, dass der NPC sie nicht mehr jagt, wenn der HB
// reaktivert wird.
void reset() {
// ggf. die abgeschalteten HBs nachholen.
if (!heartbeat)
catch_up_hbs();
// ggf. P_ENEMY_DAMAGE zuruecksetzen
ResetEnemyDamage();
}
static void _set_max_hp(int i) {
Set(P_MAX_HP,i);
SetProp(P_HP,i);
}
static void _set_max_sp(int i) {
Set(P_MAX_SP,i);
SetProp(P_SP,i);
}
// Check-Funktion fuer die P_NO_ATTACK-QueryMethod
static mixed _check_immortality()
{
int t;
if ( !(t = Query("time_to_mortality")) || time() > t ){
// Zeit ist abgelaufen - wieder angreifbar machen
Set( P_NO_ATTACK, 0, F_QUERY_METHOD );
heartbeat = 1;
beatcount = 1;
set_heart_beat(1);
return 0;
}
// der NPC ist noch unangreifbar
return break_string( capitalize(name( WER, 1 )) + " versteckt sich hinter "
"einem Fehler im Raum-Zeit-Gefuege und entgeht so "
"voruebergehend allen Angriffen.", 78 );
}
// wenn der HeartBeat buggt, wird diese Funktion vom Master aufgerufen
public void make_immortal()
{
// fuer 5 Minuten unangreifbar machen
Set( P_NO_ATTACK, #'_check_immortality, F_QUERY_METHOD );
Set( "time_to_mortality", time() + 300, F_VALUE );
// damit die Spieler keinen Vorteil durch den Bug haben, heilen
heal_self(10000);
// da nun der Heartbeat abgeschaltet ist und normalerweise erst
// reaktiviert wird, sobald jemand nach 5min P_NO_ATTACK abfragt, muss man
// aber auf Viecher achten, die immer nen Heartbeat haben wollen. In dem
// fall per call_out selber die Prop abfragen.
if (QueryProp(P_HB))
call_out(#'QueryProp, 301, P_NO_ATTACK);
}
// Damit NPCs gegeneinander weiterkaempfen, auch wenn kein Spieler
// in der Naehe ist:
static int _query_hb()
{
// TODO: return InFight() || Query(P_HB, F_VALUE), sobald InFight()
// geaendert.
return (InFight() || Query(P_HB,F_VALUE)) ? 1 : 0;
}
#define SPELL_TOTALRATE 0
#define SPELL_DAMAGE 1
#define SPELL_TEXT_FOR_ENEMY 2
#define SPELL_TEXT_FOR_OTHERS 3
#define SPELL_DAMTYPE 4
#define SPELL_FUNC 5
#define SPELL_ARG 6
varargs int AddSpell(int rate, int damage, string TextForEnemy,
string TextForOthers, string|string* dam_type, string func,
int|mapping spellarg) {
mixed *spells;
int total_rates;
if (rate<0 || damage<=0 || !stringp(TextForEnemy) ||
!stringp(TextForOthers))
return 0;
if (stringp(dam_type))
dam_type = ({dam_type});
else if (!pointerp(dam_type))
dam_type = ({DT_MAGIC});
// Tatsaechlich ist es immer ein nicht-physischer Angriff, wenn spellarg ein
// int ist, weil wenn spellarg==0 ist der Default nicht-physischer Angriff
// und bei spellarg!=0 auch. Nur mit einem mapping kann man einen phys.
// Angriff erzeugen.
if (intp(spellarg))
spellarg = ([SP_PHYSICAL_ATTACK: 0]);
// Falls vorhanden, alte Syntax auf die von replace_personal() anpassen,
// die im heart_beat() beim Ausgeben der Meldung verwendet wird.
if ( strstr(TextForOthers, "@", 0) != -1 )
{
// Zeichen nach @WER & Co in runde Klammern einschliessen, damit es als
// Sub-Pattern im Ausgabestring wiederholt werden kann. Ansonsten wuerde
// es mit ersetzt.
TextForOthers = regreplace(TextForOthers, "@WER([^1-9])", "@WER1\\1", 1);
TextForOthers = regreplace(TextForOthers, "@WESSEN([^1-9])",
"@WESSEN1\\1", 1);
TextForOthers = regreplace(TextForOthers, "@WEM([^1-9])", "@WEM1\\1", 1);
TextForOthers = regreplace(TextForOthers, "@WEN([^1-9])", "@WEN1\\1", 1);
}
total_rates=Query("npc:total_rates")+rate;
spells=Query(P_SPELLS);
if (!pointerp(spells))
spells=({});
spells+=({({total_rates, damage, TextForEnemy, TextForOthers,
dam_type, func, spellarg})});
Set(P_SPELLS,spells);
Set("npc:total_rates",total_rates);
return 1;
}
int AutoAttack(object ob) {
mixed m;
if (!query_once_interactive(ob))
return 0;
if (mappingp(m=QueryProp(P_AGGRESSIVE))) {
mixed *ind,x,z;
float f;
int i,n;
ind=m_indices(m)-({0});n=0;f=0.0;
for (i=sizeof(ind)-1;i>=0;i--) {
x=ind[i];
if ((z=m[x][ob->QueryProp(x)]) || (z=m[x][0])) {
f=f+(float)z;
n++;
}
}
if (n)
m=f/((float)n);
else
m=m[0];
}
if (((int)(100*(m+ob->QueryProp(P_AGGRESSIVE))))<=random(100))
return 0;
if (IS_LEARNER(ob)
&& (ob->QueryProp(P_INVIS)
|| ob->QueryProp(P_WANTS_TO_LEARN)))
return 0;
return 1;
}
void SpellAttack(object enemy) {
}
#if 0
TJ(string s) {
object o;
if (o=find_player("jof"))
tell_object(o,sprintf("%O: %s\n",this_object(),s));
}
#else
#define TJ(x)
#endif
protected void heart_beat() {
int r,i;
mixed env,*spells, sinfo;
object enemy;
if ( --beatcount < 0 )
beatcount = 0;
if (!beatcount && !Query(P_HB)) {
if (!environment()) {
set_heart_beat(0);
heartbeat = 0;
if( clonep(this_object()) ) remove();
return;
}
if (!QueryProp(P_POISON)) {
// Spieler anwesend?
env = filter(all_inventory(environment()), #'query_once_interactive);
if (!sizeof(env)) {
// Nein, HBs abschalten.
set_heart_beat(0);
heartbeat=0;
TJ("OFF\n");
beatcount=HB_CHECK;
Set("npc:beat_off_num",absolute_hb_count());
return;
}
}
}
::heart_beat();
if (!ME)
return;
enemy=SelectEnemy();
if (QueryProp(P_AGGRESSIVE)
&& (!enemy || environment()!=environment(enemy))
&& !beatcount) {
beatcount=HB_CHECK;
env=filter(all_inventory(environment()),#'AutoAttack);
if (!sizeof(env))
return;
i=random(sizeof(env));
Kill(env[i]);
}
else if (!beatcount)
beatcount=HB_CHECK;
if (!objectp(enemy) ||QueryProp(P_DISABLE_ATTACK)>0)
return;
SpellAttack(enemy);
if (!pointerp(spells=Query(P_SPELLS))
|| !sizeof(spells)
|| !objectp(enemy=SelectEnemy())
|| environment(enemy)!=environment()
|| (QueryProp(P_DISABLE_ATTACK)>0)
|| random(100)>Query(P_SPELLRATE))
return;
r=random(Query("npc:total_rates"));
for (i=sizeof(spells)-1;(i>0 && spells[i-1][SPELL_TOTALRATE]>r);i--)
;
string akt_spell_mess = spells[i][SPELL_TEXT_FOR_ENEMY];
if ( sizeof(akt_spell_mess) )
tell_object(enemy, break_string(akt_spell_mess, 78));
akt_spell_mess = spells[i][SPELL_TEXT_FOR_OTHERS];
// Nur, wenn ueberhaupt eine Meldung gesetzt wurde, muss diese verarbeitet
// werden.
if (stringp(akt_spell_mess) && sizeof(akt_spell_mess))
{
akt_spell_mess = replace_personal(akt_spell_mess, ({enemy}), 1);
say(break_string(akt_spell_mess, 78),({enemy, this_object()}));
}
sinfo = deep_copy(spells[i][SPELL_ARG]);
if(!mappingp(sinfo))
sinfo=([ SI_MAGIC_TYPE :({ MT_ANGRIFF }) ]);
else if(!sinfo[SI_MAGIC_TYPE])
sinfo[ SI_MAGIC_TYPE]=({ MT_ANGRIFF });
if(!sinfo[SP_PHYSICAL_ATTACK] &&
(enemy->SpellDefend(this_object(),sinfo) >
random(MAX_ABILITY+QueryProp(P_LEVEL)*50))){
tell_object(enemy,"Du wehrst den Spruch ab.\n");
say(enemy->Name(WER,1)+" wehrt den Spruch ab.\n",
({ enemy, this_object()}));
return ;
}
int damage = random(spells[i][SPELL_DAMAGE])+1;
enemy->Defend(damage, spells[i][SPELL_DAMTYPE],
spells[i][SPELL_ARG],
this_object());
// Falls der Gegner (oder wir) im Defend stirbt, hier abbrechen
if ( !objectp(ME) || !objectp(enemy)
|| enemy->QueryProp(P_GHOST) ) return;
if (spells[i][SPELL_FUNC] && stringp(spells[i][SPELL_FUNC]))
catch(call_other(this_object(),
spells[i][SPELL_FUNC],
enemy,
damage,
spells[i][SPELL_DAMTYPE]);publish);
}
// Heartbeats nachholen.
private void catch_up_hbs() {
// gibt es HBs zum nachholen?
int beat_off_num = Query("npc:beat_off_num");
if (!beat_off_num)
return; // nein.
// wieviele HBs nachholen?
beat_off_num = absolute_hb_count() - beat_off_num;
if (beat_off_num>0) {
// Nicht ausgefuehrtes HEILEN nachholen
int rlock=QueryProp(P_NO_REGENERATION);
int hp=QueryProp(P_HP);
int sp=QueryProp(P_SP);
int alc=QueryProp(P_ALCOHOL);
if (!(rlock & NO_REG_HP)) {
hp+=beat_off_num/HEAL_DELAY+alc/ALCOHOL_DELAY;
SetProp(P_HP,hp);
}
if (!(rlock & NO_REG_SP)) {
sp+=beat_off_num/HEAL_DELAY+alc/ALCOHOL_DELAY;
SetProp(P_SP,sp);
}
alc-=beat_off_num/ALCOHOL_DELAY;
if ( alc < 0 )
alc = 0;
SetProp(P_ALCOHOL,alc);
int da = QueryProp(P_DISABLE_ATTACK);
// Paralysen abbauen
if ( da > 0 ) {
da -= beat_off_num;
if ( da < 0 )
da = 0;
SetProp( P_DISABLE_ATTACK, da );
}
// Hunttimes aktualisieren, Feinde expiren
update_hunt_times(beat_off_num);
if (!heartbeat)
// HBs immer noch abgeschaltet, naechstes Mal HBs seit jetzt nachholen.
Set("npc:beat_off_num",absolute_hb_count());
else
// HB laeuft wieder, nix mehr nachholen, bis zur naechsten Abschaltung.
Set("npc:beat_off_num",0);
}
}
void init() {
// ggf. Heartbeats nachholen und wieder einschalten.
if (!heartbeat) {
set_heart_beat(1);
heartbeat=1;
catch_up_hbs();
}
if (AutoAttack(this_player()))
Kill(this_player());
}
private nosave closure mod_att_stat;
int Defend(int dam, mixed dam_type, mixed spell, object enemy) {
if (objectp(enemy=(enemy||this_player()))
&& query_once_interactive(enemy)
&& !IS_LEARNER(enemy)) {
if (!objectp(get_type_info(mod_att_stat,2))) {
object ma;
if (!objectp(ma=find_object(STATMASTER)))
return ::Defend(dam,dam_type,spell,enemy);
// Keine Statistik wenn Master nicht geladen ist.
mod_att_stat=symbol_function("ModifyAttackStat",ma);
}
funcall(mod_att_stat,
enemy->QueryProp(P_GUILD),
enemy->QueryProp(P_GUILD_LEVEL),
dam,
dam_type,
spell);
}
return ::Defend(dam,dam_type,spell,enemy);
}