Added public files
Roughly added all public files. Probably missed some, though.
diff --git a/std/living/combat.c b/std/living/combat.c
new file mode 100644
index 0000000..880f3f6
--- /dev/null
+++ b/std/living/combat.c
@@ -0,0 +1,2311 @@
+// 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
+#pragma pedantic
+
+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 string magic_attack;
+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;
+private nosave mapping peace_tries;
+// 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, ({}));
+ attack_busy=100;
+ att2_time=0;
+ enemies=([]);
+ peace_tries=([]);
+ 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+((float)resimap[old[i]]))*2.0-1.0;
+
+ for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
+ resimap[arg[i]]=(1.0+((float)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+((float)resimap[old[i]]))*0.5-1.0;
+
+ for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
+ resimap[arg[i]]=(1.0+((float)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, arena;
+ 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 = (int)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 = (mixed)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 = (int)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 "+(string)funcall(cl,WEM,0));
+ ainfo[SI_SKILLDAMAGE_MSG2] = (" mit "+(string)funcall(cl,WEM,1));
+
+ ainfo[SI_SKILLDAMAGE] = (int)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] = (string)funcall(cl,P_WEAPON_TYPE);
+ ainfo[P_NR_HANDS] = (int)funcall(cl,P_NR_HANDS);
+ ainfo[P_WC] = (int)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]=((int)(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]=(int)res[SI_SKILLDAMAGE];
+ ainfo[SI_SKILLDAMAGE_TYPE]=res[SI_SKILLDAMAGE_TYPE];
+
+ if ( stringp(res[SI_SKILLDAMAGE_MSG]) )
+ ainfo[SI_SKILLDAMAGE_MSG] = " mit "+(string)res[SI_SKILLDAMAGE_MSG];
+ else
+ ainfo[SI_SKILLDAMAGE_MSG] = " mit magischen Faehigkeiten";
+
+ if ( stringp(res[SI_SKILLDAMAGE_MSG2]) )
+ ainfo[SI_SKILLDAMAGE_MSG2] = " mit "+(string)res[SI_SKILLDAMAGE_MSG2];
+ else
+ ainfo[SI_SKILLDAMAGE_MSG2] = (string)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(((int)res[1])+1)
+ + 10*(QueryAttribute(A_STR)) )/3);
+ ainfo[SI_SKILLDAMAGE_TYPE] = res[2];
+ ainfo[SI_SKILLDAMAGE_MSG] = ainfo[SI_SKILLDAMAGE_MSG2]
+ = ((string)res[0]);
+ ainfo[P_WEAPON_TYPE] = WT_HANDS;
+ ainfo[P_WC] = (int)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)
+{ object *defs;
+
+ if ( !objectp(friend) )
+ return;
+
+ if ( !pointerp(defs=QueryProp(P_DEFENDERS)) )
+ defs=({});
+
+ if ( member(defs,friend)>=0 )
+ return;
+
+ defs+=({friend});
+ SetProp(P_DEFENDERS,defs);
+}
+
+public void RemoveDefender(object friend)
+{ object *defs;
+
+ if ( !objectp(friend) )
+ return;
+
+ if ( !pointerp(defs=QueryProp(P_DEFENDERS)) )
+ defs=({});
+
+ if ( member(defs,friend)==-1 )
+ return;
+
+ defs -= ({friend});
+ SetProp(P_DEFENDERS,defs);
+}
+
+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,x;
+ 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;
+}
+
+public int Defend(int dam, string|string* dam_type, int|mapping spell, object enemy)
+{
+ int i,k;
+ mixed res,res2;
+ object *armours,tmp;
+ mixed hookData;
+ mixed hookRes;
+
+ // string what, how;
+ string enname, myname;
+
+ // this_player(), wenn kein enemy bekannt...
+ enemy ||= this_player();
+ // Testen, ob dieses Lebewesen ueberhaupt angegriffen werden darf
+ if ( !this_object() || !enemy || QueryProp(P_NO_ATTACK)
+ || ( query_once_interactive(enemy) && ! interactive(enemy) ) )
+ return 0;
+
+ if ( intp(spell) )
+ spell = ([ SP_PHYSICAL_ATTACK : !spell,
+ SP_SHOW_DAMAGE : !spell,
+ SP_REDUCE_ARMOUR : ([ ]) ]);
+ else if ( !mappingp(spell) ) // Illegaler spell-Parameter
+ return 0;
+
+ // testen ob eine erweiterte defendinfo vorhanden ist
+ if(!member(spell,EINFO_DEFEND))
+ {
+ //spell+=([EINFO_DEFEND:([])]); // ggf hinzufuegen
+ // use a temporary mapping to avoid recursive
+ // val[x][y] = deep_copy(val);
+ mapping tmpdefend = ([
+ ORIGINAL_AINFO:deep_copy(spell),
+ ORIGINAL_DAM:dam,
+ ORIGINAL_DAMTYPE:dam_type,
+ ]);
+ spell[EINFO_DEFEND]=tmpdefend;
+ }
+
+ // Schadenstyp ueberpruefen
+ if ( !pointerp(dam_type) )
+ dam_type = ({ dam_type });
+
+ 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
+ if ( res=QueryProp(P_DEFENDERS) )
+ { object *defs,*defs_here;
+
+ defs=({});
+ defs_here=({});
+ if ( !pointerp(res) )
+ res=({res});
+ // erst alle anwesenden finden.
+ foreach(object defender: res) {
+ if ( objectp(defender) && (member(defs,defender)<0) )
+ {
+ defs+=({defender});
+ // Verteidiger muessen im gleichen Raum oder im Living selber
+ // enthalten sein.
+ if ( environment(defender) == environment()
+ || environment(defender) == ME)
+ {
+ call_other(defender,"InformDefend",enemy);
+ if (defender)
+ defs_here += ({ defender });
+ }
+ }
+ }
+ //Anwesende Verteidiger eintragen.
+ spell[EINFO_DEFEND][PRESENT_DEFENDERS]=defs_here;
+
+ // P_DEFENDERS auch gleich aktualisieren
+ if ( sizeof(defs)<1 )
+ defs=0;
+ SetProp(P_DEFENDERS,defs);
+
+ if ( spell[SP_PHYSICAL_ATTACK] ) {
+ // Bei physischen Angriffen nur Verteidiger aus Reihe 1
+ // nehmen (z.B. fuer Rueckendeckung)
+ foreach(object defender: defs_here) {
+ if ( (defender->PresentPosition())>1 ) {
+ defs_here-=({defender});
+ }
+ }
+ }
+
+ if ( (i=sizeof(defs_here)) )
+ {
+ mixed edefendtmp=({defs_here[random(i)],0,0,0});
+ res=call_other(edefendtmp[DEF_DEFENDER],"DefendOther",
+ dam,dam_type,spell,enemy);
+ if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0])
+ && pointerp(res[1]))
+ {
+ // Helfer koennen den Schaden oder Schadenstyp aendern,
+ // z.B. Umwandlung von Feuer nach Eis oder so...
+ 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]);
+ }
+ 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
+
+ if ( !member(spell,SP_REDUCE_ARMOUR) || !mappingp(spell[SP_REDUCE_ARMOUR]) )
+ spell[SP_REDUCE_ARMOUR] = ([]);
+
+ // 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=(string)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 -= (int)(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=(int)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] )
+ {
+ // Minimum ist auch hier 1.
+ int body = QueryProp(P_BODY)+QueryAttribute(A_DEX);
+ res2 = (body/4 + random(body*3/4 + 1)) || 1;
+ if (stat)
+ stat->bodystat(body, res2, random(body)+1);
+
+ // 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;
+ int i;
+
+ 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 = ((int)(res->QueryProp(P_WC)));
+ else if ( pointerp(res=QueryProp(P_HANDS)) && sizeof(res)>1
+ && intp(res[1]) )
+ totwc=((int)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 += ((int)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
+ mixed ph = (mixed)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;
+}
+