blob: ff7425647d480fdc850d1d846b816b90f6dab079 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// living/combat.c -- Basis-Kampfmodul
//
// $Id: combat.c 9568 2016-06-05 18:53:10Z Zesstra $
#pragma strong_types
#pragma save_types
#pragma range_check
#pragma no_clone
inherit "/std/living/skill_utils";
inherit "/std/living/inventory";
inherit "/std/living/team";
#include <sys_debug.h>
#include <debug_message.h>
#define NEED_PROTOTYPES
#include <hook.h>
#include <living/skills.h>
#include <thing/properties.h>
#include <player/comm.h>
#include <living/skill_attributes.h>
#include <combat.h>
#include <living.h>
#undef NEED_PROTOTYPES
#include <config.h>
#include <properties.h>
#include <language.h>
#include <wizlevels.h>
#include <attributes.h>
#include <new_skills.h>
#include <defines.h>
#include <sensitive.h>
#define HUNTTIME 300 //300 HBs sind 10 Minuten
#define RNAME(x) capitalize(getuid(x))
// 'private'-Prototypes
private string _kill_alias( string str );
// globale Variablen
nosave mapping enemies;
private nosave int attack_busy;
nosave int no_more_attacks;
private nosave int remaining_heart_beats;
private nosave int att2_time;
private nosave string last_attack_msg;
private nosave object *missing_attacks;
// Cache fuer QueryArmourByType()
private nosave mapping QABTCache;
protected void create()
{
Set(P_WIMPY, SAVE, F_MODE_AS);
Set(P_TOTAL_AC, PROTECTED, F_MODE_AS);
Set(P_HANDS, SAVE, F_MODE_AS);
Set(P_SHOW_ATTACK_MSG,SAVE,F_MODE_AS);
Set(P_RESISTANCE, ({}));
Set(P_VULNERABILITY, ({}));
Set(P_GUILD_PREPAREBLOCK, SAVE, F_MODE_AS);
// Kein Setzen von P_ARMOURS von aussen per Set(). (Per SetProp() geht es
// durch die Setmethode).
Set(P_ARMOURS,PROTECTED,F_MODE_AS);
SetProp(P_ARMOURS, ({}));
// Kein direktes Setzen von P_DEFENDERS, Setzen nur ueber
// die Zugriffsmethoden.
Set(P_DEFENDERS, PROTECTED, F_MODE_AS);
SetProp(P_DEFENDERS, ({}));
attack_busy=100;
att2_time=0;
enemies=([]);
team::create();
offerHook(H_HOOK_DEFEND,1);
offerHook(H_HOOK_ATTACK,1);
offerHook(H_HOOK_ATTACK_MOD,1);
}
#undef DEBUG
#define DEBUG(x) if (find_object("zesstra")) \
tell_object(find_player("zesstra"),x)
public void UpdateResistanceStrengths() {
mapping resmods, strmap;
if ( !mappingp(resmods=Query(P_RESISTANCE_MODIFIER)) )
return;
//erstmal die alte Aufsummation loeschen
m_delete(resmods,"me");
//wenn jetzt leer: Abbruch, keine Res-Modifier da.
if (!sizeof(resmods))
return(Set(P_RESISTANCE_MODIFIER,0));
strmap = ([]);
// ueber alle gesetzten ResModifier gehen
foreach(string mod, mapping resmap, object ob: resmods) {
if ( !mappingp(resmap) || !sizeof(resmap)
|| !objectp(ob) ) {
m_delete(resmods, mod);
continue; // Resi ungueltig, weg damit.
}
// jetzt noch ueber die Submappings laufen, die die Resis der Objekte
// beinhalten.
foreach(string reskey, float resi: resmap) {
strmap[reskey] = ((strmap[reskey]+1.0)*(resi+1.0))-1.0;
}
}
if ( !sizeof(strmap) )
Set(P_RESISTANCE_MODIFIER, 0);
else
Set(P_RESISTANCE_MODIFIER, resmods+([ "me" : strmap; 0 ]) );
}
public varargs int AddResistanceModifier(mapping mod, string add)
{ string key;
mapping res;
if ( !mappingp(mod) || !sizeof(mod) || !previous_object() )
return 0;
key = explode(object_name(previous_object()),"#")[0];
if ( add )
key += ("#"+add);
res = Query(P_RESISTANCE_MODIFIER);
mod = deep_copy(mod);
if ( !mappingp(res) )
res = ([ key : mod; previous_object() ]);
else
res += ([ key : mod; previous_object() ]);
Set(P_RESISTANCE_MODIFIER, res);
UpdateResistanceStrengths();
return 1;
}
public varargs void RemoveResistanceModifier(string add)
{ string key;
mapping res;
if ( !previous_object() )
return;
key = explode(object_name(previous_object()),"#")[0];
if ( add )
key += ("#"+add);
if ( !mappingp(res = Query(P_RESISTANCE_MODIFIER)) )
return;
m_delete(res, key);
Set(P_RESISTANCE_MODIFIER, res);
UpdateResistanceStrengths();
}
// veraltete Prop, aus Kompatibilitaetsgruenden noch vorhanden.
static mixed _set_resistance(mixed arg)
{ int i;
mapping resimap;
mixed old;
if ( !pointerp(arg) )
arg=({arg});
if ( !mappingp(resimap=Query(P_RESISTANCE_STRENGTHS)) )
resimap=([]);
if ( pointerp(old=QueryProp(P_RESISTANCE)) )
for ( i=sizeof(old)-1 ; i>=0 ; i-- )
resimap[old[i]]=(1.0+resimap[old[i]])*2.0-1.0;
for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
resimap[arg[i]]=(1.0+resimap[arg[i]])*0.5-1.0;
SetProp(P_RESISTANCE_STRENGTHS,resimap);
return Set(P_RESISTANCE,arg);
}
// veraltete Prop, aus Kompatibilitaetsgruenden noch vorhanden.
static mixed _set_vulnerability(mixed arg)
{ int i;
mapping resimap;
mixed old;
if ( !pointerp(arg) )
arg=({arg});
if ( !mappingp(resimap=Query(P_RESISTANCE_STRENGTHS)) )
resimap=([]);
if ( pointerp(old=QueryProp(P_VULNERABILITY)) )
for ( i=sizeof(old)-1 ; i>=0 ; i-- )
resimap[old[i]]=(1.0+resimap[old[i]])*0.5-1.0;
for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
resimap[arg[i]]=(1.0+resimap[arg[i]])*2.0-1.0;
SetProp(P_RESISTANCE_STRENGTHS,resimap);
return Set(P_VULNERABILITY,arg);
}
/** kill - Kampf starten.
* Fuegt ob der Feindesliste hinzu.
*/
public int Kill(object ob)
{ int res;
int|string no_attack;
if ( !objectp(ob) )
return 0;
if ( ob->QueryProp(P_GHOST) )
{
tell_object(ME,ob->Name(WER)+" ist doch schon tot!\n");
return -1;
}
if ( no_attack = ob->QueryProp(P_NO_ATTACK) )
{
if ( stringp(no_attack) )
tell_object(ME, no_attack);
else
tell_object(ME, ob->Name(WER,1)+" laesst sich nicht angreifen!\n");
return -2;
}
if ( QueryProp(P_NO_ATTACK) )
return -3;
res=InsertEnemy(ob);
if (res)
tell_object(ME, "Ok.\n");
else if (IsEnemy(ob))
tell_object(ME, "Jajaja, machst Du doch schon!\n");
//else //kein gueltiger Feind, ob wurde nicht eingetragen.
if ( !res )
return -4; // nicht aendern ohne Kill in player/combat.c
return 1;
}
public int InsertSingleEnemy(object ob)
{
if ( !living(ob) )
return 0;
// Wie lange verfolgt dieses Living? Wenn Prop nicht gesetzt oder
// ungueltig, d.h. kein int oder <= 0, dann wird die Defaultzeit genommen.
int hunttime = QueryProp(P_HUNTTIME);
hunttime = (intp(hunttime) && hunttime > 0) ?
hunttime / __HEART_BEAT_INTERVAL__ : HUNTTIME;
// Auch wenn ein Objekt schon als Gegner eingetragen ist, trotzdem die
// HUNTTIME erneuern. Das "return 0;" muss aber bleiben, da der Gegner
// ja nicht neu ist.
//
// 09.12.2000, Tiamak
if ( member( enemies, ob ) ) {
enemies[ob,ENEMY_HUNTTIME] = hunttime;
return 0;
}
if ( (ob==ME) || QueryProp(P_NO_ATTACK)
|| !objectp(ob) || (ob->QueryProp(P_NO_ATTACK))
|| (ob->QueryProp(P_GHOST)) )
return 0;
object team;
if ( ( query_once_interactive(ME) || query_once_interactive(ob) )
&& objectp(team=Query(P_TEAM)) && (team==(ob->Query(P_TEAM))) )
return 0;
set_heart_beat(1);
last_attack_msg=0;
enemies[ob,ENEMY_HUNTTIME] = hunttime;
Set(P_LAST_COMBAT_TIME,time());
return 1;
}
public int InsertEnemy(object ob)
{ int res;
if ( res=InsertSingleEnemy(ob) )
InsertEnemyTeam(ob);
return res;
}
public object QueryPreferedEnemy()
{ int sz,r;
<int|object>* pref;
object enemy;
enemy=0;
if ( pointerp(pref=QueryProp(P_PREFERED_ENEMY)) && ((sz=sizeof(pref))>1)
&& intp(r=pref[0]) && (random(100)<r) )
{
enemy=pref[1+random(sz-1)];
if ( !objectp(enemy) )
{
pref-=({enemy});
if ( sizeof(pref)<2 )
pref=0;
SetProp(P_PREFERED_ENEMY,pref);
return 0;
}
if ( !IsEnemy(enemy) )
return 0;
}
return enemy;
}
public object *PresentEnemies() {
object *here=({});
object *netdead=({});
mixed no_attack;
foreach(object pl: enemies) {
if (no_attack = pl->QueryProp(P_NO_ATTACK)) {
m_delete(enemies,pl);
// Und auch im Gegner austragen, sonst haut der weiter zu und dieses
// Living wehrt sich nicht. (Zesstra, 26.11.2006)
if (stringp(no_attack)) {
tell_object(ME, no_attack);
pl->StopHuntFor(ME, 1);
}
else pl->StopHuntFor(ME, 0);
}
else if ( environment()==environment(pl) )
{
if ( interactive(pl) || !query_once_interactive(pl) )
here+=({pl});
else
netdead+=({pl});
}
}
if ( !sizeof(here) ) // Netztote sind nur Feinde, falls keine anderen da sind
return netdead;
return here;
}
public varargs object SelectEnemy(object *here)
{ object enemy;
if ( !pointerp(here) )
here=PresentEnemies();
if ( !sizeof(here) )
return 0;
if ( !objectp(enemy=QueryPreferedEnemy())
|| (environment(enemy)!=environment()) )
enemy=here[random(sizeof(here))];
return enemy;
}
protected void heart_beat() {
int hbs;
// leider rufen viele Leute einfach das geerbte heart_beat() obwohl sie
// schon tot sind und damit das Objekt zerstoert ist.
if (!living(this_object()))
return;
// Paralyse pruefen, ggf. reduzieren
int dis_attack = QueryProp(P_DISABLE_ATTACK);
if ( intp(dis_attack) && (dis_attack > 0) )
{
SetProp(P_DISABLE_ATTACK, --dis_attack);
}
// Attacken ermitteln: SA_SPEED + Rest aus dem letzten HB
hbs = remaining_heart_beats + QuerySkillAttribute(SA_SPEED);
if ( hbs <= 0 )
hbs = 100;
// P_ATTACK_BUSY bestimmen
if ( attack_busy > 99)
attack_busy = 100 + hbs;
else
attack_busy += 100 + hbs;
// mehr fuer Seher...
if ( IS_SEER(ME) )
attack_busy+=(100+QueryProp(P_LEVEL));
// max. 500, d.h. 5 Attacken
if ( attack_busy>500 )
attack_busy=500;
// unganzzahligen Rest fuer naechsten HB speichern
remaining_heart_beats = hbs % 100;
hbs /= 100; // ganze Attacken. ;-)
if ( hbs > 10 ) // nicht mehr als 10 Attacken.
hbs = 10;
// Falls jemand seit dem letzten HB in diesem Living von aussen Attack()
// gerufen hat (nicht ExtraAttack()), wird jetzt was abgezogen.
hbs -= no_more_attacks;
no_more_attacks = 0;
// Wie lange verfolgt dieser NPC? Wenn Prop nicht gesetzt oder ungueltig,
// d.h. kein int oder <= 0, dann wird die Defaultzeit genommen.
int hunttime = QueryProp(P_HUNTTIME);
hunttime = (intp(hunttime) && hunttime > 0) ?
hunttime / __HEART_BEAT_INTERVAL__ : HUNTTIME;
// erstmal die Hunttimes der Gegner aktualisieren und nebenbei die
// anwesenden Feinde merken. Ausserdem ggf. Kampf abbrechen, wenn
// P_NO_ATTACK gesetzt ist.
// eigentlich ist diese ganze Schleife fast das gleiche wie
// PresentEnemies(), aber leider muessen ja die Hunttimes aktulisiert werde,
// daher kann man nicht direkt PresentEnemies() nehmen.
// TODO: Diese Schleife und PresentEnmies() irgendwie vereinigen.
object *netdead=({});
object *here=({});
object enemy;
mixed no_attack;
foreach(enemy, int htime: &enemies) { // Keys in enemy
// ggf. Meldungen ausgeben und Gegner austragen und dieses Living beim
// Gegner austragen.
if (no_attack=enemy->QueryProp(P_NO_ATTACK)) {
m_delete(enemies,enemy);
if ( stringp(no_attack) ) {
write( no_attack );
enemy->StopHuntFor( ME, 1 );
}
else
enemy->StopHuntFor( ME, 0 );
}
else if ( environment()==environment(enemy) ) {
// Gegner anwesend, als Feind merken...
if ( interactive(enemy) || !query_once_interactive(enemy) )
here+=({enemy});
else
netdead+=({enemy});
// ... und Hunttime erneuern.
htime = hunttime;
}
// Feind nicht anwesend. Timer dekrementieren und Feind austragen, wenn
// Jagdzeit abgelaufen.
else if ( (--htime) <= 0 )
StopHuntFor(enemy);
}
// Netztote sind nur Feinde, wenn sonst keine da sind.
if ( !sizeof(here) )
here=netdead;
// schonmal abfragen, ob ein Spell vorbereitet wird.
mixed pspell=QueryProp(P_PREPARED_SPELL);
//Da hbs 0 und no_more_attacks durch einen manuellen Aufruf von Attack() im
//Spieler >0 sein kann, kann jetzt hbs <0 sein. (s.o.)
if (hbs > 0 && sizeof(here)) {
foreach(int i: hbs) {
// Feind in Nahkampfreichweite finden
if ( objectp(enemy=SelectNearEnemy(here)) ) {
// Flucht erwuenscht?
if ( QueryProp(P_WIMPY) > QueryProp(P_HP) ) {
Flee();
// Flucht gelungen?
if ( enemy && (environment(enemy)!=environment()) ) {
here = 0; // naechste Runde neue Feinde suchen
enemy = 0;
continue; // kein Kampf, Runde ueberspringen
}
}
}
else {
// keine Feinde gefunden... Abbrechen.
break;
}
// Paralyse gesetzt? -> Abbrechen. Dieses Pruefung muss hier passieren,
// damit ggf. die automatische Flucht des Lebewesens durchgefuehrt wird,
// auch wenn es paralysiert ist.
if (dis_attack > 0) break;
// Kampf durch Spell-Prepare blockiert?
// Keine genaue Abfrage, wird spaeter noch genau geprueft.
if ( pspell && QueryProp(P_GUILD_PREPAREBLOCK) )
break; // keine Angriffe in diesem HB.
// wenn Feind da: hit'em hard.
if ( objectp(enemy) )
Attack(enemy);
// ggf. hat Attack() no_more_attacks gesetzt. Zuruecksetzen
no_more_attacks = 0;
// naechste Kampfrunde neue Feinde suchen
here = 0;
} // foreach
}
no_more_attacks=0;
// Ist ein Spell in Vorbereitung und evtl. jetzt fertig vorbereitet?
if ( pointerp(pspell)
&& (sizeof(pspell)>=3) && intp(pspell[0]) && stringp(pspell[1]) ) {
if ( time()>=pspell[0] ) // Kann der Spruch jetzt ausgefuehrt werden?
{
UseSpell(pspell[2],pspell[1]); // Dann los
SetProp(P_PREPARED_SPELL,0);
}
}
else if ( pspell ) // Unbrauchbarer Wert, loeschen
SetProp(P_PREPARED_SPELL,0);
} // Ende heart_beat
// wird von NPC gerufen, die Heartbeats nachholen und dabei die Hunttimes um
// die Anzahl verpasster Heartbeats reduzieren muessen.
// Wird von Spielerobjekten gerufen, wenn sie nach 'schlafe ein' aufwachen, um
// die notwendige Anzahl an HB hier abzuziehen und ggf. die Feinde zu expiren,
// da Spieler ja netztot keinen Heartbeat haben.
protected void update_hunt_times(int beats) {
if (!mappingp(enemies)) return;
foreach(object en, int htime: &enemies) { // Mapping-Keys in en
htime -= beats;
if ( htime <= 0 )
StopHuntFor(en);
}
}
public int IsEnemy(object wer)
{
return (member(enemies,wer));
}
public void StopHuntText(object arg)
{
tell_object(arg,
Name(WER,1)+" "+(QueryProp(P_PLURAL)?"jagen ":"jagt ")+
(arg->QueryProp(P_PLURAL)?"Euch":"Dich")+" nicht mehr.\n");
tell_object(ME,(QueryProp(P_PLURAL)?"Ihr jagt ":"Du jagst ")+
arg->name(WEN,1)+" nicht mehr.\n");
}
public varargs int StopHuntFor(object arg, int silent)
{
if ( !objectp(arg) || !IsEnemy(arg) )
return 0;
if (!silent)
StopHuntText(arg);
m_delete(enemies,arg);
last_attack_msg=0;
return 1;
}
// Begruessungsschlag nur einmal pro HB
public void Attack2(object enemy)
{
if ( att2_time > time() )
return;
att2_time=time() + __HEART_BEAT_INTERVAL__;
Attack(enemy);
}
// Fuer evtl. Attack-Aenderungen, ohne dass man gleich das ganze
// Attack ueberlagern muss:
protected void InternalModifyAttack(mapping ainfo)
{
return; // Vorerst!
/*
int fac;
mixed res;
fac=100;
if (CannotSee(1))
fac -= 50;
// Weitere Mali?
if (fac<100)
ainfo[SI_SKILLDAMAGE] = ainfo[SI_SKILLDAMAGE]*fac/100;
// Fertig
*/
}
// Ausfuehren Eines Angriffs auch wenn es bereits einen Angriff
// in dieser Runde gegeben hat. Dieser Angriff wird auch nicht
// gezaehlt. Die Nutzung dieser Funktion ist nur fuer Spezialfaelle
// gedacht und immer BALANCEPFLICHTIG!
varargs public void ExtraAttack(object enemy, int ignore_previous)
{
int saved_no_more_attacks;
// Erstschlagsinfo speichern.
saved_no_more_attacks = no_more_attacks;
// Bei Bedarf bisher schon durchgefuehrte Erstschlaege ignorieren
if (ignore_previous) no_more_attacks=0;
// Normalen Angriff durchfuehren
Attack (enemy);
// Gespeicherten Wert zuruecksetzen
no_more_attacks = saved_no_more_attacks;
return;
}
public void Attack(object enemy)
{
closure cl;
mixed res;
mapping ainfo;
mapping edefendinfo; // erweiterte Defend-Infos
mixed hookData;
mixed hookRes;
if ( no_more_attacks || QueryProp(P_GHOST) ||
!objectp(enemy) || !objectp(this_object()) ||
(QueryProp(P_DISABLE_ATTACK) > 0) || enemy->QueryProp(P_NO_ATTACK) ||
(query_once_interactive(this_object()) && !interactive(this_object())) )
return;
edefendinfo=([]);
Set(P_LAST_COMBAT_TIME,time());
// inkrementieren. Diese Variable wird im HB nach den Angriffen genullt.
// Diese Variable wird im naechsten Heartbeat von der Anzahl an verfuegbaren
// Angriffen des Livings abgezogen. Dies beruecksichtigt also zwischen den
// HBs (z.B. extern) gerufene Attacks().
no_more_attacks++;
// Wird das Attack durch einen temporaeren Attack-Hook ersetzt?
if ( res=QueryProp(P_TMP_ATTACK_HOOK) )
{
if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0]) && (time()<res[0])
&& objectp(res[1]) && stringp(res[2]) )
{
if ( !(res=call_other(res[1],res[2],enemy)) )
return;
}
else
SetProp(P_TMP_ATTACK_HOOK,0);
}
// trigger attack hook
hookData=({enemy});
hookRes=HookFlow(H_HOOK_ATTACK,hookData);
if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
if(hookRes[H_RETCODE]==H_CANCELLED){
return;
}
}
ainfo = ([ SI_ENEMY : enemy,
SI_SPELL : 0,
]);
if ( objectp(ainfo[P_WEAPON]=QueryProp(P_WEAPON)) )
{
ainfo[P_WEAPON]->TakeFlaw(enemy);
// Abfrage fuer den Fall, dass Waffe durch das TakeFlaw() zerstoert
// wurde. Dann wird das Attack abgebrochen.
if ( !objectp(ainfo[P_WEAPON]) )
return;
cl=symbol_function("name",ainfo[P_WEAPON]);
ainfo[SI_SKILLDAMAGE_MSG] = (" mit "+funcall(cl,WEM,0));
ainfo[SI_SKILLDAMAGE_MSG2] = (" mit "+funcall(cl,WEM,1));
ainfo[SI_SKILLDAMAGE] = ainfo[P_WEAPON]->QueryDamage(enemy);
cl=symbol_function("QueryProp",ainfo[P_WEAPON]);
ainfo[SI_SKILLDAMAGE_TYPE] = funcall(cl,P_DAM_TYPE);
ainfo[P_WEAPON_TYPE] = funcall(cl,P_WEAPON_TYPE);
ainfo[P_NR_HANDS] = funcall(cl,P_NR_HANDS);
ainfo[P_WC] = funcall(cl,P_WC);
// Zweihaendige Waffe?
if ( ainfo[P_NR_HANDS]==2
&& mappingp(res=UseSkill(SK_TWOHANDED,deep_copy(ainfo)))
&& member(res,SI_SKILLDAMAGE) )
{
// Nur den neuen Schadenswert uebernehmen.
ainfo[SI_SKILLDAMAGE]=res[SI_SKILLDAMAGE];
}
}
else // Keine Waffe gezueckt
{
/* Check if there is a magical attack */
if ( mappingp(res=UseSkill(SK_MAGIC_ATTACK,([SI_ENEMY:enemy]))) )
{
ainfo[SI_SKILLDAMAGE]=res[SI_SKILLDAMAGE];
ainfo[SI_SKILLDAMAGE_TYPE]=res[SI_SKILLDAMAGE_TYPE];
if ( stringp(res[SI_SKILLDAMAGE_MSG]) )
ainfo[SI_SKILLDAMAGE_MSG] = " mit "+res[SI_SKILLDAMAGE_MSG];
else
ainfo[SI_SKILLDAMAGE_MSG] = " mit magischen Faehigkeiten";
if ( stringp(res[SI_SKILLDAMAGE_MSG2]) )
ainfo[SI_SKILLDAMAGE_MSG2] = " mit "+res[SI_SKILLDAMAGE_MSG2];
else
ainfo[SI_SKILLDAMAGE_MSG2] = ainfo[SI_SKILLDAMAGE_MSG];
if ( !(ainfo[P_WEAPON_TYPE]=res[P_WEAPON_TYPE]) )
ainfo[P_WEAPON_TYPE]=WT_MAGIC;
if ( member(res,SI_SPELL) )
ainfo[SI_SPELL]=res[SI_SPELL];
}
else
{
/* Ohne (freie) Haende wird auch nicht angegriffen */
if ( interactive(this_object())
&& (QueryProp(P_USED_HANDS) >= QueryProp(P_MAX_HANDS)) )
return ;
if ( !pointerp(res=QueryProp(P_HANDS)) || (sizeof(res)<2) )
return;
ainfo[SI_SKILLDAMAGE] = (( 2*random(res[1]+1)
+ 10*(QueryAttribute(A_STR)) )/3);
ainfo[SI_SKILLDAMAGE_TYPE] = res[2];
ainfo[SI_SKILLDAMAGE_MSG] = ainfo[SI_SKILLDAMAGE_MSG2]
= res[0];
ainfo[P_WEAPON_TYPE] = WT_HANDS;
ainfo[P_WC] = res[1];
}
} // besondere Faehigkeiten mit diesem Waffentyp?
if ( mappingp(res=UseSkill(FIGHT(ainfo[P_WEAPON_TYPE]),
deep_copy(ainfo))) )
SkillResTransfer(res,ainfo);
// besondere allgemeine Kampffaehigkeiten?
if ( mappingp(res=UseSkill(SK_FIGHT,deep_copy(ainfo))) )
SkillResTransfer(res,ainfo);
// Veraenderungen durch einen Attack-Modifier?
if ( (res=QueryProp(P_TMP_ATTACK_MOD)) )
{
if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0])
&& (time()<res[0]) && objectp(res[1]) && stringp(res[2]) )
{
if ( !(res=call_other(res[1],res[2],deep_copy(ainfo)))
|| !mappingp(res) )
return;
else
SkillResTransfer(res,ainfo);
}
else
SetProp(P_TMP_ATTACK_MOD,0);
}
// trigger attack mod hook
hookData=deep_copy(ainfo);
hookRes=HookFlow(H_HOOK_ATTACK_MOD,hookData);
if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
if(hookRes[H_RETCODE]==H_CANCELLED){
return;
}
else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA] &&
mappingp(hookRes[H_RETDATA])){
SkillResTransfer(hookRes[H_RETDATA],ainfo);
}
}
// Interne Modifikationen der Angriffswerte
InternalModifyAttack(ainfo);
if ( !objectp(enemy) )
return;
// hier mal bewusst nicht auf P_PLURAL zuerst testen. in 90% der Faelle
// wird eh keine Angriffsmeldung mehr ausgegeben, also pruefen wir das
// lieber zuerst. Plural testen zieht schon genug Rechenzeit :/
// Nicht meine Idee! Nicht meine Idee! Nachtwind 7.7.2001
if ( ainfo[SI_SKILLDAMAGE_MSG2]!=last_attack_msg )
{
last_attack_msg = ainfo[SI_SKILLDAMAGE_MSG2];
if (QueryProp(P_PLURAL))
{
tell_object( ME, " Ihr greift " + enemy->name(WEN,1)
+ ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
say(" "+(Name(WER,1))+" greifen "+(enemy->name(WEN,1))+
ainfo[SI_SKILLDAMAGE_MSG]+" an.\n", enemy );
tell_object( enemy, " "+(Name(WER,1))+" greifen "+
(enemy->QueryProp(P_PLURAL)?"Euch":"Dich")+
ainfo[SI_SKILLDAMAGE_MSG2]+" an.\n" );
}
else
{
tell_object( ME, " Du greifst " + enemy->name(WEN,1)
+ ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
say(" "+(Name(WER,2))+" greift "+(enemy->name(WEN,1))+
ainfo[SI_SKILLDAMAGE_MSG]+" an.\n", enemy );
tell_object( enemy, " "+(Name(WER,1))+" greift "+
(enemy->QueryProp(P_PLURAL)?"Euch":"Dich")+
ainfo[SI_SKILLDAMAGE_MSG2]+" an.\n" ); }
}
else if ( Query(P_SHOW_ATTACK_MSG) )
if (QueryProp(P_PLURAL))
tell_object( ME, " Ihr greift " + enemy->name(WEN,1)
+ ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
else
tell_object( ME, " Du greifst " + enemy->name(WEN,1)
+ ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
// ainfo in defendinfo merken
edefendinfo[ ORIGINAL_AINFO]= deep_copy(ainfo);
edefendinfo[ ORIGINAL_DAM]= ainfo[SI_SKILLDAMAGE];
edefendinfo[ ORIGINAL_DAMTYPE]= ainfo[SI_SKILLDAMAGE_TYPE];
edefendinfo[ CURRENT_DAM]= ainfo[SI_SKILLDAMAGE];
edefendinfo[ CURRENT_DAMTYPE]= ainfo[SI_SKILLDAMAGE_TYPE];
// ainfo[SI_SPELL] auf ein mapping normieren
if ( intp(ainfo[SI_SPELL]) )
{
ainfo[SI_SPELL] = ([ SP_PHYSICAL_ATTACK : !ainfo[SI_SPELL],
SP_SHOW_DAMAGE : !ainfo[SI_SPELL],
SP_REDUCE_ARMOUR : ([ ]) ]);
}
// defendinfo anhaengen, falls ainfo[SI_SPELL] ein mapping ist
if( mappingp(ainfo[SI_SPELL]))
{
ainfo[SI_SPELL][EINFO_DEFEND]=edefendinfo;
}
enemy->Defend(ainfo[SI_SKILLDAMAGE], ainfo[SI_SKILLDAMAGE_TYPE],
ainfo[SI_SPELL], this_object());
//edefendinfo=([]);
/* Done attacking */
}
public void AddDefender(object friend)
{
if(!objectp(friend))
return;
object* defs = QueryProp(P_DEFENDERS) || ({});
defs -= ({0});
if(friend in defs)
return;
defs += ({friend});
SetProp(P_DEFENDERS, defs);
}
public void RemoveDefender(object friend)
{
if ( !objectp(friend) )
return;
object* defs = QueryProp(P_DEFENDERS);
defs -= ({0});
if(!(friend in defs))
return;
defs -= ({friend});
SetProp(P_DEFENDERS, defs);
}
public object* QueryDefenders()
{
return filter(QueryProp(P_DEFENDERS), #'objectp);
}
public object* QueryPresentDefenders(object* defenders =
QueryProp(P_DEFENDERS) || ({}))
{
// Alles selbst pruefen, um Durchlaeufe zu sparen.
return filter(defenders,
function int(object o)
{
return (objectp(o) &&
(environment(o) == environment(ME) || environment(o) == ME));
});
}
public object* QueryNearDefenders(object* defenders =
QueryProp(P_DEFENDERS) || ({}))
{
// alles selbst pruefen, um Durchlaeufe zu sparen.
return filter(defenders,
function int(object o)
{
// Nahe sind: Objekte im eigenen Inventar oder Objekt im Environment,
// welche in Teamreihe 1 oder 0 (haben einfach keine Teamreihe) sind.
return (objectp(o) &&
( environment(o) == ME ||
(environment(o) == environment(ME) && o->PresentPosition() <= 1) )
);
}
);
}
public void InformDefend(object enemy)
{
UseSkill(SK_INFORM_DEFEND,([ SI_ENEMY : enemy,
SI_FRIEND : previous_object() ]));
// Oh, oh - ich hoffe mal, dass InformDefend wirklich NUR aus Defend
// eines befreundeten livings aufgerufen wird... (Silvana)
// This is only experimental... ;)
}
public <int|string*|mapping>* DefendOther(int dam, string|string* dam_type,
int|mapping spell, object enemy)
{
<int|string*|mapping>* res;
if ( (res=UseSkill(SK_DEFEND_OTHER,([ SI_SKILLDAMAGE : dam,
SI_SKILLDAMAGE_TYPE : dam_type,
SI_SPELL : spell,
SI_FRIEND : previous_object(),
SI_ENEMY : enemy ])))
&& pointerp(res) )
return res;
return 0;
}
public void CheckWimpyAndFlee()
{
if ( (QueryProp(P_WIMPY)>QueryProp(P_HP)) && !TeamFlee()
&& find_call_out("Flee")<0 )
call_out(#'Flee,0,environment());
}
protected string mess(string msg,object me,object enemy)
{ closure mname, ename;
string *parts;
int i;
mname = symbol_function("name", me);
ename = symbol_function("name", enemy);
parts=regexplode(msg,"@WE[A-Z]*[12]");
for ( i=sizeof(parts)-2 ; i>=1 ; i-=2 )
{
switch(parts[i])
{
case "@WER1": parts[i]=funcall(mname,WER,1); break;
case "@WESSEN1": parts[i]=funcall(mname,WESSEN,1); break;
case "@WEM1": parts[i]=funcall(mname,WEM,1); break;
case "@WEN1": parts[i]=funcall(mname,WEN,1); break;
case "@WER2": parts[i]=funcall(ename,WER,1); break;
case "@WESSEN2": parts[i]=funcall(ename,WESSEN,1); break;
case "@WEM2": parts[i]=funcall(ename,WEM,1); break;
case "@WEN2": parts[i]=funcall(ename,WEN,1); break;
default: ;
}
}
return break_string(capitalize(implode(parts,"")),78," ",1);
}
// Fuer evtl. Defend-Aenderungen, ohne dass man gleich das ganze
// Attack ueberlagern muss:
protected void InternalModifyDefend(int dam, string* dt, mapping spell, object enemy)
{
return;
}
protected nomask void normalize_defend_args(int dam, string|string* dam_type,
int|mapping spell, object enemy)
{
// this_player(), wenn kein enemy bekannt...
enemy ||= this_player();
// Schadenstyp ueberpruefen
if ( !pointerp(dam_type) )
dam_type = ({ dam_type });
// Und das Spellmapping pruefen, erzeugen, ergaenzen etc.
if ( intp(spell) )
spell = ([ SP_PHYSICAL_ATTACK : !spell,
SP_SHOW_DAMAGE : !spell,
SP_REDUCE_ARMOUR : ([ ]),
EINFO_DEFEND : ([ORIGINAL_DAM:dam,
ORIGINAL_DAMTYPE:dam_type ])
]);
else if ( mappingp(spell) )
{
// testen ob eine erweiterte defendinfo vorhanden ist
if(!member(spell,EINFO_DEFEND))
{
// wenn nicht, koennen wir an den fehlenden Infos wenig machen, aber
// zumindest ergaenzen wir es und schreiben die (hier) initialen dam und
// dam_type rein.
spell[EINFO_DEFEND] = ([ORIGINAL_DAM:dam,
ORIGINAL_DAMTYPE:dam_type]);
}
if ( !mappingp(spell[SP_REDUCE_ARMOUR]) )
spell[SP_REDUCE_ARMOUR] = ([]);
else
{
// Erstmal die Variable setzen, damit die folgenden Abfragen
// uebersichtlicher werden. Auf die Existenz muss trotzdem geprueft
// werden, weil bei einem nicht existierenden Key auch 0 zurueckgegeben
// wuerde.
int default_reduce = ({int})spell[SP_REDUCE_ARMOUR][AT_DEFAULT_REDUCE];
if(AT_DEFAULT_REDUCE in spell[SP_REDUCE_ARMOUR] &&
default_reduce >= 0)
{
foreach(string key :
VALID_ARMOUR_CLASS + ([P_PARRY_WEAPON: 0, P_BODY: 0]))
{
if(key in spell[SP_REDUCE_ARMOUR]) continue;
m_add(spell[SP_REDUCE_ARMOUR], key, default_reduce);
}
}
}
}
else // Illegaler spell-Parameter
raise_error(sprintf("Ungueltiger Typ des spell-Arguments: %d\n",
get_type_info(spell,0)));
}
public int Defend(int dam, string|string* dam_type, int|mapping spell, object enemy)
{
mixed res,res2;
object *armours,tmp;
mixed hookData;
mixed hookRes;
int i;
// string what, how;
string enname, myname;
normalize_defend_args(&dam, &dam_type, &spell, &enemy);
// Testen, ob dieses Lebewesen ueberhaupt angegriffen werden darf
if ( !this_object() || !enemy || QueryProp(P_NO_ATTACK)
|| ( query_once_interactive(enemy) && ! interactive(enemy) ) )
return 0;
spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
// Testen, ob der Angreifer schon als Feind registriert worden ist.
// Wenn nein, registrieren.
if ( !IsEnemy(enemy) && !spell[SP_NO_ENEMY] )
{
spell[EINFO_DEFEND][ENEMY_INSERTED]=1;
InsertEnemy(enemy);
}
// RFR-Taktik abfangen
if ( !QueryProp(P_ENABLE_IN_ATTACK_OUT) )
{
i=time()-(enemy->QueryProp(P_LAST_MOVE));
// Gegner hat sich bewegt, man selbst nicht
if ( (i<3) && (time()-QueryProp(P_LAST_MOVE)>=5) )
{
// Bei Erster Kampfrunde wenige Schaden
dam/=(4-i);
spell[EINFO_DEFEND][RFR_REDUCE]=dam;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
}
}
// Man kann Verteidiger haben. Diese kommen als erste zum Zuge, falls sie
// anwesend sind.
object* defs_here = QueryPresentDefenders();
if (sizeof(defs_here))
{
// Die werden erstmal alle informiert.
defs_here->InformDefend(enemy);
// Leider koennten dabei Objekte zerstoert worden sein. *seufz*
defs_here -= ({0});
// Anwesende Verteidiger eintragen.
spell[EINFO_DEFEND][PRESENT_DEFENDERS] = defs_here;
// Bei physischen Angriffen koennen nur Verteidiger aus Reihe 1 helfen
// (z.B. fuer Rueckendeckung der Kaempfer)
if(spell[SP_PHYSICAL_ATTACK])
{
defs_here = QueryNearDefenders(defs_here);
}
if(sizeof(defs_here))
{
// Wenn in Frage kommende Verteidiger anwesend sind, einen davon
// auswaehlen...
<object|int|string*|mapping>* edefendtmp = ({defs_here[random(sizeof(defs_here))], 0, 0, 0});
// ... und DefendOther() darin rufen. Das wirkt wie ein Defend-Hook-Light
res = call_other(edefendtmp[DEF_DEFENDER], "DefendOther",
dam, dam_type, spell, enemy);
// Wenn das Defender valide Daten geliefert hat und den Schaden oder
// Schadenstyp geaendert hat (z.B. Umwandlung von Feuer nach Eis),
// werden die neuen Daten uebernommen.
if(pointerp(res) && (sizeof(res)>=3) && intp(res[0])
&& pointerp(res[1]))
{
dam = res[0];
edefendtmp[DEF_DAM] = dam;
dam_type = res[1];
edefendtmp[DEF_DAMTYPE] = dam_type;
if(mappingp(res[2]))
{
spell = res[2];
// teuer, aber geht nicht anders (Rekursion vermeiden)
edefendtmp[DEF_SPELL] = deep_copy(res[2]);
}
// Die Werte sind jetzt auch die aktuell wirksamen Werte.
spell[EINFO_DEFEND][CURRENT_DAMTYPE] = dam_type;
spell[EINFO_DEFEND][CURRENT_DAM] = dam;
}
spell[EINFO_DEFEND][DEFENDING_DEFENDER] = edefendtmp;
}
} // Ende Defender-Verarbeitung
// Ueber einen P_TMP_DEFEND_HOOK werden z.B. Schutzzauber gehandhabt
spell[EINFO_DEFEND][DEFEND_HOOK]=DI_NOHOOK;
if ( res=QueryProp(P_TMP_DEFEND_HOOK) )
{
if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0]) && (time()<res[0])
&& objectp(res[1]) && stringp(res[2]) )
{
if ( !(res=call_other(res[1],res[2],dam,dam_type,spell,enemy)) )
{
// Ein P_TMP_DEFEND_HOOK kann den Schaden vollstaendig abfangen,
// das Defend wird dann hier abgebrochen *SICK* und es wird nur
// noch getestet, ob man in die Flucht geschlagen wurde
spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
CheckWimpyAndFlee();
return 0;
}
else
{
spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
if ( pointerp(res) && (sizeof(res)>=3)
&& intp(res[0] && pointerp(res[1])) )
{
mixed edefendtmp=({0,0,0});
// Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
// -art sowie die Spell-Infos veraendern
dam=res[0];
edefendtmp[HOOK_DAM]=dam;
dam_type=res[1];
edefendtmp[HOOK_DAMTYPE]=dam_type;
if ( mappingp(res[2]) )
{
spell=res[2];
// Waeh. Teuer. Aber geht nicht anders.
edefendtmp[HOOK_SPELL]=deep_copy(spell);
}
spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
}
}
}
else
SetProp(P_TMP_DEFEND_HOOK,0);
} // P_TMP_DEFEND_HOOK
// trigger defend hook
hookData=({dam,dam_type,spell,enemy});
hookRes=HookFlow(H_HOOK_DEFEND,hookData);
if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
if(hookRes[H_RETCODE]==H_CANCELLED){
spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
CheckWimpyAndFlee();
return 0;
}
else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA]){
spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
if ( pointerp(hookRes[H_RETDATA]) && (sizeof(hookRes[H_RETDATA])>=3)
&& intp(hookRes[H_RETDATA][0] && pointerp(hookRes[H_RETDATA][1])) )
{
mixed edefendtmp=({0,0,0});
// Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
// -art sowie die Spell-Infos veraendern
dam=hookRes[H_RETDATA][0];
edefendtmp[HOOK_DAM]=dam;
dam_type=hookRes[H_RETDATA][1];
edefendtmp[HOOK_DAMTYPE]=dam_type;
if ( mappingp(hookRes[H_RETDATA][2]) )
{
spell=hookRes[H_RETDATA][2];
// Teuer, teuer... :-(
edefendtmp[HOOK_SPELL]=deep_copy(spell);
}
spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
}
}
} // Ende Hook-Behandlung
// Es gibt auch Parierwaffen,
if ( objectp(tmp=QueryProp(P_PARRY_WEAPON)) )
{
res2=tmp->QueryDefend(dam_type, spell, enemy);
// Reduzierbare Wirksamkeit der Parierwaffe?
if ( member(spell[SP_REDUCE_ARMOUR],P_PARRY_WEAPON)
&& intp(res=spell[SP_REDUCE_ARMOUR][P_PARRY_WEAPON]) && (res>=0) )
{
res2=(res2*res)/100;
}
dam-=res2;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
}
// Jetzt kommen die Ruestungen des Lebewesens ins Spiel (wenn es denn
// welche traegt)
armours=QueryProp(P_ARMOURS)-({0});
if ( (i=sizeof(armours))>0 ) {
string aty;
tmp=armours[random(i)];
if ( objectp(tmp) )
//Uebergabe des Mappings eh als Pointer/Referenz, daher billig
tmp->TakeFlaw(dam_type,spell[EINFO_DEFEND]);
// pro Ruestung ein Key an Platz reservieren
spell[EINFO_DEFEND][DEFEND_ARMOURS]=m_allocate(i,2);
foreach(object armour : armours) {
if ( objectp(armour) ) {
aty=armour->QueryProp(P_ARMOUR_TYPE);
if ( member(spell[SP_REDUCE_ARMOUR],aty)
&& intp(res2=spell[SP_REDUCE_ARMOUR][aty]) && (res2>=0) )
dam -= (res2*armour->QueryDefend(dam_type, spell, enemy))/100;
else
dam -= armour->QueryDefend(dam_type, spell, enemy);
// Schaden NACH DefendFunc vermerken.
// Schutzwirkung VOR DefendFunc (DEF_ARMOUR_PROT) vermerkt
// das QueryDefend() selber.
spell[EINFO_DEFEND][DEFEND_ARMOURS][armour,DEF_ARMOUR_DAM]=dam;
// akt. Schaden vermerken und Schutz der aktuellen Ruestung (vor
// DefendFunc) wieder auf 0 setzen fuer den naechsten Durchlauf.
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
spell[EINFO_DEFEND][DEFEND_CUR_ARMOUR_PROT]=0;
}
}
}
// Manche Gilden haben einen Verteidigungsskill. Der kommt jetzt zum
// tragen. Der Skill kann die Schadenshoehe und -art beeinflussen.
spell[EINFO_DEFEND]+=([DEFEND_GUILD:({})]);
if ( mappingp(res=UseSkill(SK_MAGIC_DEFENSE,
([ SI_ENEMY : enemy,
SI_SKILLDAMAGE : dam,
SI_SKILLDAMAGE_TYPE : dam_type,
SI_SPELL : spell ]))) )
{
dam=res[SI_SKILLDAMAGE];
if ( pointerp(res[SI_SKILLDAMAGE_TYPE]) )
dam_type=res[SI_SKILLDAMAGE_TYPE];
spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
spell[EINFO_DEFEND][DEFEND_GUILD]=({dam,dam_type});
}
// Evtl. interne Modifikationen
InternalModifyDefend(&dam, &dam_type, &spell, &enemy);
spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
// Testen, ob irgendwas im Inventory des Lebewesen auf den Angriff
// "reagiert"
CheckSensitiveAttack(dam,dam_type,spell,enemy);
if ( !objectp(enemy) )
return 0;
// Angriffszeit im Gegner setzen
enemy->SetProp(P_LAST_COMBAT_TIME,time());
// Die Resistenzen des Lebewesens (natuerliche und durch Ruestungen
// gesetzte) beruecksichtigen
dam = to_int(CheckResistance(dam_type)*dam);
spell[EINFO_DEFEND][DEFEND_RESI]=dam;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
// Bei physikalischen Angriffen wird die natuerliche Panzerung und die
// Geschicklichkeit des Lebewesens beruecksichtigt
object stat = find_object("/d/erzmagier/zesstra/pacstat"); // TODO: remove
if ( spell[SP_PHYSICAL_ATTACK] )
{
// Schutz bestimmen, Minimum 1, aber nur wenn P_BODY > 0
// Um Rundungsverluste zu reduzieren, wird P_BODY anfangs mit 10000
// skaliert. Beim runterskalieren wird aufgerundet (Addition von
// 5000 vor Division).
int body = (QueryProp(P_BODY)+QueryAttribute(A_DEX)) * 10000;
res2 = ((body/4 + random(body*3/4 + 1) + 5000)/10000) || 1;
if (stat)
stat->bodystat(body, res2, (random(body)+1)/10000);
// Reduzierbare Wirksamkeit des Bodies?
if ( member(spell[SP_REDUCE_ARMOUR], P_BODY)
&& intp(res=spell[SP_REDUCE_ARMOUR][P_BODY]) && (res>=0) )
res2=(res2*res)/100;
dam-=res2;
}
spell[EINFO_DEFEND][DEFEND_BODY]=dam;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
// Ist ueberhaupt noch etwas vom Schaden uebrig geblieben?
if ( dam<0 )
dam = 0;
spell[EINFO_DEFEND][CURRENT_DAM]=dam;
// fuer die Statistik
// TODO: entfernen nach Test-Uptime
if (stat)
stat->damagestat(spell[EINFO_DEFEND]);
// Die Anzahl der abzuziehenden Lebenspunkte ist der durch 10 geteilte
// Schadenswert
dam = dam / 10;
spell[EINFO_DEFEND][DEFEND_LOSTLP]=dam;
// evtl. hat entweder der Aufrufer oder das Lebewesen selber eigene
// Schadensmeldungen definiert. Die vom Aufrufer haben Prioritaet.
mixed dam_msg = spell[SP_SHOW_DAMAGE];
// Wenn != 0 (d.h. Treffermeldungen grundsaetzlich erwuenscht), aber kein
// Array, hier im Living fragen.
if (dam_msg && !pointerp(dam_msg))
dam_msg = QueryProp(P_DAMAGE_MSG);
// In den meisten Faellen soll auch eine Schadensmeldung ausgegeben
// werden, die die Hoehe des Schadens (grob) anzeigt
if (spell[SP_SHOW_DAMAGE] && !pointerp(dam_msg)) {
myname=name(WEN);
enname=enemy->Name(WER);
if (enemy->QueryProp(P_PLURAL)) {
switch (dam) {
case 0:
tell_object(enemy,sprintf(" Ihr verfehlt %s.\n",myname));
tell_object(this_object(),sprintf(" %s verfehlen Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s verfehlen %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 1:
tell_object(enemy,sprintf(" Ihr kitzelt %s am Bauch.\n",myname));
tell_object(this_object(),
sprintf(" %s kitzeln Dich am Bauch.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s kitzeln %s am Bauch.\n",enname,myname),
({ enemy, this_object() }));
break;
case 2..3:
tell_object(enemy,sprintf(" Ihr kratzt %s.\n",myname));
tell_object(this_object(),sprintf(" %s kratzen Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s kratzen %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 4..5:
tell_object(enemy,sprintf(" Ihr trefft %s.\n",myname));
tell_object(this_object(),sprintf(" %s treffen Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s treffen %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 6..10:
tell_object(enemy,sprintf(" Ihr trefft %s hart.\n",myname));
tell_object(this_object(),sprintf(" %s treffen Dich hart.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s treffen %s hart.\n",enname,myname),
({ enemy, this_object() }));
break;
case 11..20:
tell_object(enemy,sprintf(" Ihr trefft %s sehr hart.\n",myname));
tell_object(this_object(),
sprintf(" %s treffen Dich sehr hart.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s treffen %s sehr hart.\n",enname,myname),
({ enemy, this_object() }));
break;
case 21..30:
tell_object(enemy,
sprintf(" Ihr schlagt %s mit dem Krachen brechender Knochen.\n",
myname));
tell_object(this_object(),
sprintf(" %s schlagen Dich mit dem Krachen brechender Knochen.\n",
enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s schlagen %s mit dem Krachen brechender Knochen.\n",
enname,myname), ({ enemy, this_object() }));
break;
case 31..50:
tell_object(enemy,
sprintf(" Ihr zerschmettert %s in kleine Stueckchen.\n",myname));
tell_object(this_object(),
sprintf(" %s zerschmettern Dich in kleine Stueckchen.\n",
enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s zerschmettern %s in kleine Stueckchen.\n",
enname,myname), ({ enemy, this_object() }));
break;
case 51..75:
tell_object(enemy,sprintf(" Ihr schlagt %s zu Brei.\n",myname));
tell_object(this_object(),
sprintf(" %s schlagen Dich zu Brei.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s schlagen %s zu Brei.\n",enname,myname),
({ enemy, this_object() }));
break;
case 76..100:
tell_object(enemy,sprintf(" Ihr pulverisiert %s.\n",myname));
tell_object(this_object(),sprintf(" %s pulverisieren Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s pulverisieren %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 101..150:
tell_object(enemy,sprintf(" Ihr zerstaeubt %s.\n",myname));
tell_object(this_object(),sprintf(" %s zerstaeuben Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s zerstaeuben %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 151..200:
tell_object(enemy,sprintf(" Ihr atomisiert %s.\n",myname));
tell_object(this_object(),sprintf(" %s atomisieren Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s atomisieren %s.\n",enname,myname),
({ enemy, this_object() }));
break;
default:
tell_object(enemy,sprintf(" Ihr vernichtet %s.\n",myname));
tell_object(this_object(),sprintf(" %s vernichten Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s vernichten %s.\n",enname,myname),
({ enemy, this_object() }));
break;
}
}
else {
switch (dam) {
case 0:
tell_object(enemy,sprintf(" Du verfehlst %s.\n",myname));
tell_object(this_object(),sprintf(" %s verfehlt Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s verfehlt %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 1:
tell_object(enemy,sprintf(" Du kitzelst %s am Bauch.\n",myname));
tell_object(this_object(),
sprintf(" %s kitzelt Dich am Bauch.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s kitzelt %s am Bauch.\n",enname,myname),
({ enemy, this_object() }));
break;
case 2..3:
tell_object(enemy,sprintf(" Du kratzt %s.\n",myname));
tell_object(this_object(),sprintf(" %s kratzt Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s kratzt %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 4..5:
tell_object(enemy,sprintf(" Du triffst %s.\n",myname));
tell_object(this_object(),sprintf(" %s trifft Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s trifft %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 6..10:
tell_object(enemy,sprintf(" Du triffst %s hart.\n",myname));
tell_object(this_object(),sprintf(" %s trifft Dich hart.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s trifft %s hart.\n",enname,myname),
({ enemy, this_object() }));
break;
case 11..20:
tell_object(enemy,sprintf(" Du triffst %s sehr hart.\n",myname));
tell_object(this_object(),
sprintf(" %s trifft Dich sehr hart.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s trifft %s sehr hart.\n",enname,myname),
({ enemy, this_object() }));
break;
case 21..30:
tell_object(enemy,
sprintf(" Du schlaegst %s mit dem Krachen brechender Knochen.\n",
myname));
tell_object(this_object(),
sprintf(" %s schlaegt Dich mit dem Krachen brechender Knochen.\n",
enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s schlaegt %s mit dem Krachen brechender Knochen.\n",
enname,myname), ({ enemy, this_object() }));
break;
case 31..50:
tell_object(enemy,
sprintf(" Du zerschmetterst %s in kleine Stueckchen.\n",myname));
tell_object(this_object(),
sprintf(" %s zerschmettert Dich in kleine Stueckchen.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s zerschmettert %s in kleine Stueckchen.\n",
enname,myname), ({ enemy, this_object() }));
break;
case 51..75:
tell_object(enemy,sprintf(" Du schlaegst %s zu Brei.\n",myname));
tell_object(this_object(),
sprintf(" %s schlaegt Dich zu Brei.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s schlaegt %s zu Brei.\n",enname,myname),
({ enemy, this_object() }));
break;
case 76..100:
tell_object(enemy,sprintf(" Du pulverisierst %s.\n",myname));
tell_object(this_object(),sprintf(" %s pulverisiert Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s pulverisiert %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 101..150:
tell_object(enemy,sprintf(" Du zerstaeubst %s.\n",myname));
tell_object(this_object(),sprintf(" %s zerstaeubt Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s zerstaeubt %s.\n",enname,myname),
({ enemy, this_object() }));
break;
case 151..200:
tell_object(enemy,sprintf(" Du atomisierst %s.\n",myname));
tell_object(this_object(),sprintf(" %s atomisiert Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s atomisiert %s.\n",enname,myname),
({ enemy, this_object() }));
break;
default:
tell_object(enemy,sprintf(" Du vernichtest %s.\n",myname));
tell_object(this_object(),sprintf(" %s vernichtet Dich.\n",enname));
tell_room(environment(enemy)||environment(this_object()),
sprintf(" %s vernichtet %s.\n",enname,myname),
({ enemy, this_object() }));
break;
}
}
}
// Man kann auch selbst-definierte Schadensmeldungen ausgeben lassen
else if( spell[SP_SHOW_DAMAGE] && pointerp(dam_msg) )
{
for( i=sizeof(dam_msg) ; --i >= 0 ; )
{
if ( dam>dam_msg[i][0] )
{
tell_object(ME,mess(dam_msg[i][1],ME,enemy));
tell_object(enemy,mess(dam_msg[i][2],ME,enemy));
say(mess(dam_msg[i][3],ME,enemy), enemy);
break;
}
}
}
// else (!spell[SP_SHOW_DAMAGE]) keine Schadensmeldung.
// Informationen ueber den letzten Angriff festhalten
Set(P_LAST_DAMTYPES, dam_type);
Set(P_LAST_DAMTIME, time());
Set(P_LAST_DAMAGE, dam);
// Bei Angriffen mit SP_NO_ENEMY-Flag kann man nicht sterben ...
if ( spell[SP_NO_ENEMY] )
reduce_hit_points(dam);
// ... bei allen anderen natuerlich schon
else
do_damage(dam,enemy);
// evtl. ist dies Objekt hier tot...
if (!objectp(ME)) return dam;
// Testen, ob man in die Fucht geschlagen wird
CheckWimpyAndFlee();
// Verursachten Schaden (in LP) zurueckgeben
return dam;
}
public float CheckResistance(string *dam_type) {
//funktion kriegt die schadensarten uebergeben, schaut sich dieses
//sowie P_RESISTENCE_STRENGTH und P_RESITENCE_MODFIFIER an und berechnet
//einen Faktor, mit dem die urspruengliche Schadensmenge multipliziert
//wird, um den Schaden nach Beruecksichtigung der Resis/Anfaelligkeiten zu
//erhalten. Rueckgabewert normalerweise (s.u.) >=0.
mapping rstren, mod;
float faktor,n;
int i;
mod = Query(P_RESISTANCE_MODIFIER);
if ( mappingp(mod) )
mod = mod["me"];
if ( !mappingp(rstren=Query(P_RESISTANCE_STRENGTHS)) )
{
if (!mappingp(mod))
return 1.0;
else
rstren = ([]);
}
if ( !mappingp(mod) )
mod = ([]);
if ( (i=sizeof(dam_type))<1 )
return 1.0;
n=to_float(i);
faktor=0.0;
//dies hier tut nicht mehr das gewuenschte, wenn in P_RESISTENCE_STRENGTHS
//Faktoren <-1.0 angegeben werden. Rueckgabewerte <0 sind dann moeglich und
//leider werden die Schadensarten ohne Resis nicht mehr richtig verwurstet. :-/
foreach(string dt: dam_type) {
faktor = faktor + (1.0 + to_float(rstren[dt]))
* (1.0 + to_float(mod[dt]))-1.0;
}
return 1.0+(faktor/n);
}
public varargs mapping StopHuntingMode(int silent)
{ mapping save_enemy;
save_enemy=enemies;
if ( !silent )
walk_mapping(enemies, #'StopHuntText); //#');
enemies=m_allocate(0,1);
last_attack_msg=0;
return save_enemy;
}
public <object*|int*>* QueryEnemies()
{
return ({m_indices(enemies),m_values(enemies)});
}
public mapping GetEnemies()
{
return enemies;
}
public mapping SetEnemies(<object*|int*>* myenemies)
{
enemies=mkmapping(myenemies[0],myenemies[1]);
return enemies;
}
private string _kill_alias( string str )
{
return "\\" + str;
}
public varargs void Flee( object oldenv, int force )
{ mixed *exits, exit, dex;
mapping tmp;
int i;
object env;
if ( !environment() )
return;
// mit 'force' kann man die Checks umgehen, damit der Spieler auf jeden
// Fall fluechtet ...
if ( !force && ( oldenv && (oldenv != environment()) ||
query_once_interactive(ME) && (!EnemyPresent() ||
(QueryProp(P_HP) >= QueryProp(P_WIMPY))) ) )
return;
// ... aber Magier fluechten zu lassen ist nicht ganz so einfach ;-)
if ( query_once_interactive(this_object()) && IS_LEARNING(this_object()) )
return;
// Geister brauchen nicht um ihr Leben zu fuerchten
if ( QueryProp(P_GHOST) )
return;
tell_object( ME, "Die Angst ist staerker als Du ... "+
"Du willst nur noch weg hier.\n");
if ( TeamFlee() ) // Erfolgreiche Flucht in hintere Kampfreihe?
return;
env = environment();
tmp = environment()->QueryProp(P_EXITS);
exits = m_indices(tmp);
tmp = environment()->QueryProp(P_SPECIAL_EXITS);
exits += m_indices(tmp);
if ( query_once_interactive(ME) )
exits = map( exits, #'_kill_alias/*'*/ );
// die Fluchtrichtung von Magiern wird aus Sicherheitsgruenden
// nicht ausgewertet
if ( interactive(ME) && IS_SEER(ME) && !IS_LEARNER(ME)
&& (dex=QueryProp(P_WIMPY_DIRECTION)) )
{
i = 60 + 4 * (QueryProp(P_LEVEL) - 30) / 3;
exits += ({dex}); // bevorzugte Fluchtrichtung mindestens einmal
}
if ( !sizeof(exits) )
{
tell_object( ME, "Du versuchst zu fliehen, schaffst es aber nicht.\n" );
return;
}
while ( sizeof(exits) && (environment()==env) )
{
if ( dex // Vorzugsweise Fluchtrichtung?
&& (member(exits,dex) >= 0) // moeglich?
&& (random(100) <= i)) // und Wahrscheinlichkeit gross genug?
exit = dex;
else
exit = exits[random(sizeof(exits))];
catch(command(exit);publish);
exits -= ({exit});
}
if ( environment()==env )
tell_object( ME, "Dein Fluchtversuch ist gescheitert.\n" );
}
public object EnemyPresent()
{
foreach(object en: enemies) {
if (environment()==environment(en))
return en;
}
return 0;
}
public object InFight()
{
return EnemyPresent();
}
public varargs int StopHuntID(string str, int silent) {
if ( !stringp(str) )
return 0;
int j;
foreach(object en: enemies) {
if (en->id(str)) {
StopHuntFor(en,silent);
j++;
}
}
return j;
}
public int SpellDefend(object caster, mapping sinfo)
{ int re;
mixed res;
string *ind;
re = UseSkill(SK_SPELL_DEFEND,([ SI_SKILLARG : sinfo ,
SI_ENEMY : caster ]) );
if ( (res=QueryProp(P_MAGIC_RESISTANCE_OFFSET)) && mappingp(res)
&& pointerp(sinfo[SI_MAGIC_TYPE]))
{
ind = m_indices(res) & sinfo[SI_MAGIC_TYPE];
if (pointerp(ind) && sizeof(ind) ) {
foreach(string index : ind)
re+=res[index];
}
}
else if(res && intp(res))
re+=res;
if ( (re>3333) && query_once_interactive(this_object()) )
re=3333; /* Maximal 33% Abwehrchance bei Spielern */
return re;
}
// **** this is property-like
static int _set_attack_busy(mixed val)
{
if ( ((to_int(val))>5) && previous_object(1)
&& query_once_interactive(previous_object(1)) )
log_file("ATTACKBUSY",sprintf("%s %d Taeter: %O Opfer: %O\n",
dtime(time()),to_int(val),previous_object(1),this_object()));
attack_busy-=to_int(val*100.0);
if ( attack_busy<-2000 )
attack_busy=-2000;
return attack_busy;
}
static int _query_attack_busy()
{
if (IS_LEARNING(ME))
return 0;
return (attack_busy<100);
}
// **** local property methods
static int _set_wimpy(int i)
{
if ( !intp(i) || (i>QueryProp(P_MAX_HP)) || (i<0) )
return 0;
// ggf. Statusreport ausgeben
if (interactive(ME))
status_report(DO_REPORT_WIMPY, i);
return Set(P_WIMPY, i);
}
static string _set_wimpy_dir(string s) {
// ggf. Statusreport ausgeben
if (interactive(ME))
status_report(DO_REPORT_WIMPY_DIR, s);
return Set(P_WIMPY_DIRECTION, s, F_VALUE);
}
static mixed _set_hands(mixed h)
{
if ( sizeof(h)==2 )
h += ({ ({DT_BLUDGEON}) });
if (!pointerp(h[2]))
h[2] = ({h[2]});
return Set(P_HANDS, h, F_VALUE);
}
//TODO normalisieren/korrigieren in updates_after_restore().
static mixed _query_hands()
{
mixed *hands = Query(P_HANDS);
if ( !hands )
return Set(P_HANDS, ({ " mit blossen Haenden", 30, ({DT_BLUDGEON})}));
else if ( sizeof(hands)<3 )
return Set(P_HANDS, ({hands[0], hands[1], ({DT_BLUDGEON})}));
else if ( !pointerp(hands[2]) )
return Set(P_HANDS, ({hands[0], hands[1], ({ hands[2] })}));
return Query(P_HANDS);
}
static int _query_total_wc()
{ mixed res;
int totwc;
if ( objectp(res=QueryProp(P_WEAPON)) )
totwc = res->QueryProp(P_WC);
else if ( pointerp(res=QueryProp(P_HANDS)) && sizeof(res)>1
&& intp(res[1]) )
totwc=res[1];
else
totwc=30;
totwc = ((2*totwc)+(10*QueryAttribute(A_STR)))/3;
return Set(P_TOTAL_WC, totwc, F_VALUE);
}
static int _query_total_ac() {
int totac = 0;
object *armours = QueryProp(P_ARMOURS);
object parry = QueryProp(P_PARRY_WEAPON);
if ( member(armours,0)>=0 ) {
armours -= ({ 0 });
}
foreach(object armour: armours)
totac += armour->QueryProp(P_AC);
if ( objectp(parry) )
totac += parry->QueryProp(P_AC);
totac += (QueryProp(P_BODY)+QueryAttribute(A_DEX));
return Set(P_TOTAL_AC, totac, F_VALUE);
}
static mapping _query_resistance_strengths() {
UpdateResistanceStrengths();
mapping rstren = copy(Query(P_RESISTANCE_STRENGTHS, F_VALUE));
mapping mod = Query(P_RESISTANCE_MODIFIER, F_VALUE);
if ( !mappingp(rstren) )
rstren = ([]);
if ( !mappingp(mod) || !mappingp(mod=mod["me"]) || !sizeof(mod) )
return rstren;
foreach(string modkey, float modvalue : mod)
rstren[modkey] = ((1.0+rstren[modkey])*(1.0+modvalue))-1.0;
return rstren;
}
static int _set_disable_attack(int val)
{
if (val<-100000)
{
log_file("PARALYSE_TOO_LOW",
sprintf("Wert zu klein: %s, Wert: %d, "
"Aufrufer: %O, Opfer: %O",
ctime(time()),
val,previous_object(1),
this_object()));
}
if ( val>30 )
val=30;
if ( (val>=20) && previous_object(1)!=ME && query_once_interactive(ME) )
log_file("PARALYSE",sprintf("%s %d Taeter: %O Opfer: %O\n",
ctime(time())[4..15],
val,previous_object(1),this_object()));
if (time()<QueryProp(P_NEXT_DISABLE_ATTACK))
{
// gueltige Zeitsperre existiert.
// Erhoehen von P_DISABLE_ATTACK geht dann nicht. (Ja, auch nicht erhoehen
// eines negativen P_DISABLE_ATTACK)
if (val >= QueryProp(P_DISABLE_ATTACK))
return DISABLE_TOO_EARLY;
// val ist kleiner als aktuelles P_DISABLE_ATTACK - das ist erlaubt, ABER es
// darf die bestehende Zeitsperre nicht verringern, daher wird sie nicht
// geaendert.
return Set(P_DISABLE_ATTACK,val,F_VALUE);
}
// es existiert keine gueltige Zeitsperre - dann wird sie nun gesetzt.
// (Sollte val < 0 sein, wird eine Zeitsperre in der Vergangenheit gesetzt,
// die schon abgelaufen ist. Das ist ueberfluessig, schadet aber auch
// nicht.)
SetProp(P_NEXT_DISABLE_ATTACK,time()+val*2*__HEART_BEAT_INTERVAL__);
return Set(P_DISABLE_ATTACK,val);
}
// Neue Verwaltung der Haende:
// P_HANDS_USED_BY enhaelt ein Array mit allen Objekten, die Haende
// belegen, jedes kommt so oft vor wie Haende belegt werden.
static mixed *_query_hands_used_by()
{
return ((Query(P_HANDS_USED_BY, F_VALUE) || ({}) ) - ({0}));
}
static int _query_used_hands()
{
return sizeof(QueryProp(P_HANDS_USED_BY));
}
static int _query_free_hands()
{
return (QueryProp(P_MAX_HANDS)-QueryProp(P_USED_HANDS));
}
public varargs int UseHands(object ob, int num)
{ mixed *h;
if ( !ob && !(ob=previous_object(1)) )
return 0;
if ( (num<=0) && ((num=ob->QueryProp(P_HANDS))<=0) )
return 0;
h=QueryProp(P_HANDS_USED_BY)-({ob});
if ( (sizeof(h)+num)>QueryProp(P_MAX_HANDS) )
return 0;
foreach(int i: num)
h+=({ob});
SetProp(P_HANDS_USED_BY,h);
return 1;
}
public varargs int FreeHands(object ob)
{
if ( !ob && !(ob=previous_object(1)) )
return 0;
SetProp(P_HANDS_USED_BY,QueryProp(P_HANDS_USED_BY)-({ob}));
return 1;
}
// Kompatiblitaetsfunktionen:
static int _set_used_hands(int new_num)
{ int old_num, dif;
object ob;
old_num=QueryProp(P_USED_HANDS);
if ( !objectp(ob=previous_object(1)) )
return old_num;
// Meldung ins Debuglog schreiben. Aufrufer sollte gefixt werden.
debug_message(sprintf("P_USED_HANDS in %O wird gesetzt durch %O\n",
this_object(), ob), DMSG_LOGFILE|DMSG_STAMP);
if ( !(dif=new_num-old_num) )
return new_num;
if ( dif>0 )
UseHands(ob,dif);
else
FreeHands(ob);
return QueryProp(P_USED_HANDS);
}
// Funktionen fuer Begruessungsschlag / Nackenschlag:
public int CheckEnemy(object ob)
{
return (living(ob) && IsEnemy(ob));
}
public varargs void ExecuteMissingAttacks(object *remove_attackers)
{
if ( !pointerp(missing_attacks) )
missing_attacks=({});
if ( pointerp(remove_attackers) )
missing_attacks-=remove_attackers;
foreach(object ob : missing_attacks) {
if ( objectp(ob) && (environment(ob)==environment()) )
ob->Attack2(ME);
}
missing_attacks=({});
}
public void InitAttack()
{ object ob,next;
closure cb;
if ( !living(ME) )
return;
ExecuteMissingAttacks();
//EMA kann das Living zerstoeren oder toeten...
if (!living(ME) || QueryProp(P_GHOST)) return;
if ( objectp(ob=IsTeamMove()) )
cb=symbol_function("InitAttack_Callback",ob);
else
cb=0;
for ( ob=first_inventory(environment()) ; objectp(ob) ; ob=next)
{
next=next_inventory(ob);
if ( !living(ob) )
continue;
if (ob->IsEnemy(ME))
{
// Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
// aktualisiert und b) werden Teammitglieder von mir bei diesem
// InsertEnemy() ggf. erfasst.
ob->InsertEnemy(ME);
if ( closurep(cb) && funcall(cb,ob) ) // Wird ganzes Team gemoved?
missing_attacks += ({ ob }); // Dann erstmal warten bis alle da sind
else
ob->Attack2(ME);
}
else if ( IsEnemy(ob) )
{
// Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
// aktualisiert und b) werden Teammitglieder von ob bei diesem
// InsertEnemy() ggf. erfasst.
InsertEnemy(ob);
Attack2(ob);
}
//Attack2 kann dieses Objekt zerstoeren oder toeten. Wenn ja: abbruch
if ( !living(ME) || QueryProp(P_GHOST)) break;
}
}
public void ExitAttack()
{
if ( !living(ME) )
return;
// Noch nachzuholende Begruessungsschlaege:
ExecuteMissingAttacks();
}
public object|object*|mapping QueryArmourByType(string type)
{
// Rueckgabewert:
// DIE Ruestung vom Typ <type>, falls <type> nicht AT_MISC,
// Array aller AT_MISC-Ruestungen falls <type> AT_MISC (auch leer),
// Mapping mit allen oben genannten Infos, falls <type> Null
object *armours;
string typ2;
// Wenn Cache vorhanden, dann Cache liefern.
if (mappingp(QABTCache)) {
if (type == AT_MISC)
return QABTCache[AT_MISC] - ({0});
else if (type)
return QABTCache[type];
else
return copy(QABTCache);
}
if ( !pointerp(armours=QueryProp(P_ARMOURS)) )
armours=({});
// Cache erzeugen
QABTCache = ([ AT_MISC: ({}) ]);
foreach(object ob: armours - ({0}) ) {
if ( !stringp(typ2=ob->QueryProp(P_ARMOUR_TYPE)) )
continue;
if ( typ2==AT_MISC )
QABTCache[AT_MISC] += ({ob});
else
QABTCache[typ2] = ob;
}
// Und gewuenschtes Datum liefern.
if (type)
return QABTCache[type];
else
return copy(QABTCache);
}
//TODO: langfristig waers besser, wenn hier nicht jeder per SetProp() drauf
//los schreiben wuerde.
static object *_set_armours(object *armours) {
if (pointerp(armours)) {
// Cache wegwerfen
QABTCache = 0;
// armours ggf. unifizieren. Ausserdem Kopie reinschreiben.
return Set(P_ARMOURS, m_indices(mkmapping(armours)), F_VALUE);
}
return QueryProp(P_ARMOURS);
}
/** Reduziert die Befriedezaehler pro Reset im Durchschnitt um 2.5.
Berechnet ganzzahlige durchschnittliche Resets seit dem letzten Expire und
erhoeht die letzte Expirezeit um Resets*__RESET_TIME__ (Standard Intervall
fuer Reset ist momentan 3600s, im Durchschnitt kommen dann 2700 zwischen 2
Resets bei raus). So wird der unganzzahlige Rest beim naechsten Aufruf
beruecksichtigt.
Diese Variante des Expires wurde gewaehlt, um zu vermeiden, combat.c einen
reset() zu geben und in jedem Reset in jedem Lebewesen ein Expire machen zu
muessen, auch wenn nur in sehr wenigen Lebewesen was gemacht werden muss.
@param[in,out] ph Mapping aus P_PEACE_HISTORY. Wird direkt aktualisiert.
@attention Muss ein gueltiges P_PEACE_HISTORY uebergeben bekommen, anderem
Datentyp inkl. 0 wird die Funktion buggen.
*/
private void _decay_peace_history(mixed ph) {
// Ganzzahlige resets seit letztem Expire ermitteln. (Durchschnitt)
int resets = (time() - ph[0]) / (__RESET_TIME__*75/100);
// auf Zeitstempel draufrechnen, damit der unganzzahlige Rest nicht
// verlorengeht, der wird beim naechsten Expire dann beruecksichtigt.
ph[0] += resets * (__RESET_TIME__ * 75 / 100);
// pro Reset werden im Durchschnitt 2.5 Versuche abgezogen. (Hier werden
// beim Expire mal 2 und mal 3 Versuche pro Reset gerechnet. Aber falls hier
// viele Resets vergangen sind, ist es vermutlich eh egal, weil die Counter
// auf 0 fallen.)
int expire = resets * (random(2) + 2);
// ueber alle Gilden
mapping tmp=ph[1];
foreach(string key, int count: &tmp ) {
count-=expire;
if (count < 0) count = 0;
}
}
/** Pacify() dient zur Bestimmung, ob sich ein Lebewesen gerade
* befrieden lassen will.
* Berechnet eine Wahrscheinlichkeit nach unten stehender Formel, welche die
* Intelligenz dieses Lebenwesens, die Intelligenz des Casters und die
* bisherige Anzahl erfolgreicher Befriedungsversuche dieser Gilde eingeht.
* Anschliessend wird aus der Wahrscheinlichkeit und random() bestimmt, ob
* dieser Versuch erfolgreich ist.
Formel: w = (INT_CASTER + 10 - ANZ*4) / (INT_ME + 10)
INT_CASTER: Caster-Intelligenz, INT_ME: Intelligenz dieses Livings
ANZ: Anzahl erfolgreicher Befriedungsversuche
Annahme: INT_CASTER === 22, alle Wahrscheinlichkeiten * 100
INT_ME Erfolgswahrscheinlichkeiten je nach Anzahl erfolgreicher Versuche
1 2 3 4 5 6 7 8
0 280 240 200 160 120 80 40 0
2 233,33 200 166,67 133,33 100 66,67 33,33 0
4 200 171,43 142,86 114,29 85,71 57,14 28,57 0
6 175 150 125 100 75 50 25 0
8 155,56 133,33 111,11 88,89 66,67 44,44 22,22 0
10 140 120 100 80 60 40 20 0
12 127,27 109,09 90,91 72,73 54,55 36,36 18,18 0
14 116,67 100 83,33 66,67 50 33,33 16,67 0
16 107,69 92,31 76,92 61,54 46,15 30,77 15,38 0
18 100 85,71 71,43 57,14 42,86 28,57 14,29 0
20 93,33 80 66,67 53,33 40 26,67 13,33 0
22 87,5 75 62,5 50 37,5 25 12,5 0
24 82,35 70,59 58,82 47,06 35,29 23,53 11,76 0
26 77,78 66,67 55,56 44,44 33,33 22,22 11,11 0
28 73,68 63,16 52,63 42,11 31,58 21,05 10,53 0
30 70 60 50 40 30 20 10 0
32 66,67 57,14 47,62 38,1 28,57 19,05 9,52 0
34 63,64 54,55 45,45 36,36 27,27 18,18 9,09 0
35 62,22 53,33 44,44 35,56 26,67 17,78 8,89 0
36 60,87 52,17 43,48 34,78 26,09 17,39 8,7 0
38 58,33 50 41,67 33,33 25 16,67 8,33 0
40 56 48 40 32 24 16 8 0
42 53,85 46,15 38,46 30,77 23,08 15,38 7,69 0
44 51,85 44,44 37,04 29,63 22,22 14,81 7,41 0
46 50 42,86 35,71 28,57 21,43 14,29 7,14 0
48 48,28 41,38 34,48 27,59 20,69 13,79 6,9 0
50 46,67 40 33,33 26,67 20 13,33 6,67 0
52 45,16 38,71 32,26 25,81 19,35 12,9 6,45 0
54 43,75 37,5 31,25 25 18,75 12,5 6,25 0
56 42,42 36,36 30,3 24,24 18,18 12,12 6,06 0
58 41,18 35,29 29,41 23,53 17,65 11,76 5,88 0
60 40 34,29 28,57 22,86 17,14 11,43 5,71 0
62 38,89 33,33 27,78 22,22 16,67 11,11 5,56 0
64 37,84 32,43 27,03 21,62 16,22 10,81 5,41 0
66 36,84 31,58 26,32 21,05 15,79 10,53 5,26 0
68 35,9 30,77 25,64 20,51 15,38 10,26 5,13 0
70 35 30 25 20 15 10 5 0
72 34,15 29,27 24,39 19,51 14,63 9,76 4,88 0
74 33,33 28,57 23,81 19,05 14,29 9,52 4,76 0
76 32,56 27,91 23,26 18,6 13,95 9,3 4,65 0
78 31,82 27,27 22,73 18,18 13,64 9,09 4,55 0
80 31,11 26,67 22,22 17,78 13,33 8,89 4,44 0
82 30,43 26,09 21,74 17,39 13,04 8,7 4,35 0
84 29,79 25,53 21,28 17,02 12,77 8,51 4,26 0
86 29,17 25 20,83 16,67 12,5 8,33 4,17 0
88 28,57 24,49 20,41 16,33 12,24 8,16 4,08 0
90 28 24 20 16 12 8 4 0
92 27,45 23,53 19,61 15,69 11,76 7,84 3,92 0
94 26,92 23,08 19,23 15,38 11,54 7,69 3,85 0
96 26,42 22,64 18,87 15,09 11,32 7,55 3,77 0
98 25,93 22,22 18,52 14,81 11,11 7,41 3,7 0
100 25,45 21,82 18,18 14,55 10,91 7,27 3,64 0
* @return 1, falls Befrieden erlaubt ist, 0 sonst.
* @param[in] caster Derjenige, der den Spruch ausfuehrt.
* @attention Wenn acify() 1 zurueckliefert, zaehlt dies als
* erfolgreicher Befriedungsversuch und in diesem Lebewesen wurde
* StopHuntingMode(1) aufgerufen.
*/
public int Pacify(object caster) {
// wenn das Viech keine Gegner hat, dann witzlos. ;-) Ohne Caster gehts auch
// direkt raus.
if (!mappingp(enemies) || !sizeof(enemies)
|| !objectp(caster)) {
return 0;
}
// Wenn P_ACCEPT_PEACE gesetzt ist, altes Verhalten wiederspiegeln
// -> der NPC ist einfach immer befriedbar. Gleiches gilt fuer den Caster
// selber, der wird sich ja nicht gegen das eigene Befriede wehren. Und auch
// im team wehrt man sich nicht gegen das Befriede eines Teamkollegen
if (QueryProp(P_ACCEPT_PEACE)==1 || caster==ME
|| member(TeamMembers(), caster) > -1) {
StopHuntingMode(1); // Caster/Gilde sollte eigene Meldung ausgeben
return 1;
}
string gilde = caster->QueryProp(P_GUILD) || "ANY";
// ggf. P_PEACE_HISTORY initialisieren
<int|mapping>* ph = QueryProp(P_PEACE_HISTORY);
if (!pointerp(ph))
SetProp(P_PEACE_HISTORY, ph=({time(), ([]) }) );
// ggf. die Zaehler reduzieren.
if ( ph[0] + __RESET_TIME__ * 75/100 < time()) {
_decay_peace_history(&ph);
}
float w = (caster->QueryAttribute(A_INT) + 10 - ph[1][gilde] * 4.0) /
(QueryAttribute(A_INT) + 10);
// auf [0,1] begrenzen.
if (w<0) w=0.0;
else if (w>1) w=1.0;
// w * n ist eine Zahl zwischen 0 und n, wenn w * n > random(n)+1,
// darf befriedet werden. Da das Random fuer grosse Zahlen
// besser verteilt ist, nehm ich n = __INT_MAX__ und vernachlaessige
// ausserdem die +1 beim random().
if (ceil(w * __INT_MAX__) > random(__INT_MAX__) ) {
ph[1][gilde]++;
StopHuntingMode(1);
return 1;
}
// ein SetProp(P_PEACE_HISTORY) kann entfallen, da das Mapping direkt
// geaendert wird. Sollte die Prop allerdings mal ne Querymethode haben,
// welche eine Kopie davon liefert, muss das hier geaendert oder die
// Prop per Query() abgefragt werden.
return 0;
}