Added public files
Roughly added all public files. Probably missed some, though.
diff --git a/std/living/attributes.c b/std/living/attributes.c
new file mode 100644
index 0000000..90027a4
--- /dev/null
+++ b/std/living/attributes.c
@@ -0,0 +1,671 @@
+// MorgenGrauen MUDlib
+//
+// living/attributes.c -- attributes for living objects
+//
+// $Id: attributes.c 8375 2013-02-12 21:52:58Z Zesstra $
+
+#include <sys_debug.h>
+
+// attribute handling
+//
+// filter_ldfied: str, dex, con, int
+//
+// In den Attributen und Abilites werden Rassenspzifische und erworbene
+// Faehigkeiten (mit autoload-Eigenschaft) abgepseichert.
+//
+// Funktionen:
+// SetAttribute( attr, val ) (Erzeuge und) setze das Attribut auf val.
+// QueryAttribute( attr ) Gebe den Wert des Attributes zurueck.
+//
+// Wenn ein Objekt eine Funktion _filterattr_<name> beitzt, wird beim Setzen
+// des Attributes <name>, der vorgeschlagene Wert uebergeben und der von
+// dieser Funktion zurueckgegebene gesetzt. (fuer ueberpruefungszwecke)
+// Gleiches gilt fuer _filterabil_<name>.
+
+#pragma strict_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <thing/properties.h>
+#include <attributes.h>
+#include <player/gmcp.h>
+#undef NEED_PROTOTYPES
+
+#include <config.h>
+#include <properties.h>
+
+mapping attributes; // Dies sind die mit ZTs veraenderbaren Attribute
+mapping attributes_modifier; // Modifier sollen gespeichert werden
+nosave mixed* attributes_timed_mods; //P_TIMED_ATTR_MOD
+nosave mapping attributes_offsets; // Offsets NICHT speichern!
+nosave mapping used_attributes_offsets; // Zur Beschleunigung der Berechnung
+nosave object* all_modifiers; // objekte mit P_X/M_ATTR_MOD, P_X/M_HEALTH_MOD
+nosave object* invalid_modifiers; // objekte welche das limit ueberschreiten
+nosave int cumulative_mod; // bilanz der P_X/M_ATTR_MOD
+nosave int hp_off;
+nosave int sp_off;
+
+
+nomask public int SetTimedAttrModifier(string key, mapping modifier,
+ int outdated, object dependent, mixed notify) {
+ if(!key || key=="" || !modifier || (outdated>0 && outdated<time()) ||
+ (notify && !stringp(notify) && !objectp(notify)) ) {
+ return TATTR_INVALID_ARGS;
+ }
+
+
+ if(member(attributes_timed_mods[TATTR_ENTRIES],key)) {
+ // change entry
+ attributes_timed_mods[TATTR_ENTRIES][key,TATTR_MOD]=modifier;
+ attributes_timed_mods[TATTR_ENTRIES][key,TATTR_OUTDATED]=outdated;
+ attributes_timed_mods[TATTR_ENTRIES][key,TATTR_DEPENDENT]=dependent;
+ attributes_timed_mods[TATTR_ENTRIES][key,TATTR_NOTIFY]=notify;
+ }
+ else {
+ // add entry
+ attributes_timed_mods[TATTR_ENTRIES]+=([key:modifier;outdated;dependent;notify]);
+ }
+
+ // add outdate
+ if(outdated>0 && member(attributes_timed_mods[TATTR_OUTDATE],outdated)==-1)
+ {
+ attributes_timed_mods[TATTR_OUTDATE]+=({outdated});
+ attributes_timed_mods[TATTR_OUTDATE]=
+ sort_array(attributes_timed_mods[TATTR_OUTDATE],#'<);
+ }
+
+ // add dependent
+ if(objectp(dependent))
+ {
+ if(member(attributes_timed_mods[TATTR_DEPENDENTS],key))
+ {
+ attributes_timed_mods[TATTR_DEPENDENTS][key]=dependent;
+ }
+ else
+ {
+ attributes_timed_mods[TATTR_DEPENDENTS]+=([key:dependent]);
+ }
+ }
+ else
+ {
+ // remove previously set dependent
+ if(member(attributes_timed_mods[TATTR_DEPENDENTS],key))
+ {
+ attributes_timed_mods[TATTR_DEPENDENTS]-=([key]);
+ }
+ }
+
+ UpdateAttributes();
+
+ return TATTR_OK;
+}
+
+nomask public mapping QueryTimedAttrModifier(string key)
+{
+ int outdated;
+ object dependent;
+ mixed notify;
+ mapping mod;
+
+ if(!key || key=="")
+ {
+ return ([]);
+ }
+
+ if(!member(attributes_timed_mods[TATTR_ENTRIES],key))
+ {
+ return ([]);
+ }
+
+ mod=deep_copy(attributes_timed_mods[TATTR_ENTRIES][key,TATTR_MOD]);
+ outdated=attributes_timed_mods[TATTR_ENTRIES][key,TATTR_OUTDATED];
+ dependent=attributes_timed_mods[TATTR_ENTRIES][key,TATTR_DEPENDENT];
+ notify=attributes_timed_mods[TATTR_ENTRIES][key,TATTR_NOTIFY];
+
+ return ([key:mod;outdated;dependent;notify ]);
+}
+
+nomask public int DeleteTimedAttrModifier(string key)
+{
+ if(!key || key=="")
+ {
+ return TATTR_INVALID_ARGS;
+ }
+
+ if(!member(attributes_timed_mods[TATTR_ENTRIES],key))
+ {
+ return TATTR_NO_SUCH_MODIFIER;
+ }
+
+ attributes_timed_mods[TATTR_DEPENDENTS]-=([key]);
+ attributes_timed_mods[TATTR_ENTRIES]-=([key]);
+ UpdateAttributes();
+
+ return TATTR_OK;
+}
+
+nomask protected void attribute_hb()
+{
+ int now,i,k,update,outdated;
+ string* keys;
+ mapping tonotify;
+
+ // initialize
+ now=time();
+ tonotify=([]);
+
+ keys=m_indices(attributes_timed_mods[TATTR_ENTRIES]);
+
+ // delete outdated
+ for(i=sizeof(attributes_timed_mods[TATTR_OUTDATE])-1;i>=0;i--)
+ {
+ outdated=attributes_timed_mods[TATTR_OUTDATE][i];
+ if(outdated>now)
+ {
+ break;
+ }
+
+ for(k=sizeof(keys)-1;k>=0;k--)
+ {
+ if(attributes_timed_mods[TATTR_ENTRIES][keys[k],TATTR_OUTDATED]==outdated)
+ {
+ // bei fehlendem notifier wurde das zum verhaengnis
+ // dank an gloinson
+ /*
+ if(objectp(attributes_timed_mods[TATTR_ENTRIES][keys[k],TATTR_NOTIFY]))
+ {
+ */
+ tonotify+=([keys[k]:attributes_timed_mods[TATTR_ENTRIES][keys[k],TATTR_NOTIFY]]);
+ //}
+
+ attributes_timed_mods[TATTR_DEPENDENTS]-=([keys[k]]);
+ attributes_timed_mods[TATTR_ENTRIES]-=([keys[k]]);
+ keys-=({keys[k]});
+ }
+ }
+
+ attributes_timed_mods[TATTR_OUTDATE]-=({outdated});
+ }
+
+ // delete depending
+ keys=m_indices(attributes_timed_mods[TATTR_DEPENDENTS]);
+ for(i=sizeof(keys)-1;i>=0;i--)
+ {
+ if(!objectp(attributes_timed_mods[TATTR_DEPENDENTS][keys[i]]))
+ {
+ // siehe oben
+ /*
+ if(objectp(attributes_timed_mods[TATTR_ENTRIES][keys[i],TATTR_NOTIFY]))
+ {
+ */
+ tonotify+=([keys[i]:attributes_timed_mods[TATTR_ENTRIES][keys[i],TATTR_NOTIFY]]);
+ //}
+
+ attributes_timed_mods[TATTR_DEPENDENTS]-=([keys[i]]);
+ attributes_timed_mods[TATTR_ENTRIES]-=([keys[i]]);
+ keys-=({keys[i]});
+ }
+ }
+
+
+ // update
+ if(sizeof(tonotify))
+ {
+ UpdateAttributes();
+ call_out(#'notifyExpiredModifiers,0,tonotify);
+ }
+}
+
+nomask protected void notifyExpiredModifiers(mapping nots)
+{
+ int i;
+ string* keys;
+ while(remove_call_out(#'notifyExpiredModifiers)!=-1);
+
+ if(!nots)
+ {
+ return;
+ }
+
+ keys=m_indices(nots);
+ for(i=sizeof(nots)-1;i>=0;i--)
+ {
+ if(nots[keys[i]] &&
+ ( objectp(nots[keys[i]])||stringp(nots[keys[i]]) ) )
+ {
+ call_other(nots[keys[i]],"NotifyTimedAttrModExpired",keys[i]);
+ }
+ }
+}
+
+
+// invalide modifier benachrichtigen
+nomask protected void notifyInvalidModifiers() {
+
+ while(remove_call_out(#'notifyInvalidModifiers)!=-1);
+
+ if(!invalid_modifiers) {
+ invalid_modifiers=({});
+ }
+
+ call_other(invalid_modifiers,"NotifyXMAttrModLimitViolation");
+
+}
+
+
+// welche Modifier / modif. Objekte wirken nun?
+protected nomask void calculate_valid_modifiers() {
+ closure qp;
+ mapping res;
+ string key;
+ int wert;
+ // Unterscheidung Bonus <-> Malus, weil der Malus voll eingehen soll
+ int hp_malus, sp_malus;
+
+ used_attributes_offsets=([]);
+ cumulative_mod=0;
+ hp_off=sp_off=0;
+ invalid_modifiers=({});
+
+ // rassenspezifische boni P_ATTRIBUTES_OFFSETS
+ if ( mappingp(attributes_offsets) )
+ used_attributes_offsets+=attributes_offsets;
+
+ if (!pointerp(all_modifiers) || !sizeof(all_modifiers)) {
+ // in diesem Fall koennen wir hier direkt mit dieser Funktion Schluss
+ // machen. ;-)
+ return;
+ }
+ else
+ all_modifiers-=({0}); //zerstoerte Objekte rauswerfen.
+
+ // einmal ueber alle modifizierenden Objekt iterieren und aufaddieren
+ foreach(object ob: all_modifiers) {
+ qp = symbol_function("QueryProp",ob);
+
+ if (!objectp(ob) || environment(ob)!=this_object()) {
+ all_modifiers-=({ob});
+ continue;
+ }
+
+ // ext. Attribut-Modifier
+ if ( mappingp(res=funcall(qp,P_X_ATTR_MOD)) ) {
+ foreach(key, wert: res) {
+ cumulative_mod+=wert;
+ }
+ }
+
+ // magic Attribut-Modifier
+ if ( mappingp(res=funcall(qp,P_M_ATTR_MOD))
+ && ( funcall(qp,P_WORN)
+ || funcall(qp,P_WIELDED) ) ) {
+ foreach(key, wert: res) {
+ cumulative_mod+=wert;
+ }
+ }
+ // Magic Health-Modifier
+ if ( mappingp(res=funcall(qp,P_M_HEALTH_MOD))
+ && ( funcall(qp,P_WORN)
+ || funcall(qp,P_WIELDED) ) ) {
+ if (res[P_HP] < 0)
+ hp_malus += res[P_HP];
+ else
+ hp_off+=res[P_HP];
+
+ if (res[P_SP] < 0)
+ sp_malus += res[P_SP];
+ else
+ sp_off+=res[P_SP];
+ }
+
+ // external Health Modifier
+ if ( mappingp(res=funcall(qp,P_X_HEALTH_MOD)) ) {
+ if (res[P_HP] < 0)
+ hp_malus += res[P_HP];
+ else
+ hp_off+=res[P_HP];
+
+ if (res[P_SP] < 0)
+ sp_malus += res[P_SP];
+ else
+ sp_off+=res[P_SP];
+ }
+
+ } // Ende 1. foreach()
+
+
+ // und nochmal, soviele Objekt wieder rausschmeissen, bis das Limit wieder
+ // unterschritten wird. (Verbesserungsvorschlaege erwuenscht.) :-(
+ foreach(object ob: all_modifiers) {
+
+ qp = symbol_function("QueryProp",ob);
+
+ if ( mappingp(res=funcall(qp,P_X_ATTR_MOD)) ) {
+ if(cumulative_mod>CUMULATIVE_ATTR_LIMIT) {
+ invalid_modifiers+=({ob});
+ foreach(key, wert: res) {
+ cumulative_mod-=wert;
+ }
+ }
+ else {
+ add_offsets(res);
+ }
+ }
+
+ if ( mappingp(res=funcall(qp,P_M_ATTR_MOD))
+ && ( funcall(qp,P_WORN)
+ || funcall(qp,P_WIELDED) ) ) {
+ if(cumulative_mod>CUMULATIVE_ATTR_LIMIT) {
+ if(member(invalid_modifiers,ob)==-1) {
+ invalid_modifiers+=({ob});
+ }
+ foreach(key, wert: res) {
+ cumulative_mod-=wert;
+ }
+ }
+ else {
+ add_offsets(res);
+ }
+ }
+ }
+
+ // HEALTH_MOD werden durch eine Formel 'entschaerft', damit man nur schwer
+ // das Maximum von 150 erreichen kann. (Formel von Humni, beschlossen auf 3.
+ // EM-Treffen)
+ // Mali gehen aber voll ein
+ hp_off = (int)(150 - (150*150.0/(150 + hp_off))) + hp_malus;
+ sp_off = (int)(150 - (150*150.0/(150 + sp_off))) + sp_malus;
+
+ /* alte Version
+ hp_off += hp_malus;
+ sp_off += sp_malus;
+ */
+
+ // notify invalid modifiers
+ if(sizeof(invalid_modifiers)>0) {
+ call_out(#'notifyInvalidModifiers,0);
+ }
+}
+
+// abmelden eines modifiers
+nomask public void deregister_modifier(object modifier)
+{
+ if (!pointerp(all_modifiers)) {
+ return;
+ }
+
+ // valid object?
+ if (!objectp(modifier) || member(all_modifiers,modifier)==-1) {
+ return;
+ }
+
+ all_modifiers-=({modifier});
+ if (invalid_modifiers) {
+ invalid_modifiers-=({modifier});
+ }
+}
+
+// anmelden eines modifiers
+nomask public void register_modifier(object modifier) {
+ closure qp;
+
+ if (!pointerp(all_modifiers)) {
+ all_modifiers=({});
+ }
+
+ // valid object?
+ if (!objectp(modifier) || environment(modifier)!=this_object() ||
+ member(all_modifiers,modifier)!=-1) {
+ return;
+ }
+
+ qp = symbol_function("QueryProp",modifier);
+ // modifier after all? Die P_M_* muessen getragen/gezueckt sein.
+ if(mappingp(funcall(qp,P_X_ATTR_MOD)) ||
+ mappingp(funcall(qp,P_X_HEALTH_MOD)) ||
+ ((mappingp(funcall(qp,P_M_ATTR_MOD)) ||
+ mappingp(funcall(qp,P_M_HEALTH_MOD))) &&
+ (funcall(qp,P_WORN) || funcall(qp,P_WIELDED)) ) ) {
+ all_modifiers+=({modifier});
+ }
+}
+
+protected void add_offsets(mapping arr) {
+ mixed *ind;
+ int i;
+
+ if ( !mappingp(arr) )
+ return;
+
+ foreach(string key, int wert: arr) {
+ used_attributes_offsets[key]+=wert;
+ }
+}
+
+public void UpdateAttributes() {
+ mixed *ind;
+ int i;
+
+ // alle gueltigen Modifier ermitteln aus Objekten im Inventar.
+ calculate_valid_modifiers();
+
+ // persistente modifier drauf (frosch zb)
+ // aus P_ATTRIBUTES_MODIFIERS
+ if ( mappingp(attributes_modifier) ) {
+ foreach(mixed key, mapping wert: attributes_modifier) {
+ add_offsets(wert); // Modifier addieren...
+ }
+ }
+ // timed modifier drauf aus P_TIMED_ATTR_MOD
+ ind=m_indices(attributes_timed_mods[TATTR_ENTRIES]);
+ for ( i=sizeof(ind)-1 ; i>=0 ; i-- ) {
+ // Modifier addieren...
+ add_offsets(attributes_timed_mods[TATTR_ENTRIES][ind[i],0]);
+ }
+ // Bei Monstern werden die HP/SP ueblicherweise selbst gesetzt
+ if ( !query_once_interactive(this_object()))
+ return;
+
+ SetProp(P_MAX_HP, QueryAttribute(A_CON)*8+42+hp_off);
+ SetProp(P_MAX_SP, QueryAttribute(A_INT)*8+42+sp_off);
+
+ if(QueryProp(P_HP)>QueryProp(P_MAX_HP)) {
+ SetProp(P_HP,QueryProp(P_MAX_HP));
+ }
+
+ if(QueryProp(P_SP)>QueryProp(P_MAX_SP)) {
+ SetProp(P_SP,QueryProp(P_MAX_SP));
+ }
+
+ GMCP_Char( ([ A_INT: QueryAttribute(A_INT),
+ A_DEX: QueryAttribute(A_DEX),
+ A_STR: QueryAttribute(A_STR),
+ A_CON: QueryAttribute(A_CON) ]) );
+}
+
+
+protected void create() {
+ hp_off=sp_off=0;
+ used_attributes_offsets=([]);
+ all_modifiers=({});
+ invalid_modifiers=({});
+ cumulative_mod=0;
+
+ Set(P_ATTRIBUTES_MODIFIER, attributes_modifier=([])); // nicht geschuetzt
+ Set(P_ATTRIBUTES_OFFSETS, attributes_offsets=([]));
+ Set(P_ATTRIBUTES_OFFSETS, PROTECTED, F_MODE);
+ Set(P_ATTRIBUTES, attributes=([]));
+ Set(P_ATTRIBUTES, PROTECTED, F_MODE);
+
+ Set(P_TIMED_ATTR_MOD, attributes_timed_mods=({ ({}),([]),([]) }));
+ Set(P_TIMED_ATTR_MOD, NOSETMETHOD|SECURED, F_MODE_AS);
+}
+
+static mixed _query_timed_attr_mod() {
+ mixed ret;
+ return Set(P_TIMED_ATTR_MOD,
+ ({attributes_timed_mods[0],
+ deep_copy(attributes_timed_mods[1]),
+ deep_copy(attributes_timed_mods[2])}));
+}
+
+static mapping _set_attributes(mapping arr)
+{
+ Set(P_ATTRIBUTES, attributes=arr);
+ UpdateAttributes();
+ return arr;
+}
+
+static mapping _query_attributes()
+{
+ return deep_copy(Set(P_ATTRIBUTES, attributes));
+}
+
+static mapping _set_attributes_offsets(mapping arr)
+{
+ Set(P_ATTRIBUTES_OFFSETS, attributes_offsets=arr);
+ UpdateAttributes();
+ return attributes_offsets;
+}
+
+static mapping _query_attributes_offsets()
+{
+ return deep_copy(Set(P_ATTRIBUTES_OFFSETS, attributes_offsets));
+}
+
+static mixed _set_attributes_modifier(mixed arr)
+{ string fn;
+ mixed pre;
+ mapping map_ldfied;
+
+ if ( pointerp(arr) && (sizeof(arr)>=2) )
+ {
+ pre=arr[0];
+ map_ldfied=arr[1];
+ }
+ else
+ {
+ pre=previous_object();
+ map_ldfied=arr;
+ }
+
+ if ( objectp(pre) )
+ fn=old_explode(object_name(pre),"#")[0];
+ else
+ fn=pre;
+
+ if ( !stringp(fn) )
+ return 0;
+
+ // wenn Modifier kein mapping oder ein leeres Mapping: loeschen
+ if ( !mappingp(map_ldfied) || !sizeof(map_ldfied))
+ m_delete(attributes_modifier,fn);
+ else
+ attributes_modifier[fn]=map_ldfied;
+
+ Set(P_ATTRIBUTES_MODIFIER, attributes_modifier);
+ UpdateAttributes();
+ return attributes_modifier[fn];
+}
+
+static mapping _query_attributes_modifier()
+{
+ return deep_copy(attributes_modifier);
+}
+
+public int SetAttr(string attr, int val)
+{ closure filter_ldfied;
+
+ if ( filter_ldfied = symbol_function("_filterattr_"+attr, this_object()) )
+ val = funcall(filter_ldfied, val );
+
+ attributes[attr] = val;
+ UpdateAttributes();
+ return val;
+}
+
+public int SetAttribute(string attr, int val)
+{
+ return SetAttr(attr, val-used_attributes_offsets[attr]);
+}
+
+// Diese Funktion sollte zum Abfragen verwendet werden:
+public int QueryAttribute(string attr)
+{ int re;
+
+ re=attributes[attr]+used_attributes_offsets[attr];
+
+ if ( query_once_interactive(this_object()) && (re>30) )
+ re=30;
+
+ return re;
+}
+
+public int QueryRealAttribute(string attr)
+{
+ return attributes[attr];
+}
+
+public int SetRealAttribute(string attr, int val)
+{
+ return SetAttr(attr, val);
+}
+
+public int QueryAttributeOffset(string attr)
+{
+ return used_attributes_offsets[attr];
+}
+
+public status TestLimitViolation(mapping check)
+{
+ int k,test;
+ mixed* ind;
+
+ if(!check || !mappingp(check))
+ {
+ return 0;
+ }
+
+ test=0;
+ ind=m_indices(check);
+ for( k=sizeof(ind)-1 ; k>=0 ; k-- )
+ {
+ test+=check[ind[k]];
+ }
+
+ test+=cumulative_mod;
+ if(test>CUMULATIVE_ATTR_LIMIT)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ *------------------------------------------------------------
+ * attributes compatibility functions
+ */
+
+protected int _filterattr_str(int val)
+{
+ return ( (val<0) ? 0 : (val>20) ? 20 : val );
+}
+
+protected int _filterattr_dex(int val)
+{
+ return ( (val<0) ? 0 : (val>20) ? 20 : val );
+}
+
+protected int _filterattr_int(int val)
+{
+ return ( (val<0) ? 0 : (val>20) ? 20 : val );
+}
+
+protected int _filterattr_con(int val)
+{
+ return ( (val<0) ? 0 : (val>20) ? 20 : val );
+}
diff --git a/std/living/clothing.c b/std/living/clothing.c
new file mode 100644
index 0000000..56aff44
--- /dev/null
+++ b/std/living/clothing.c
@@ -0,0 +1,96 @@
+// MorgenGrauen MUDlib
+//
+// living/clothing.c -- Modul fuer Bekleidungsfragen. ;-)
+//
+// $Id: armour.c,v 3.8 2003/08/25 09:36:04 Rikus Exp $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <thing/properties.h>
+#undef NEED_PROTOTYPES
+#include <living/clothing.h>
+#include <living/combat.h>
+#include <armour.h>
+
+protected void create() {
+ SetProp(P_CLOTHING, ({}));
+ Set(P_CLOTHING, PROTECTED, F_MODE_AS);
+}
+
+public object *FilterClothing(closure filterfun, varargs mixed* extra) {
+ if (!closurep(filterfun))
+ return ({});
+ return apply(#'filter, QueryProp(P_CLOTHING), filterfun, extra);
+}
+
+public object *FilterArmours(closure filterfun, varargs mixed* extra) {
+ if (!closurep(filterfun))
+ return ({});
+ return apply(#'filter, QueryProp(P_ARMOURS)-({0}), filterfun, extra);
+}
+
+public int WearClothing(object ob) {
+ object *clothing = QueryProp(P_CLOTHING);
+ if (member(clothing, ob) != -1)
+ return 0;
+ clothing += ({ob});
+ SetProp(P_CLOTHING, clothing);
+ return 1;
+}
+
+public int WearArmour(object ob) {
+ if (!VALID_ARMOUR_TYPE(ob->QueryProp(P_ARMOUR_TYPE)))
+ return 0;
+
+ object *armours = QueryProp(P_ARMOURS);
+ if (member(armours, ob) != -1)
+ return 0;
+
+ armours += ({ob});
+ SetProp(P_ARMOURS, armours);
+ return 1;
+}
+
+public int Wear(object ob) {
+ // reihenfolge ist wichtig! Ruestung sind _auch_ Kleidung, aber Kleidung
+ // keine Ruestung.
+ if (ob->IsArmour())
+ return WearArmour(ob);
+ else if (ob->IsClothing())
+ return WearClothing(ob);
+ return 0;
+}
+
+public int UnwearClothing(object ob) {
+object *clothing = QueryProp(P_CLOTHING);
+ if (member(clothing, ob) == -1)
+ return 0;
+ clothing -= ({ob});
+ SetProp(P_CLOTHING, clothing);
+ return 1;
+}
+
+public int UnwearArmour(object ob) {
+ object *armours = QueryProp(P_ARMOURS);
+ if (member(armours, ob) == -1)
+ return 0;
+
+ armours -= ({ob});
+ SetProp(P_ARMOURS, armours);
+ return 1;
+}
+
+public int Unwear(object ob) {
+ // reihenfolge ist wichtig! Ruestung sind _auch_ Kleidung, aber Kleidung
+ // keine Ruestung.
+ if (ob->IsArmour())
+ return UnwearArmour(ob);
+ else if (ob->IsClothing())
+ return UnwearClothing(ob);
+ return 0;
+}
+
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;
+}
+
diff --git a/std/living/comm.c b/std/living/comm.c
new file mode 100644
index 0000000..6001ffd
--- /dev/null
+++ b/std/living/comm.c
@@ -0,0 +1,133 @@
+// MorgenGrauen MUDlib
+//
+// living/comm.c -- communiction module for livings
+//
+// $Id$
+
+#pragma strong_types,save_types
+#pragma no_clone
+#pragma pedantic
+#pragma range_check
+
+#include <defines.h>
+#include <living/comm.h>
+
+void create_super()
+{
+ set_next_reset(-1);
+}
+
+protected string comm_guess_action() {
+ string cmd;
+ string action = query_verb();
+ // Die Aktionen sind intern in der Regel nach den haeufigsten Kommandoverben
+ // dieser Aktion benannt. Bei einigen Aktionen sind mehrere Kommandoverben
+ // ueblich, die sollen hier noch abgehandelt werden.
+ switch(action) {
+ case "nehme":
+ // MA_TAKE == nimm
+ action = MA_TAKE;
+ break;
+
+ case "norden":
+ case "nordosten":
+ case "osten":
+ case "suedosten":
+ case "sueden":
+ case "suedwesten":
+ case "westen":
+ case "nordwesten":
+ case "oben":
+ case "unten":
+ case "betrete":
+ case "verlasse":
+ case "teleport":
+ case "teleportiere":
+ action = MA_MOVE;
+ break;
+
+ case "unt":
+ action = MA_LOOK;
+ break;
+
+ case "wirf":
+ if (strstr(query_command(), " weg") > -1)
+ action = MA_PUT;
+ break;
+
+ case "stecke":
+ cmd = query_command();
+ if (strstr(cmd, " weg") > -1)
+ action = MA_UNWIELD;
+ else if (strstr(cmd," in ") > -1)
+ action = MA_PUT;
+ break;
+
+ case "ziehe":
+ cmd = query_command();
+ if (strstr(cmd, " an") > -1)
+ action = MA_WEAR;
+ else if (strstr(cmd, " aus") > -1)
+ action = MA_UNWEAR;
+ break;
+
+ case "esse":
+ case "friss":
+ action = MA_EAT;
+ break;
+
+ case "saufe":
+ action = MA_DRINK;
+ break;
+
+ case "hoere":
+ //MA_LISTEN == lausche
+ action = MA_LISTEN;
+ break;
+ case "lese":
+ action = MA_READ;
+ break;
+
+ case ":":
+ case ";":
+ action = MA_EMOTE;
+ break;
+
+ case "zerbreche":
+ case "zerstoere":
+ case "verbrenne":
+ case "entsorge":
+ action = MA_REMOVE;
+ break;
+ }
+ return action;
+}
+
+protected int comm_guess_message_type(string action, mixed origin) {
+ // everything not mentioned in the switch becomes MT_LOOK.
+ switch(action) {
+ case MA_FIGHT:
+ // Kampf kann man meisten sowohl sehen als auch hoeren.
+ return MT_LOOK | MT_LISTEN;
+ case MA_LISTEN:
+ case MA_SAY:
+ return MT_LISTEN;
+ case MA_FEEL:
+ return MT_FEEL;
+ case MA_SMELL:
+ return MT_SMELL;
+ case MA_CHANNEL:
+ return MT_COMM | MT_FAR;
+ case MA_EMOTE:
+ if (objectp(origin)
+ && environment(origin) == environment())
+ return MT_COMM;
+ else
+ return MT_COMM | MT_FAR;
+ case MA_SHOUT:
+ return MT_LISTEN | MT_FAR;
+ }
+ // die meisten Aktionen sind zumindest sichtbar...
+ return MT_LOOK;
+}
+
diff --git a/std/living/description.c b/std/living/description.c
new file mode 100644
index 0000000..6353ca9
--- /dev/null
+++ b/std/living/description.c
@@ -0,0 +1,332 @@
+// MorgenGrauen MUDlib
+//
+// living/description.c -- description for living objects
+//
+// $Id: description.c 9395 2015-12-08 23:04:38Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/container/description";
+
+#define NEED_PROTOTYPES
+
+#include <living/skills.h>
+#include <living/clothing.h>
+#include <thing/properties.h>
+#include <wizlevels.h>
+#include <new_skills.h>
+#include <properties.h>
+#include <language.h>
+#include <defines.h>
+#include <class.h>
+#include <sys_debug.h>
+
+public string _query_internal_extralook() {
+ mixed xl;
+ int zeit;
+ string res, look="";
+
+ xl=Query(P_INTERNAL_EXTRA_LOOK,F_VALUE);
+ if (!mappingp(xl))
+ return(0);
+
+ foreach(string key, mapping xld: xl) {
+ if (intp(zeit=xld["xlduration"])) {
+ //hat offenbar nen Ablaufdatum
+ if ( (zeit > 0 && zeit < time()) ||
+ (zeit < 0 && abs(zeit) < object_time(ME)) ) {
+ // Zeit abgelaufen oder
+ // negative "Ablaufzeit" und das Objekt ist neuer als die
+ // Eintragzeit, also loeschen und weiter (ja, das geht. ;-) und xld
+ // hat das Eintragsmapping ja noch, weitere Benutzung also moeglich.)
+ m_delete(xl,key);
+ // ggf. Meldung ausgeben
+ if (interactive(ME)) {
+ if (sizeof(xld["xlende"])) {
+ tell_object(ME,xld["xlende"]);
+ }
+ //kein einfacher String, aber Objekt+Funktion gegeben?
+ else if (sizeof(xld["xlendefun"]) && sizeof(xld["xlobjectname"]) &&
+ (!catch(res=call_other(xld["xlobjectname"],xld["xlendefun"],ME)
+ ;publish))) {
+ if (stringp(res) && sizeof(res))
+ tell_object(ME,res);
+ }
+ }
+ continue;
+ }
+ }
+ // Der Eintrag ist offenbar noch gueltig, Meldung anhaengen, bzw. via
+ // Funktionsaufruf beschaffen.
+ if (sizeof(xld["xllook"]))
+ look+=xld["xllook"];
+ else if (sizeof(xld["xlfun"]) && sizeof(xld["xlobjectname"])) {
+ closure cl;
+ if (catch(cl=symbol_function(xld["xlfun"],xld["xlobjectname"]);publish)
+ || !cl) {
+ // wenn Fehler beim Laden/Closure erstellen, dann Eintrag loeschen
+ // -> Annahme, dass dieser Fehler permanent sein wird, z.B. Eintrag
+ // von Clonen
+ m_delete(xl,key);
+ continue;
+ }
+ else if (!catch(res=funcall(cl, ME); publish)) {
+ if (!stringp(res) || !sizeof(res)) {
+ // keinen String oder leeren String gekriegt -> ueberspringen.
+ continue;
+ }
+ else
+ look+=res;
+ }
+ }
+ }
+ // fertig. Wenn look nicht leer ist, zurueckgeben, sonst 0.
+ if (sizeof(look))
+ return(look);
+ else
+ return(0);
+}
+
+public varargs int AddExtraLook(string look, int duration, string key,
+ string lookende, object ob) {
+ mapping xl;
+ string oname;
+ if (!stringp(key) || !sizeof(key)) {
+ // Automatisch erzeugen, wenn moeglich
+ if (!objectp(previous_object()) ||
+ !stringp(key=object_name(previous_object())) || !sizeof(key))
+ return(-1);
+ }
+
+ if (!stringp(look) || !sizeof(look))
+ return(-2);
+ if (!intp(duration))
+ return(-3);
+
+ xl=Query(P_INTERNAL_EXTRA_LOOK,F_VALUE); // dran denken: liefert referenz zurueck
+ if (!mappingp(xl)) {
+ Set(P_INTERNAL_EXTRA_LOOK, xl=([]) );
+ }
+
+ // kein Automatisches Ueberschreiben.
+ if (member(xl,key))
+ return(-4);
+
+ // neg. Werte: "bis Ende/reboot", abs(duration) == Eintragzeit
+ // 0: "fuer ewig", >0: Zeitdauer in Sekunden
+ if (duration > 0)
+ duration+=time(); // hoffentlich gibt es reichtzeitig 64bit-Ints
+ else if (duration < 0)
+ duration=negate(time());
+ // 0 bleibt, wie es ist.
+
+ if (objectp(ob)) {
+ // Funktionsname und Objektname (als Name, damit es auch noch geht, wenn
+ // das Objekt entladen wurde, Crash/reboot war etc.) speichern
+ // Clone werden auch gespeichert, aber es wird direkt ein harter Fehler
+ // ausgeloest, wenn ein permanenter Xlook fuer einen Clone registriert
+ // werden soll: das kann nicht gehen.
+ if (!duration && clonep(ob))
+ raise_error(sprintf(
+ "AddExtraLook(): Fehlerhaftes Argument <duration>: %d, "
+ "permanente Extralooks durch Clone (%s) nicht registrierbar.\n",
+ duration, object_name(ob)));
+
+ xl[key]=(["xlobjectname":object_name(ob),
+ "xlfun": look,
+ ]);
+ // ggf. Name der Funktion speichern, die bei Ablauf aufgerufen wird.
+ if (stringp(lookende) && sizeof(lookende))
+ xl[key]["xlendefun"]=lookende;
+ }
+ else {
+ // Einfacher Eintrag, nur den bearbeiteten String merken. ;-)
+ xl[key]=(["xllook": break_string(replace_personal(look,({ME}),1),78,
+ "",BS_LEAVE_MY_LFS),
+ ]);
+ // ggf. Meldung speichern, die bei Ablauf ausgegeben werden soll.
+ if (stringp(lookende) && sizeof(lookende)) {
+ xl[key]["xlende"]=break_string(replace_personal(lookende,({ME}),1),78,
+ "",BS_LEAVE_MY_LFS);
+ }
+ }
+ // Endezeit vermerken.
+ if (duration != 0)
+ xl[key]["xlduration"]=duration;
+
+ // Kein Set noetig, weil Query das Mapping ja als Referenz lieferte.
+ return(1);
+}
+
+public int RemoveExtraLook(string key) {
+ mapping xl;
+ if (!stringp(key) || !sizeof(key)) {
+ // Automatisch erzeugen, wenn moeglich
+ if (!objectp(previous_object()) ||
+ !stringp(key=object_name(previous_object())) || !sizeof(key))
+ return(-1);
+ }
+ xl=Query(P_INTERNAL_EXTRA_LOOK,F_VALUE); // dran denken: liefert referenz zurueck
+ if (!mappingp(xl))
+ return (-2);
+ if (!member(xl,key))
+ return(-2);
+
+ m_delete(xl,key);
+ // Kein Set noetig, weil Query das Mapping ja als Referenz lieferte.
+ return(1);
+}
+
+void create()
+{
+ ::create();
+ Set(P_GENDER, SAVE, F_MODE);
+ // Extralook-Property speichern und vor manueller Aenderung schuetzen
+ // EMs duerfen, die wissen hoffentlich, was sie tun.
+ Set(P_INTERNAL_EXTRA_LOOK, SAVE|PROTECTED, F_MODE_AS);
+ SetProp(P_CLOTHING,({}));
+ AddId("Living");
+}
+
+string condition()
+{
+ int hpnt, max_hpnt, perc;
+
+ hpnt = QueryProp( P_HP );
+ max_hpnt = QueryProp( P_MAX_HP );
+
+ if(max_hpnt>0 && hpnt>0)
+ perc=100*hpnt/max_hpnt;
+
+ switch(perc) {
+ case 0..9:
+ return capitalize(QueryPronoun(WER))+" steht auf der Schwelle des Todes.\n";
+ case 10..19:
+ return capitalize(QueryPronoun(WER))+" braucht dringend einen Arzt.\n";
+ case 20..29:
+ return capitalize(QueryPronoun(WER))+" ist in keiner guten Verfassung.\n";
+ case 30..39:
+ return capitalize(QueryPronoun(WER))+" wankt bereits bedenklich.\n";
+ case 40..49:
+ return capitalize(QueryPronoun(WER))+" macht einen mitgenommenen Eindruck.\n";
+ case 50..59:
+ return capitalize(QueryPronoun(WER))+" sieht nicht mehr taufrisch aus.\n";
+ case 60..69:
+ return capitalize(QueryPronoun(WER))+" ist leicht angeschlagen.\n";
+ case 70..79:
+ return capitalize(QueryPronoun(WER))+" fuehlte sich heute schon besser.\n";
+ case 80..89:
+ return capitalize(QueryPronoun(WER))+" ist schon etwas geschwaecht.\n";
+ }
+ //fall-through
+ return capitalize(QueryPronoun(WER))+" ist absolut fit.\n";
+}
+
+varargs string long() {
+ string str, cap_pronoun;
+ string descr, invl,tmp,exl;
+ int hpnt, max_hpnt;
+ mixed filter_ldfied;
+ object ob;
+
+ str = process_string( QueryProp(P_LONG) );
+ if(!stringp(str)) str = "";
+
+ str += condition();
+
+ // Extralook
+ if(stringp(tmp = QueryProp(P_EXTRA_LOOK)))
+ str += tmp;
+ if (stringp(tmp = QueryProp(P_INTERNAL_EXTRA_LOOK)))
+ str += tmp;
+ for(ob = first_inventory(ME); ob; ob = next_inventory(ob))
+ if(exl = ob->QueryProp(P_EXTRA_LOOK))
+ str += exl;
+ else if(exl = ob->extra_look())
+ str += exl; // TO BE REMOVED
+
+
+ if(filter_ldfied = QueryProp(P_TRANSPARENT))
+ {
+ invl = make_invlist(PL, all_inventory(ME));
+ if(invl != "")
+ str += capitalize(QueryPronoun(WER))+" traegt bei sich:\n" + invl;
+ }
+ return str;
+}
+
+varargs string name(int casus, int demonst)
+{
+ if( QueryProp( P_INVIS ) )
+ {
+ if( casus == RAW ) return "Jemand";
+ return ({"Jemand","Jemands","Jemandem","Jemanden"})[casus];
+ }
+ if (QueryProp(P_FROG) && casus != RAW )
+ {
+ string s=QueryArticle(casus, 0, 1)+"Frosch";
+ if (casus==WESSEN) s += "es";
+ return s;
+ }
+ return ::name( casus, demonst );
+}
+
+static int _query_gender()
+{
+ if (QueryProp(P_FROG)) return 1;
+ return Query(P_GENDER);
+}
+
+// NPC sollen aus Kompatibilitaetsgruenden auch eine "echte" Rasse haben.
+// Default ist hier die Rasse, man kann aber hiermit auch einen NPC faken,
+// der sich tarnt, indem man P_REAL_RACE per Hand setzt.
+static string _query_real_race()
+{
+ return Query(P_REAL_RACE,F_VALUE)||QueryProp(P_RACE);
+}
+
+static mixed _set_name(mixed nm )
+{
+ string lvnam;
+ lvnam = nm;
+ if(pointerp(nm)) lvnam = nm[0];
+ set_living_name(lower_case(lvnam));
+ return Set(P_NAME, nm);
+}
+
+int _query_container()
+{
+ return 0;
+}
+
+int is_class_member(mixed str) {
+ // Keine Klasse, keine Mitgliedschaft ...
+ if (!str || (!stringp(str) && !pointerp(str)) || str=="")
+ return 0;
+
+ if (::is_class_member(str))
+ return 1;
+
+ if (stringp(str))
+ str = ({str});
+
+ // Rassen werden als implizite Klassen aufgefasst.
+ // TODO: Pruefen, ob das unbedingt hart-kodiert sein muss.
+ string race = QueryProp(P_RACE);
+ if ( stringp(race) && member( str, lower_case(race) ) > -1 )
+ return 1;
+ else
+ return 0;
+}
+
+mapping _query_material() {
+ mixed res;
+
+ if (mappingp(res=Query(P_MATERIAL)))
+ return res;
+ return ([MAT_MISC_LIVING:100]);
+}
+
diff --git a/std/living/helpers.c b/std/living/helpers.c
new file mode 100644
index 0000000..3d292c0
--- /dev/null
+++ b/std/living/helpers.c
@@ -0,0 +1,149 @@
+// MorgenGrauen MUDlib
+//
+// living/helpers.c -- (Query)Methoden fuer Hilfsobjekte, z.B. zum Tauchen
+//
+// $Id: moneyhandler.h,v 3.1 1997/02/12 13:29:09 Wargon Exp %
+
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#include <living/helpers.h>
+#define NEED_PROTOTYPES
+#include <thing/properties.h>
+#undef NEED_PROTOTYPES
+
+public int RegisterHelperObject(object helper, int type,
+ string|closure callback)
+{
+ // cb: closure auf die Callback-Funktion in previous_object()
+ closure cb;
+ // helpers: Mapping aller eingetragenen Helfer-Objekte
+ mapping helpers;
+
+ // Kein positiver Integerwert als Helfertyp uebergeben?
+ if ( !intp(type) || type < 1 )
+ raise_error(sprintf( "Wrong argument 1 to RegisterHelperObject(). "
+ "Expected positive <int>, got %O.\n", type));
+ // Kein Objekt vorhanden, an dem die Callback-Funktion gerufen werden soll?
+ if ( !objectp(helper) )
+ return HELPER_NO_CALLBACK_OBJECT;
+
+ // Funktionsname zum Zweck des Callbacks uebergeben?
+ if ( stringp(callback) ) {
+ // Dann Closure davon erstellen.
+ cb = symbol_function(callback, helper);
+ // Wenn das nicht klappt (zB weil die Funktion private ist), dann
+ // Fehler werfen und abbrechen.
+ if ( !closurep(cb) )
+ raise_error(sprintf("Error in RegisterHelperObject(): Unable to call "
+ "function %s in object %O.\n", callback, helper));
+ }
+ // Wenn schon eine Closure uebergeben wurde, dann diese direkt speichern.
+ else if ( closurep(callback) ) {
+ cb = callback;
+ }
+ // Weder Funktionsname, noch Closure, dann Fehler werfen und abbrechen.
+ else
+ raise_error(sprintf("Wrong argument 2 to RegisterHelperObject(). "
+ "Expected <string/closure>, got %O.\n",callback));
+
+ // Property auslesen und zwischenspeichern
+ helpers = QueryProp(P_HELPER_OBJECTS);
+ // Wenn die Prop leer ist, hier initialisieren
+ if ( !helpers ) {
+ helpers = ([type:({})]);
+ }
+ // Wenn der Typ noch nicht existiert, hier nachtragen.
+ else if ( !pointerp(helpers[type]) ) {
+ helpers[type] = ({});
+ }
+
+ // Closure eintragen, wenn noch nicht vorhanden
+ if ( member(helpers[type], cb)==-1 ) {
+ helpers[type] = helpers[type]+({cb});
+ SetProp(P_HELPER_OBJECTS, helpers);
+ return HELPER_SUCCESS;
+ }
+ else
+ return HELPER_ALREADY_LISTED;
+}
+
+public int UnregisterHelperObject(object helper, int type) {
+ if ( !intp(type) || type < 1 )
+ raise_error(sprintf( "Wrong argument 2 to UnregisterHelperObject(). "
+ "Expected positive <int>, got %O.\n", type));
+ if ( !objectp(helper) )
+ return HELPER_NO_CALLBACK_OBJECT;
+
+ mapping helpers = Query(P_HELPER_OBJECTS, F_VALUE);
+
+ if ( mappingp(helpers) ) {
+ foreach(closure cl: helpers[type]) {
+ if ( get_type_info(cl,2) == helper ) {
+ helpers[type] = helpers[type]-({cl});
+ return HELPER_SUCCESS;
+ }
+ }
+ }
+ return HELPER_NOTHING_TO_UNREGISTER;
+}
+
+// Querymethode fuer P_AQUATIC_HELPERS
+public mapping _query_lib_p_aquatic_helpers() {
+ mapping ret = ([]);
+ // eingetragene Callback-Closures auslesen
+ closure *helpers =
+ ( Query(P_HELPER_OBJECTS, F_VALUE) || ([]) )[HELPER_TYPE_AQUATIC];
+ // Es sind gar keine Werte eingetragen? Dann gleich rausspringen.
+ if ( !pointerp(helpers) )
+ return ret;
+
+ // Nullelement substrahieren
+ helpers -= ({0});
+
+ if ( sizeof(helpers) ) {
+ // Mapping erstellen: Keys sind die Objekte, deren Closures eingetragen
+ // sind. Values sind die Rueckgabewerte der Closures,
+ // die dabei das Spielerobjekt und das abfragende Objekt uebergeben
+ // bekommen.
+ object *keys = map(helpers, #'get_type_info, 2);
+ int *vals = map(helpers, #'funcall, this_object(), previous_object(1));
+ ret = mkmapping(keys,vals);
+ }
+ return ret;
+}
+
+// Querymethode fuer P_AERIAL_HELPERS
+public mapping _query_lib_p_aerial_helpers() {
+ mapping ret = ([]);
+ // eingetragene Callback-Closures auslesen
+ closure *helpers =
+ ( Query(P_HELPER_OBJECTS, F_VALUE) || ([]) )[HELPER_TYPE_AERIAL];
+
+ // Es sind gar keine Werte eingetragen? Dann gleich rausspringen.
+ if ( !pointerp(helpers) )
+ return ret;
+
+ // Nullelement substrahieren
+ helpers -= ({0});
+
+ if ( sizeof(helpers) ) {
+ // Mapping erstellen: Keys sind die Objekte, deren Closures eingetragen
+ // sind. Values sind die Rueckgabewerte der Closures,
+ // die dabei das Spielerobjekt und das abfragende Objekt uebergeben
+ // bekommen.
+ object *keys = map(helpers,#'get_type_info, 2);
+ int *vals = map(helpers, #'funcall, this_object(), previous_object(1));
+ ret = mkmapping(keys,vals);
+ }
+ return ret;
+}
+
+// Querymethode fuer P_HELPER_OBJECTS
+public mapping _query_lib_p_helper_objects() {
+ return deep_copy(Query(P_HELPER_OBJECTS,F_VALUE));
+}
+
diff --git a/std/living/inventory.c b/std/living/inventory.c
new file mode 100644
index 0000000..e3027e4
--- /dev/null
+++ b/std/living/inventory.c
@@ -0,0 +1,66 @@
+// MorgenGrauen MUDlib
+//
+// container/inventory.c -- Umgang mit besonderen Objekten im Inventory
+//
+// $Id: inventory.c 6554 2007-10-17 22:45:53Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/container/inventory";
+
+#define NEED_PROTOTYPES
+
+#include <properties.h>
+#include <sensitive.h>
+#include <attributes.h>
+
+#define ME this_object()
+
+public void RemoveSensitiveObject(object ob) {
+ ::RemoveSensitiveObject(ob);
+ RemoveSensitiveObjectFromList(ob,SENSITIVE_ATTACK);
+ if (objectp(ob) && (ob->QueryProp(P_X_ATTR_MOD) ||
+ ob->QueryProp(P_X_HEALTH_MOD) ))
+ {
+ deregister_modifier(ob);
+ // Erst wenn das Objekt den Spieler verlassen konnte, die Attribute
+ // neu berechnen.
+ if (find_call_out("UpdateAttributes")==-1)
+ call_out("UpdateAttributes",0);
+ }
+}
+
+public void InsertSensitiveObject(object ob, mixed arg) {
+ ::InsertSensitiveObject(ob,arg);
+ if (objectp(ob) && (ob->QueryProp(P_X_ATTR_MOD) ||
+ ob->QueryProp(P_X_HEALTH_MOD) ))
+ {
+ register_modifier(ob);
+ UpdateAttributes();
+ }
+}
+
+public void CheckSensitiveAttack(int dam, mixed dam_type, mixed spell,
+ object enemy) {
+ mixed a,x;
+ int i;
+
+ if (!pointerp(a=QueryProp(P_SENSITIVE_ATTACK)))
+ return;
+ if (!pointerp(dam_type))
+ dam_type=({dam_type});
+ for (i=sizeof(a)-1;i>=0;i--)
+ if (pointerp(x=a[i]) &&
+ dam>x[SENS_THRESHOLD] &&
+ member(dam_type,x[SENS_KEY])>=0 &&
+ objectp(x[SENS_OBJECT]) &&
+ environment(x[SENS_OBJECT])==ME &&
+ closurep(x[SENS_CLOSURE]))
+ funcall(x[SENS_CLOSURE],
+ enemy,x[SENS_KEY],dam,
+ spell,x[SENS_OPT]);
+}
+
diff --git a/std/living/life.c b/std/living/life.c
new file mode 100644
index 0000000..f3a72f5
--- /dev/null
+++ b/std/living/life.c
@@ -0,0 +1,1572 @@
+// MorgenGrauen MUDlib
+//
+// living/life.c -- life variables
+//
+// $Id: life.c 9426 2016-01-03 10:02:57Z Zesstra $
+
+// living object life variables
+//
+// P_ALIGN -- alignment value
+// P_NPC -- if living is an NPC
+// P_HP -- HitPoints
+// P_SP -- SpellPoints
+// P_ALCOHOL -- value of intoxication
+// P_DRINK -- value of soakness
+// P_FOOD -- value of stuffness
+// P_XP -- experience
+// P_POISON -- level of poison
+// P_CORPSE -- corpse-object
+// P_DEAF -- if living is deaf
+#pragma strong_types,save_types,rtt_checks
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <hook.h>
+#include <living/skills.h>
+#include <thing/properties.h>
+#include <living/life.h>
+#include <living/moving.h>
+#include <living/combat.h>
+#include <living/attributes.h>
+#include <thing/description.h>
+#include <thing/language.h>
+#undef NEED_PROTOTYPES
+#include <health.h>
+#include <defines.h>
+#include <new_skills.h>
+#include <scoremaster.h>
+#include <defuel.h>
+#include <properties.h>
+#include <events.h>
+#include <wizlevels.h>
+
+#define ALCOHOL_VALUE(n) n
+
+#include <debug_info.h> //voruebergehend
+
+
+// 'private'-Prototypen
+private void DistributeExp(object enemy, int exp_to_give);
+
+// Variablen
+nosave int delay_alcohol; /* time until next alcohol effect */
+nosave int delay_drink; /* time until next drink effect */
+nosave int delay_food; /* time until next food effect */
+nosave int delay_heal; /* time until next heal effect */
+nosave int delay_sp; /* time until next sp regeneration */
+nosave int delay_poison; /* time until next poison effect */
+nosave int drop_poison;
+nosave mapping enemy_damage;
+nosave mapping hp_buffer;
+nosave mapping sp_buffer;
+nosave int remove_me;
+int nextdefueltimefood;
+int nextdefueltimedrink;
+
+
+protected void create()
+{
+ Set(P_GHOST, SAVE, F_MODE);
+ Set(P_FROG, SAVE, F_MODE);
+ Set(P_ALIGN, SAVE, F_MODE);
+ Set(P_HP, SAVE, F_MODE);
+ Set(P_SP, SAVE, F_MODE);
+ Set(P_XP, SAVE, F_MODE);
+ Set( P_LAST_XP, ({ "", 0 }) );
+ Set( P_LAST_XP, PROTECTED, F_MODE_AS );
+
+ Set(P_ALCOHOL, SAVE, F_MODE);
+ Set(P_DRINK, SAVE, F_MODE);
+ Set(P_FOOD, SAVE, F_MODE);
+ Set(P_POISON, SAVE, F_MODE);
+ Set(P_DEAF, SAVE, F_MODE);
+
+ SetProp(P_FOOD_DELAY, FOOD_DELAY);
+ SetProp(P_DRINK_DELAY, DRINK_DELAY);
+ SetProp(P_ALCOHOL_DELAY, ALCOHOL_DELAY);
+ SetProp(P_HP_DELAY,HEAL_DELAY);
+ SetProp(P_SP_DELAY,HEAL_DELAY);
+ SetProp(P_POISON_DELAY,POISON_DELAY);
+ // default fuer alle Lebewesen (NPC + Spieler):
+ SetProp(P_MAX_POISON, 10);
+ SetProp(P_CORPSE, "/std/corpse");
+
+ nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
+ nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
+
+ enemy_damage=([:2 ]);
+ hp_buffer=([]);
+ sp_buffer=([]);
+
+ SetProp(P_DEFUEL_LIMIT_FOOD,1);
+ SetProp(P_DEFUEL_LIMIT_DRINK,1);
+ SetProp(P_DEFUEL_TIME_FOOD,1);
+ SetProp(P_DEFUEL_TIME_DRINK,1);
+ SetProp(P_DEFUEL_AMOUNT_FOOD,1);
+ SetProp(P_DEFUEL_AMOUNT_DRINK,1);
+
+ offerHook(H_HOOK_DIE,1);
+
+ offerHook(H_HOOK_FOOD,1);
+ offerHook(H_HOOK_DRINK,1);
+ offerHook(H_HOOK_ALCOHOL,1);
+ offerHook(H_HOOK_POISON,1);
+ offerHook(H_HOOK_CONSUME,1);
+}
+
+// Wenn der letzte Kampf lang her ist und das Lebewesen wieder vollgeheilt
+// ist, wird P_ENEMY_DAMAGE zurueckgesetzt.
+protected void ResetEnemyDamage() {
+ if (time() > QueryProp(P_LAST_COMBAT_TIME) + __RESET_TIME__ * 4
+ && QueryProp(P_HP) == QueryProp(P_MAX_HP))
+ enemy_damage=([:2 ]);
+}
+
+private void DistributeExp(object enemy, int exp_to_give) {
+ int total_damage, tmp, ex;
+ mapping present_enemies;
+
+ if ( exp_to_give<=0 )
+ return;
+
+ mapping endmg=deep_copy(enemy_damage);
+
+ // Mitglieder im Team des Killers bekommen:
+ //
+ // Gesamtanteil des Teams
+ // Eigenen Anteil + ----------------------
+ // Anzahl Teammitglieder
+ // ---------------------------------------
+ // 2
+ //
+ object *inv = enemy->TeamMembers();
+ if ( pointerp(inv) )
+ {
+ present_enemies=m_allocate(sizeof(inv), 1);
+ foreach(object ob: inv)
+ {
+ if ( objectp(ob) && (environment(ob)==environment()) )
+ {
+ tmp=endmg[object_name(ob)];
+ total_damage+=tmp;
+ present_enemies[ob] = tmp/2;
+ m_delete(endmg,object_name(ob)); //s.u.
+ }
+ }
+ int mitglieder = sizeof(present_enemies);
+ if ( mitglieder )
+ {
+ tmp=total_damage/(2*mitglieder);
+ foreach(object ob, int punkte: &present_enemies)
+ punkte += tmp;
+ }
+ }
+ else {
+ // ohne Team wird trotzdem ein Mapping gebraucht. Da Groessenveraenderung
+ // rel. teuer sind, kann einfach mal fuer 3 Eintraege Platz reservieren.
+ present_enemies=m_allocate(3, 1);
+ }
+ // Und noch die Lebewesen im Raum ohne Team.
+ foreach(object ob: all_inventory(environment()))
+ {
+ if ( tmp=endmg[object_name(ob)] )
+ {
+ total_damage += tmp;
+ present_enemies[ob] = tmp;
+ m_delete(endmg,object_name(ob)); // Nur einmal pro Leben Punkte :)
+ }
+ }
+ if ( !total_damage )
+ {
+ enemy->AddExp(exp_to_give);
+ }
+ else
+ {
+ foreach(object ob, int damage: present_enemies)
+ {
+ if ( !objectp(ob) )
+ continue;
+ if ( query_once_interactive(ob) && ( !interactive(ob)
+ || (query_idle(ob)>600) ) )
+ continue;
+ //exp_to_give*present_enemies[i][1]/total_damage gibt bei viel Schaden
+ //einen numerical overflow. Daher muessen wir hier wohl doch
+ //zwischenzeitlich mit floats rechnen, auch wenn das 0-1 XP Verlust
+ //durch float->int-Konversion gibt. (ceil() lohnt sich IMHO nicht.)
+ ex = (int)(exp_to_give*((float)damage/(float)total_damage));
+ ob->AddExp(ex);
+ }
+ }
+}
+
+/*
+ * This function is called from other players when they want to make
+ * damage to us. But it will only be called indirectly.
+ * We return how much damage we received, which will
+ * change the attackers score. This routine is probably called from
+ * heart_beat() from another player.
+ * Compare this function to reduce_hit_points(dam).
+ */
+public int do_damage(int dam, object enemy)
+{ int hit_point,al,al2;
+
+ if ( extern_call()
+ && objectp(enemy)
+ && living(enemy)
+ && !QueryProp(P_ENABLE_IN_ATTACK_OUT))
+ {
+ al=time()-enemy->QueryProp(P_LAST_MOVE);
+ if (al<3) // Erste Kampfrunde nach Betreten des Raumes?
+ dam/=(4-al); // Gegen Rein-Feuerball-Raus-Taktik
+ }
+
+ if ( QueryProp(P_GHOST) || QueryProp(P_NO_ATTACK) || (dam<=0)
+ || ( objectp(enemy)
+ && ( enemy->QueryProp(P_GHOST)
+ || enemy->QueryProp(P_NO_ATTACK) ) ) )
+ return 0;
+
+ hit_point = QueryProp(P_HP)-dam;
+
+ if ( QueryProp(P_XP) && objectp(enemy) )
+ {
+ if ( !QueryProp(P_NO_XP) )
+ enemy->AddExp(dam*(int)QueryProp(P_TOTAL_WC)/10);
+ }
+
+ if (living(enemy)) {
+ string enname = object_name(enemy);
+ // Hmpf. Blueprints sind doof. Die Chance ist zwar gering, aber koennte
+ // sein, dass ein Unique-NPC mit zwei verschiedenen Spielern am gleichen
+ // NPC metzelt.
+ // TODO: MHmm. wie gross ist das Risiko wirklich?
+ //if (!clonep(enemy))
+ // enname = enname + "_" + to_string(object_time(enemy));
+ // nur wenn gegner NPC ist und noch nicht drinsteht: Daten aus
+ // P_HELPER_NPC auswerten
+ if (!member(enemy_damage,enemy) && !query_once_interactive(enemy)) {
+ mixed helper = enemy->QueryProp(P_HELPER_NPC);
+ if (pointerp(helper) && objectp(helper[0]))
+ enemy_damage[enname,1] = helper[0];
+ }
+ enemy_damage[enname,0]+=dam;
+ }
+
+ SetProp(P_HP, hit_point);
+
+ if ( hit_point<0 )
+ {
+ //TODO: Warum nicht das ganze Zeug ins die() verlegen?
+ if ( enemy )
+ {
+ enemy->StopHuntFor(ME,1);
+ if ( !QueryProp(P_NO_XP) )
+ DistributeExp(enemy,QueryProp(P_XP)/100);
+ if ( !query_once_interactive(ME) )
+ log_file ("NPC_XP", sprintf(
+ "[%s] %s, XP: %d, HP*WC: %d, Killer: %s\n",
+ dtime(time()), object_name(ME), (QueryProp(P_XP)/100),
+ QueryProp(P_TOTAL_WC)*QueryProp(P_MAX_HP)/10,
+ enemy->name()||"NoName" ));
+ al = QueryProp(P_ALIGN)/50 + enemy->QueryProp(P_ALIGN)/200;
+ if (al>20)
+ al=20;
+ else if(al<-20)
+ al=-20;
+ enemy->SetProp(P_ALIGN,enemy->QueryProp(P_ALIGN)-al);
+ }
+ SetProp(P_KILLER, enemy);
+
+ die();
+ }
+ return dam;
+}
+
+
+private void _transfer( object *obs, string|object dest, int flag )
+{ int i;
+
+ i = sizeof(obs);
+
+ // Eine Schleife ist zwar langsamer als filter() o.ae., aber
+ // selbst mit einer noch so schnellen Loesung kann leider nicht
+ // ausgeschlossen werden, dass irgendwo ein too-long-eval-Bug dazwischen
+ // kommt. Dazu sind die Kaempfe mit Gilden-NPCs etc. einfach zu teuer ...
+ // Pruefung auf zerstoerte Objekte, da einige sich evtl. im NotifyPlayerDeath()
+ // zerstoeren.
+ while ( i && get_eval_cost() > 300000 )
+ if ( objectp(obs[--i]) && !obs[i]->QueryProp(P_NEVERDROP) )
+ // Jetzt wird's noch etwas teurer mit catch() - aber manche Sachen
+ // duerfen einfach nicht buggen
+ catch( obs[i]->move( dest, flag );publish );
+
+ if ( i > 0 )
+ // Zuviel Rechenzeit verbraten, es muessen noch Objekte bewegt werden
+ call_out( #'_transfer, 0, obs[0..i-1], dest, flag );
+ else {
+ if ( remove_me )
+ remove();
+ }
+}
+
+
+public varargs void transfer_all_to( string|object dest, int isnpc ) {
+ int flags;
+ object *obs;
+
+ if ( !objectp(ME) )
+ return;
+
+ // Das Flag "isnpc" ist fuer NPCs gedacht. Deren Ausruestung darf nicht
+ // mit M_NOCHECK bewegt werden, da Spieler das bei Nicht-Standard-Leichen
+ // sonst u.U. ausnutzen koennten.
+ if ( isnpc )
+ flags = M_SILENT;
+ else
+ flags = M_SILENT|M_NOCHECK;
+
+ obs = all_inventory(ME) || ({});
+
+ // unnoetig, weil _transfer() auch auf P_NEVERDROP prueft. Zesstra
+ //obs -= filter_objects( obs, "QueryProp", P_NEVERDROP );
+
+ _transfer( obs, dest, flags );
+}
+
+
+protected varargs void create_kill_log_entry(string killer, object enemy) {
+ int level,lost_exp;
+
+ if ( (level=QueryProp(P_LEVEL))<20 || !IS_SEER(ME) )
+ lost_exp = QueryProp(P_XP)/3;
+ else
+ lost_exp = QueryProp(P_XP)/(level-17);
+
+ log_file("KILLS",sprintf("%s %s (%d,%d) %s\n", strftime("%e %b %H:%M"),
+ capitalize(REAL_UID(ME)), level, lost_exp/1000, killer));
+}
+
+// Liefert im Tod (nach dem toetenden do_damage()) das Spielerobjekt, was den
+// Tod wohl zu verantworten hat, falls es ermittelt werden kann. Es werden vor
+// allem registrierte Helfer-NPC und einige Sonderobjekte beruecksichtigt.
+protected object get_killing_player()
+{
+ object killer=QueryProp(P_KILLER);
+ // koennte sein, wenn ausserhalb des Todes gerufen oder eine Vergiftung uns
+ // umgebracht hat.
+ if (!objectp(killer))
+ return 0;
+
+ while (killer && !query_once_interactive(killer))
+ killer = killer->QueryUser();
+
+ return killer;
+}
+
+protected object GiveKillScore(object pl, int npcnum)
+{
+ // Stufenpunkt fuer den Kill vergeben.
+ // Falls der Killer den Punkt schon hat, wird
+ // zufaellig ein Mitglied seines Teams ausgewaehlt
+ // und diesem der Punkt gegeben.
+ object *obs,ob;
+ mixed *fr;
+ int i,j,sz;
+
+ if ( pointerp(obs=pl->TeamMembers()) && (member(obs,pl)>=0) )
+ {
+ if ( !pointerp(fr=pl->PresentTeamRows())
+ || !sizeof(fr)
+ || !pointerp(fr=fr[0])) // Erste Reihe des Teams
+ fr=({});
+ fr-=({pl,0});
+ obs-=({pl,0});
+ obs-=fr;
+ i=sz=sizeof(obs); // restliche Teammitglieder in zufaelliger Reihenfolge:
+ for ( --i ; i>=0 ; i-- )
+ {
+ j=random(sz);
+ ob=obs[j];
+ obs[j]=obs[0];
+ obs[0]=ob;
+ }
+ i=sz=sizeof(fr); // Erste Reihe in zufaelliger Reihenfolge:
+ for ( --i ; i>=0 ; i-- )
+ {
+ j=random(sz);
+ ob=fr[j];
+ fr[j]=fr[0];
+ fr[0]=ob;
+ }
+
+ obs+=fr; // Erste Reihe wird vor Rest getestet
+ obs+=({pl}); // Killer wird als erstes getestet
+ }
+ else
+ {
+ obs=({pl});
+ }
+ for ( i=sizeof(obs)-1 ; i>=0 ; i-- )
+ if ( objectp(ob=obs[i] )
+ && interactive(ob) // Nur netztot dabei stehen gilt nicht :)
+ && query_idle(ob)<600 // gegen Leute die sich nur mitschleppen lassen
+ && environment(ob)==environment(pl) // Nur anwesende Teammitglieder
+ && !IS_LEARNER(ob)
+// && !ob->QueryProp(P_TESTPLAYER)
+ && !(SCOREMASTER->HasKill(ob,ME)) )
+ return SCOREMASTER->GiveKill(ob,npcnum),ob;
+
+ return SCOREMASTER->GiveKill(pl,npcnum),pl;
+}
+
+// zum ueberschreiben in Spielern
+public int death_suffering() {
+ return 0; // NPC haben keine Todesfolgen
+}
+
+// kein 2. Leben fuer Nicht-Spieler. ;-)
+varargs protected int second_life( object corpse ) {
+ return 0;
+}
+
+public varargs void die( int poisondeath, int extern )
+{ object corpse;
+ string die_msg, tmp;
+ mixed res;
+ mixed hookData;
+ mixed hookRes;
+
+ if ( !objectp(this_object()) || QueryProp(P_GHOST) )
+ return; // Ghosts dont die ...
+
+ // direkt von extern aufgerufen und nicht ueber heart_beat() oder
+ // do_damage() hierher gelangt?
+ if (extern_call() && previous_object() != this_object()) {
+ extern=1;
+ SetProp(P_KILLER, previous_object());
+ }
+
+ if ( res = QueryProp(P_TMP_DIE_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], poisondeath ) ) {
+ SetProp(P_KILLER,0);
+ return;
+ }
+ }
+ else
+ SetProp(P_TMP_DIE_HOOK,0);
+ }
+
+ // trigger die hook
+ hookData=poisondeath;
+ hookRes=HookFlow(H_HOOK_DIE,hookData);
+ if (pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
+ if(hookRes[H_RETCODE]==H_CANCELLED) {
+ SetProp(P_KILLER,0);
+ return;
+ }
+ else if (hookRes[H_RETCODE]==H_ALTERED)
+ poisondeath = hookRes[H_RETDATA];
+ }
+
+ if ( IS_LEARNING(ME) && query_once_interactive(ME) ){
+ tell_object( ME, "Sei froh dass Du unsterblich bist, sonst waere es "
+ "eben Dein Ende gewesen.\n");
+ SetProp(P_KILLER,0);
+ return;
+ }
+
+ // Gegner befrieden.
+ map_objects( QueryEnemies()[0], "StopHuntFor", ME, 1 );
+ StopHuntingMode(1);
+
+ // Falls die() direkt aufgerufen wurde und dies ein Spieler ist, muss das
+ // die() noch Eintraege in /log/KILLS via create_kill_log_entry bzw. in
+ // /log/KILLER erstellen.
+ if ( query_once_interactive(ME) && extern )
+ {
+ object killer = QueryProp(P_KILLER)
+ || previous_object() || this_interactive() || this_player();
+ if ( killer && !query_once_interactive(killer) )
+ {
+ tmp = explode( object_name(killer), "#")[0] + " (direkt !)";
+
+ create_kill_log_entry( tmp + " (" + REAL_UID(killer) + ")", killer );
+ }
+ else if ( killer && !QueryProp(P_TESTPLAYER) && !IS_LEARNER(ME) )
+ {
+ log_file( "KILLER", sprintf( "%s %s (%d/%d) toetete %s (%d/%d)\n",
+ ctime(time()),
+ capitalize(getuid(killer)),
+ query_wiz_level(killer),
+ killer->QueryProp(P_LEVEL),
+ capitalize(getuid(ME)),
+ query_wiz_level(ME),
+ QueryProp(P_LEVEL) ) );
+
+ killer->SetProp( P_KILLS, -1 );
+ }
+ }
+
+ // Bei NPC EKs vergeben und ggf. in der Gilde des Killers und im Raum
+ // NPC_Killed_By() rufen.
+ if ( !query_once_interactive(ME) )
+ {
+ object killer = ((object) QueryProp(P_KILLER)) || previous_object() ||
+ this_interactive() || this_player();
+
+ if ( killer && query_once_interactive(killer) )
+ {
+ if (stringp(res=killer->QueryProp(P_GUILD))
+ && objectp(res=find_object("/gilden/"+res)))
+ res->NPC_Killed_By(killer);
+
+ if (environment())
+ environment()->NPC_Killed_By(killer);
+
+ res = QueryProp(P_XP);
+ res = (res < SCORE_LOW_MARK) ? 0 : ((res > SCORE_HIGH_MARK) ? 2 : 1);
+ if ( !QueryProp(P_NO_SCORE) && !IS_LEARNER(killer) &&
+ // !killer->QueryProp(P_TESTPLAYER) &&
+ pointerp( res = SCOREMASTER->QueryNPC(res)) )
+ GiveKillScore( killer, res[0] );
+ }
+ }
+
+ if( !(die_msg = QueryProp(P_DIE_MSG)) )
+ if (QueryProp(P_PLURAL))
+ die_msg = " fallen tot zu Boden.\n";
+ else
+ die_msg = " faellt tot zu Boden.\n";
+
+ if ( poisondeath )
+ {
+ Set( P_LAST_DAMTYPES, ({ DT_POISON }) );
+ Set( P_LAST_DAMTIME, time() );
+ Set( P_LAST_DAMAGE, 1 );
+ die_msg = " wird von Gift hinweggerafft und kippt um.\n";
+ }
+
+ say( capitalize(name(WER,1)) + die_msg );
+
+ // Wenn keine Leiche, dann Kram ins Env legen.
+ if ( QueryProp(P_NOCORPSE) || !(tmp = QueryProp(P_CORPSE))
+ || catch(corpse = clone_object(tmp);publish)
+ || !objectp(corpse) )
+ {
+ // Magier oder Testspieler behalten ihre Ausruestung.
+ // Sonst kaemen u.U. Spieler an Magiertools etc. heran
+ if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
+ (!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
+ transfer_all_to( environment(), 0 );
+ else
+ // Aber sie ziehen sich aus.
+ filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
+ }
+ else
+ // sonst in die Leiche legen.
+ {
+ corpse->Identify(ME);
+ corpse->move( environment(), M_NOCHECK|M_SILENT );
+ // Magier oder Testspieler behalten ihre Ausruestung.
+ // Sonst kaemen u.U. Spieler an Magiertools etc. heran
+ if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
+ (!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
+ transfer_all_to( corpse, !query_once_interactive(ME) );
+ else
+ // Aber sie ziehen sich aus.
+ filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
+ }
+
+ if ( query_once_interactive(ME) ) {
+ Set( P_DEADS, Query(P_DEADS) + 1 );
+ // Spieler-Tod-event ausloesen
+ EVENTD->TriggerEvent(EVT_LIB_PLAYER_DEATH, ([
+ E_OBJECT: ME, E_PLNAME: getuid(ME),
+ E_ENVIRONMENT: environment(), E_TIME: time(),
+ P_KILLER: QueryProp(P_KILLER),
+ P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
+ P_LAST_DAMTYPES: copy(QueryProp(P_LAST_DAMTYPES)),
+ E_EXTERNAL_DEATH: extern,
+ E_POISON_DEATH: poisondeath,
+ E_CORPSE: (objectp(corpse)?corpse:0) ]) );
+ }
+ else {
+ // NPC-Todes-Event ausloesen. Div. Mappings/Arrays werden nicht kopiert,
+ // weil der NPC ja jetzt eh zerstoert wird.
+ mapping data = ([
+ E_OBNAME: object_name(ME),
+ E_ENVIRONMENT: environment(), E_TIME: time(),
+ P_NAME: QueryProp(P_NAME),
+ P_KILLER: QueryProp(P_KILLER),
+ P_ENEMY_DAMAGE: QueryProp(P_ENEMY_DAMAGE),
+ P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
+ P_LAST_DAMTYPES: QueryProp(P_LAST_DAMTYPES),
+ E_EXTERNAL_DEATH: extern,
+ E_POISON_DEATH: poisondeath,
+ E_CORPSE: (objectp(corpse)?corpse:0),
+ P_XP: QueryProp(P_XP),
+ P_ATTRIBUTES: QueryProp(P_ATTRIBUTES),
+ P_MAX_HP: QueryProp(P_MAX_HP),
+ P_HANDS: QueryProp(P_HANDS),
+ P_ALIGN: QueryProp(P_ALIGN),
+ P_RACE: QueryProp(P_RACE),
+ P_CLASS: QueryProp(P_CLASS),
+ ]);
+ EVENTD->TriggerEvent(EVT_LIB_NPC_DEATH(""), data);
+ EVENTD->TriggerEvent(
+ EVT_LIB_NPC_DEATH(load_name(ME)), data);
+ }
+
+ // transfer_all_to() ist evtl. (wenn zuviele Objekte bewegt werden mussten)
+ // noch nicht ganz fertig und wird per call_out() den Rest erledigen.
+ // Sollte die Leiche dann nicht mehr existieren, verbleiben die restlichen
+ // Objekte im Spieler.
+ // Es bleiben aber auf jeden Fall noch rund 300k Eval-Ticks ueber, damit
+ // kein Spieler dank "evalcost too high" ungeschoren davon kommt.
+ if ( !(second_life(corpse)) )
+ {
+ Set( P_GHOST, 1 ); // Fuer korrekte Ausgabe auf Teamkanal.
+
+ if ( find_call_out(#'_transfer) == -1 )
+ // Falls kein call_out() mehr laeuft, sofort destructen ...
+ remove();
+ else
+ // ... ansonsten vormerken
+ remove_me = 1;
+ }
+}
+
+public void heal_self(int h)
+{
+ if ( h<=0 )
+ return;
+ SetProp(P_HP, QueryProp(P_HP)+h);
+ SetProp(P_SP, QueryProp(P_SP)+h);
+}
+
+
+//--------------------------------------------------------------------------
+//
+// int defuel_food( /* void */ )
+//
+// Enttankt den Spieler um einen gewissen Essens-Wert.
+// Sollte nur von Toiletten aufgerufen werden.
+//
+//--------------------------------------------------------------------------
+public int defuel_food()
+{
+ int food;
+
+ food=QueryProp(P_FOOD);
+
+// wenn spieler kein food hat: return 0
+ if ( !food )
+ return NO_DEFUEL;
+
+// wenn spieler unter enttank-grenze: return -1
+ if ( food < QueryProp(P_DEFUEL_LIMIT_FOOD) )
+ return DEFUEL_TOO_LOW;
+
+// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
+ if ( time() < nextdefueltimefood )
+ return DEFUEL_TOO_SOON;
+
+ food=to_int(((food*QueryProp(P_DEFUEL_AMOUNT_FOOD))/2));
+ food+=random(food);
+
+// sicherheitshalber
+ if ( food > QueryProp(P_FOOD) )
+ food=QueryProp(P_FOOD);
+
+ SetProp(P_FOOD,(QueryProp(P_FOOD)-food));
+
+ nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
+
+ return food;
+}
+
+
+//--------------------------------------------------------------------------
+//
+// int defuel_drink( /* void */ )
+//
+// Enttankt den Spieler um einen gewissen Fluessigkeits-Wert.
+// Gleichzeitig wird eine gewisse Menge Alkohol reduziert.
+// Sollte nur von Toiletten aufgerufen werden.
+//
+//--------------------------------------------------------------------------
+public int defuel_drink()
+{
+ int alc, drink;
+
+ drink=QueryProp(P_DRINK);
+
+// wenn spieler kein drink hat: return 0
+ if ( !drink )
+ return NO_DEFUEL;
+
+// wenn spieler unter enttank-grenze: return -1
+ if ( drink < QueryProp(P_DEFUEL_LIMIT_DRINK) )
+ return DEFUEL_TOO_LOW;
+
+// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
+ if ( time() < nextdefueltimedrink )
+ return DEFUEL_TOO_SOON;
+
+ drink=to_int(((drink*QueryProp(P_DEFUEL_AMOUNT_DRINK))/2));
+ drink+=random(drink);
+
+// sicherheitshalber
+ if ( drink > QueryProp(P_DRINK) )
+ drink=QueryProp(P_DRINK);
+
+ SetProp(P_DRINK,(QueryProp(P_DRINK)-drink));
+
+// jedes fluessige Enttanken macht auch etwas nuechterner :^)
+// bei sehr kleinen Mengen enttankt man keinen Alkohol
+// ansonsten in Abhaengigkeit von enttankter Menge, P_ALCOHOL und P_WEIGHT
+
+ if ( drink > 9 && QueryProp(P_ALCOHOL) > 0 )
+ {
+ alc=(to_int(exp(log(1.1)*(drink)))*
+ to_int(exp(log(0.67)*(QueryProp(P_ALCOHOL)))))/
+ (QueryProp(P_MAX_DRINK)*QueryProp(P_MAX_ALCOHOL))*
+ (to_int(QueryProp(P_WEIGHT)/1000));
+
+ SetProp(P_ALCOHOL,QueryProp(P_ALCOHOL)-(alc+random(alc)));
+ }
+
+ nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
+
+ return drink;
+}
+
+
+public void reduce_spell_points(int h)
+{
+ SetProp(P_SP, QueryProp(P_SP)-h);
+}
+
+public void restore_spell_points(int h)
+{
+ SetProp(P_SP, QueryProp(P_SP)+h);
+}
+
+/* Reduce hitpoints. Log who is doing it. */
+public int reduce_hit_points(int dam)
+{ object o;
+ int i;
+
+#ifdef LOG_REDUCE_HP
+ if (this_player()!=ME)
+ {
+ log_file("REDUCE_HP", name()+" by ");
+ if(!this_player()) log_file("REDUCE_HP","?\n");
+ else {
+ log_file("REDUCE_HP",this_player()->name());
+ o=previous_object();
+ if (o)
+ log_file("REDUCE_HP", " " + object_name(o) + ", " +
+ o->name(WER,0) + " (" + creator(o) + ")\n");
+ else
+ log_file("REDUCE_HP", " ??\n");
+ }
+ }
+#endif
+ if ((i=QueryProp(P_HP)) <= dam)
+ return SetProp(P_HP,1);
+ return SetProp(P_HP, i - dam);
+}
+
+public int restore_hit_points(int heal)
+{
+ return reduce_hit_points(-heal);
+}
+
+public varargs int drink_alcohol(int strength,int testonly, string mytext)
+{ int alc,add,res;
+
+ add=ALCOHOL_VALUE(strength);
+ res=UseSkill(SK_BOOZE,([
+ SI_SKILLARG : add,
+ SI_TESTFLAG : 1])); // Kann der Spieler gut saufen?
+ if (intp(res) && res>0) add=res;
+ alc=QueryProp(P_ALCOHOL)+add;
+ if ((alc >= QueryProp(P_MAX_ALCOHOL)) && !IS_LEARNING(this_object())){
+ if(!testonly)
+ tell_object(ME,mytext||"So ein Pech, Du hast alles verschuettet.\n");
+ return 0;
+ }
+ if(testonly)return 1;
+ UseSkill(SK_BOOZE,([ SI_SKILLARG : ALCOHOL_VALUE(strength) ]));
+ if(alc < 0) alc = 0;
+ if(!alc) tell_object(ME, "Du bist stocknuechtern.\n");
+ SetProp(P_ALCOHOL, alc);
+ return 1;
+}
+
+public varargs int drink_soft(int strength, int testonly, string mytext)
+{ int soaked;
+
+ soaked = QueryProp(P_DRINK);
+ if((soaked + strength > QueryProp(P_MAX_DRINK)) &&
+ !IS_LEARNING(this_object())){
+ if(!testonly)
+ tell_object(ME, mytext||
+ "Nee, so viel kannst Du momentan echt nicht trinken.\n" );
+ return 0;
+ }
+ if(testonly)return 1;
+ if((soaked += DRINK_VALUE(strength)) < 0) soaked = 0;
+ if(!soaked) tell_object(ME, "Dir klebt die Zunge am Gaumen.\n");
+ SetProp(P_DRINK, soaked);
+ return 1;
+}
+
+public varargs int eat_food(int strength, int testonly, string mytext)
+{ int stuffed;
+
+ stuffed = QueryProp(P_FOOD);
+ if ((stuffed + strength > QueryProp(P_MAX_FOOD)) &&
+ !IS_LEARNING(this_object()))
+ {
+ if(!testonly)
+ tell_object(ME,
+ mytext || "Das ist viel zu viel fuer Dich! Wie waers mit etwas "
+ "leichterem?\n");
+ return 0;
+ }
+ if(testonly)return 1;
+ stuffed += FOOD_VALUE(strength);
+ if(stuffed < 0) stuffed = 0;
+ if(!stuffed) tell_object(ME, "Was rumpelt denn da in Deinem Bauch?\n");
+ SetProp(P_FOOD, stuffed);
+ return 1;
+}
+
+public int buffer_hp(int val,int rate)
+{
+ int dif;
+
+ if(val<=0 || rate<=0)return 0;
+ if(val < rate)rate = val;
+ if(rate>20) rate=20;
+
+ /* Check for BufferOverflow */
+ if((dif=(hp_buffer[0]+val)-QueryProp(P_MAX_HP)) > 0)val-=dif;
+ if(val<=0)return 0;
+
+ hp_buffer[0] += val;
+ hp_buffer[1+rate] += val;
+ if(rate > hp_buffer[1])hp_buffer[1] = rate;
+
+ return hp_buffer[0];
+}
+
+public int buffer_sp(int val,int rate)
+{
+ int dif;
+
+ if(val<=0 || rate<=0)return 0;
+ if(val < rate)rate = val;
+ if(rate>20) rate=20;
+
+ /* Check for BufferOverflow */
+ if((dif=(sp_buffer[0]+val)-QueryProp(P_MAX_SP)) > 0)val-=dif;
+ if(val<=0)return 0;
+
+ sp_buffer[0] += val;
+ sp_buffer[1+rate] += val;
+ if(rate > sp_buffer[1])sp_buffer[1] = rate;
+
+ return sp_buffer[0];
+}
+
+protected void update_buffers()
+{ int i, rate, max;
+
+ rate=0;
+ max=0;
+ for(i=1;i<=20;i++){
+ if(member(hp_buffer, i+1))
+ if(hp_buffer[i+1]<=0)
+ hp_buffer = m_delete(hp_buffer,i+1);
+ else{
+ max+=hp_buffer[i+1];
+ rate=i;
+ }
+ }
+
+ hp_buffer[0]=max;
+ hp_buffer[1]=rate;
+ rate=0;
+ max=0;
+ for(i=1;i<=20;i++){
+ if(member(sp_buffer, i+1))
+ if(sp_buffer[i+1]<=0)
+ sp_buffer = m_delete(sp_buffer,i+1);
+ else{
+ max+=sp_buffer[i+1];
+ rate=i;
+ }
+ }
+ sp_buffer[0]=max;
+ sp_buffer[1]=rate;
+}
+
+public int check_timed_key(string key) {
+
+ // keine 0 als key (Typ wird per RTTC geprueft)
+ if (!key)
+ return 0;
+ mapping tmap=Query(P_TIMING_MAP, F_VALUE);
+ if (!mappingp(tmap)) {
+ tmap=([]);
+ Set(P_TIMING_MAP, tmap, F_VALUE);
+ }
+ // Wenn key noch nicht abgelaufen, Ablaufzeitpunkt zurueckgeben.
+ // Sonst 0 (key frei)
+ return (time() < tmap[key]) && tmap[key];
+}
+
+public int check_and_update_timed_key(int duration,string key) {
+
+ // wenn key noch gesperrt, die zeit der naechsten Verfuegbarkeit
+ // zurueckgeben.
+ int res = check_timed_key(key);
+ if (res) {
+ return res;
+ }
+
+ // duration <= 0 ist unsinnig. Aber key ist nicht mehr gesperrt, d.h. time()
+ // ist ein sinnvoller Rueckgabewert.
+ if (duration <= 0)
+ return time();
+
+ mapping tmap = Query(P_TIMING_MAP,F_VALUE);
+ tmap[key]=time()+duration;
+
+ // speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
+ // keine Kopie.
+ //SetProp(P_TIMING_MAP, tmap);
+
+ return -1; // Erfolg.
+}
+
+protected void expire_timing_map() {
+
+ mapping tmap = Query(P_TIMING_MAP, F_VALUE);
+ if (!mappingp(tmap) || !sizeof(tmap))
+ return;
+ foreach(string key, int endtime: tmap) {
+ if (endtime < time())
+ m_delete(tmap, key);
+ }
+ // speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
+ // keine Kopie.
+}
+
+protected void heart_beat()
+{
+ if ( !this_object() )
+ return;
+
+ attribute_hb();
+
+ // Als Geist leidet man nicht unter so weltlichen Dingen wie
+ // Alkohol, Gift&Co ...
+ if ( QueryProp(P_GHOST) )
+ return;
+
+ int hpoison = QueryProp(P_POISON);
+ int rlock = QueryProp(P_NO_REGENERATION);
+ int hp = QueryProp(P_HP);
+ int sp = QueryProp(P_SP);
+ int alc;
+
+ // Wenn Alkohol getrunken: Alkoholauswirkungen?
+ if ( (alc = QueryProp(P_ALCOHOL)) && !random(40) ){
+ int n;
+ string gilde;
+ object ob;
+
+ n = random( 5 * (alc - 1)/QueryProp(P_MAX_ALCOHOL) );
+
+ switch (n){
+ case ALC_EFFECT_HICK:
+ say( capitalize(name( WER, 1 )) + " sagt: <Hick>!\n" );
+ write( "<Hick>! Oh, Tschuldigung.\n" );
+ break;
+
+ case ALC_EFFECT_STUMBLE:
+ say( capitalize(name( WER, 1 )) + " stolpert ueber " +
+ QueryPossPronoun( FEMALE, WEN ) + " Fuesse.\n" );
+ write( "Du stolperst.\n" );
+ break;
+
+ case ALC_EFFECT_LOOKDRUNK:
+ say( capitalize(name( WER, 1 )) + " sieht betrunken aus.\n" );
+ write( "Du fuehlst Dich benommen.\n" );
+ break;
+
+ case ALC_EFFECT_RUELPS:
+ say( capitalize(name( WER, 1 )) + " ruelpst.\n" );
+ write( "Du ruelpst.\n" );
+ break;
+ }
+
+ // Gilde und Environment informieren ueber Alkoholauswirkung.
+ if ( stringp(gilde = QueryProp(P_GUILD))
+ && objectp(ob = find_object( "/gilden/" + gilde )) )
+ ob->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_GUILD );
+
+ if ( environment() )
+ environment()->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_ENV );
+ }
+
+ // Alkohol abbauen und etwas extra heilen, falls erlaubt.
+ if ( alc && (--delay_alcohol < 0)
+ && !(rlock & NO_REG_ALCOHOL) ){
+
+ SetProp( P_ALCOHOL, alc - 1 );
+
+ if ( !hpoison ){
+ hp++;
+ sp++;
+ }
+
+ delay_alcohol = QueryProp(P_ALCOHOL_DELAY);
+ }
+
+ // P_DRINK reduzieren, falls erlaubt.
+ if ( (--delay_drink < 0) && !(rlock & NO_REG_DRINK) ){
+ delay_drink = QueryProp(P_DRINK_DELAY);
+ SetProp( P_DRINK, QueryProp(P_DRINK) - 1 );
+ }
+
+ // P_FOOD reduzieren, falls erlaubt.
+ if ( (--delay_food < 0) && !(rlock & NO_REG_FOOD) ){
+ delay_food = QueryProp(P_FOOD_DELAY);
+ SetProp( P_FOOD, QueryProp(P_FOOD) - 1 );
+ }
+
+ // Regeneration aus dem HP-Puffer
+ // Hierbei wird zwar nur geheilt, wenn das erlaubt ist, aber der Puffer
+ // muss trotzdem abgearbeitet/reduziert werden, da Delfen sonst eine
+ // mobile Tanke kriegen. (Keine Heilung im Hellen, Puffer bleibt sonst
+ // konstant und kann bei Bedarf ueber dunkelmachende Items abgerufen
+ // werden.)
+ int val;
+ if (hp_buffer[0]) {
+ int rate = hp_buffer[1];
+ val = hp_buffer[rate + 1];
+
+ if ( val > rate )
+ val = rate;
+ hp_buffer[0] -= val;
+ hp_buffer[rate + 1] -= val;
+ if ( hp_buffer[rate + 1] <= 0 )
+ update_buffers();
+ }
+ // Jetzt Regeneration aus dem Puffer durchfuehren, aber nur wenn erlaubt.
+ if ( val && !(rlock & NO_REG_BUFFER_HP) )
+ hp += val;
+ // normales Heilen, falls keine Regeneration aus dem Puffer erfolgte und
+ // es erlaubt ist.
+ else if ( (--delay_heal < 0) && !(rlock & NO_REG_HP) ){
+ delay_heal = QueryProp(P_HP_DELAY);
+ if ( !hpoison )
+ hp++;
+ }
+
+ // Gleiches Spiel jetzt fuer den SP-Puffer (s.o.)
+ val=0;
+ if ( sp_buffer[0] ) {
+ int rate = sp_buffer[1];
+ val = sp_buffer[rate + 1];
+
+ if ( val > rate )
+ val = rate;
+
+ sp_buffer[0] -= val;
+ sp_buffer[rate + 1] -= val;
+
+ if ( sp_buffer[rate + 1] <= 0 )
+ update_buffers();
+ }
+ // Regeneration erlaubt?
+ if ( val && !(rlock & NO_REG_BUFFER_SP) )
+ sp += val;
+ // Wenn nicht, normales Hochideln versuchen.
+ else if ( (--delay_sp < 0) && !(rlock & NO_REG_SP) ){
+ delay_sp = QueryProp(P_SP_DELAY);
+ if ( !hpoison )
+ sp++;
+ }
+
+ if ( hpoison && (interactive(ME) || !query_once_interactive(ME)) ){
+ // Vanion, 26.10.03
+ // Wenn _set_poison() per SET_METHOD ueberschrieben wird, kann
+ // nicht sichergestellt werden, dass poison immer groesser 0 ist
+ // Daher muss hier ein Test rein, so teuer das auch ist :(
+ if (--hpoison < 0)
+ hpoison=0;
+
+ if ( --delay_poison < 0 ){
+ delay_poison = QueryProp(P_POISON_DELAY)
+ + random(POISON_MERCY_DELAY);
+ hp -= hpoison;
+
+ if ( hp < 0 ){
+ tell_object( ME, "Oh weh - das Gift war zuviel fuer Dich!\n"
+ + "Du stirbst.\n" );
+
+ if ( query_once_interactive(ME) ){
+ create_kill_log_entry( "Vergiftung", 0 );
+
+ // Beim Gifttod gibt es keinen Killer. Aber auf diese Art
+ // erkennt der Todesraum die Ursache korrekt und gibt die
+ // richtige Meldung aus.
+ SetProp( P_KILLER, "gift" );
+ }
+
+ die(1);
+ return;
+ }
+
+ if ( (hpoison < 3 || !query_once_interactive(ME) )
+ && --drop_poison < 0)
+ {
+ // Giftlevel eins reduzieren. hpoison wurde oben schon
+ // reduziert, d.h. einfach hpoison in P_POISON schreiben.
+ // dabei wird dann auch ggf. drop_poison richtig gesetzt.
+ SetProp( P_POISON, hpoison );
+ if ( !hpoison )
+ tell_object( ME, "Du scheinst die Vergiftung "
+ "ueberwunden zu haben.\n" );
+ }
+ }
+
+ if ( hpoison && !random(15) )
+ switch ( hp*100/QueryProp(P_MAX_HP) ){
+ case 71..100 :
+ write( "Du fuehlst Dich nicht gut.\n" );
+ say( capitalize(name(WER)) +
+ " sieht etwas benommen aus.\n" );
+ break;
+
+ case 46..70 :
+ write( "Dir ist schwindlig und Dein Magen revoltiert.\n" );
+ say( capitalize(name(WER)) + " taumelt ein wenig.\n" );
+ break;
+
+ case 26..45 :
+ write( "Dir ist heiss. Du fuehlst Dich schwach. Kopfweh "
+ "hast Du auch.\n" );
+ say( capitalize(name(WER)) + " glueht direkt und scheint "
+ "grosse Schwierigkeiten zu haben.\n" );
+ break;
+
+ case 11..25 :
+ write( "Du fuehlst Dich beschissen. Alles tut weh, und Du "
+ "siehst nur noch unscharf.\n" );
+ say( capitalize(name(WER)) + " taumelt und stoehnt und "
+ "kann gerade noch vermeiden, hinzufallen.\n" );
+ break;
+
+ case 0..10 :
+ write( break_string( "Du siehst fast nichts mehr und kannst "
+ "Dich nur noch unter groessten Schmerzen "
+ "bewegen. Aber bald tut nichts mehr weh"
+ "...", 78 ) );
+ say( break_string( capitalize(name(WER)) + " glueht wie "
+ "im Fieber, kann sich kaum noch ruehren "
+ "und hat ein schmerzverzerrtes Gesicht.\n",
+ 78 ) );
+ break;
+ }
+ }
+
+ SetProp( P_HP, hp );
+ SetProp( P_SP, sp );
+}
+
+public int AddExp( int e )
+{
+ int experience;
+ string fn;
+ mixed last;
+
+ experience = QueryProp(P_XP);
+
+ if ( QueryProp(P_KILLS) > 1 && e > 0 )
+ return experience;
+
+ fn = implode( explode( object_name( environment() || this_object() ),
+ "/" )[0..<2], "/" );
+
+ if ( pointerp(last = Query(P_LAST_XP)) && sizeof(last) == 2 && last[0] == fn )
+ Set( P_LAST_XP, ({ fn, last[1]+e }) );
+ else
+ Set( P_LAST_XP, ({ fn, e }) );
+
+ if ( (experience += e) < 0 )
+ experience = 0;
+
+ return SetProp( P_XP, experience );
+}
+
+static <string|int>* _set_last_xp( <int|string>* last )
+{
+ if ( !pointerp(last) || sizeof(last) != 2 || !stringp(last[0]) ||
+ !intp(last[1]) )
+ return Query(P_LAST_XP);
+ else
+ return Set( P_LAST_XP, last );
+}
+
+
+static int _set_align(int a)
+{
+ if (a<-1000) a = -1000;
+ if (a>1000) a = 1000;
+ return Set(P_ALIGN, a);
+}
+
+
+static int _set_hp( int hp )
+{
+ if ( QueryProp(P_GHOST) )
+ return QueryProp(P_HP);
+
+ if ( hp < 0 )
+ return Set( P_HP, 0 );
+
+ if ( hp > QueryProp(P_MAX_HP) )
+ return Set( P_HP, QueryProp(P_MAX_HP), F_VALUE );
+
+ return Set( P_HP, hp, F_VALUE );
+}
+
+static int _set_sp( int sp )
+{
+ //einige Leute schreiben floats in die P_HP. :-(
+ if (!intp(sp)) {
+ sp=to_int(sp);
+ //ja, es ist teuer. Aber ich will wissen, wers ist. Kann vor
+ //naechstem Reboot wieder raus.
+ log_file("ILLEGAL_TYPE.log",sprintf(
+ "Versuch, einen nicht-int in P_SP in %O zu schreiben: \n%O\n",
+ this_object(),
+ debug_info(DINFO_TRACE,DIT_STR_CURRENT)));
+ }
+
+ if ( QueryProp(P_GHOST) )
+ QueryProp(P_SP);
+
+ if ( sp < 0 )
+ return Set( P_SP, 0 );
+
+ if ( sp > QueryProp(P_MAX_SP) )
+ return Set( P_SP, QueryProp(P_MAX_SP), F_VALUE );
+
+ return Set( P_SP, sp, F_VALUE );
+}
+
+static int _set_alcohol(int n)
+{
+ if(!intp(n))
+ raise_error(sprintf(
+ "_set_alcohol(): expected <int>, got %.50O\n", n));
+
+ if (QueryProp(P_GHOST))
+ return Query(P_ALCOHOL, F_VALUE);
+
+ // nur Änderungen und Werte >=0 werden gesetzt...
+ n = n < 0 ? 0 : n;
+ int old = Query(P_ALCOHOL, F_VALUE);
+ if ( old == n)
+ return old;
+
+ // Hooks aufrufen
+ int *ret = HookFlow(H_HOOK_ALCOHOL, n);
+ // Bei Abbruch alten Wert zurueckgeben
+ switch (ret[H_RETCODE]) {
+ case H_CANCELLED:
+ return old;
+ case H_ALTERED:
+ // sonst neuen Wert setzen
+ if(!intp(ret[H_RETDATA]))
+ raise_error(sprintf(
+ "_set_alcohol(): data from HookFlow() != <int>: %.50O\n",
+ ret[H_RETDATA]));
+ n = ret[H_RETDATA];
+ n = n < 0 ? 0 : n;
+
+ // H_NO_MOD is fallthrough
+ }
+
+ return Set(P_ALCOHOL, n, F_VALUE);
+}
+
+static int _set_drink(int n)
+{
+ if(!intp(n))
+ raise_error(sprintf(
+ "_set_drink(): expected <int>, got %.50O\n", n));
+
+ if (QueryProp(P_GHOST))
+ return Query(P_DRINK, F_VALUE);
+
+ // nur Änderungen und Werte >=0 werden gesetzt...
+ n = n < 0 ? 0 : n;
+ int old = Query(P_DRINK, F_VALUE);
+ if ( old == n)
+ return old;
+
+ // Hooks aufrufen
+ int *ret = HookFlow(H_HOOK_DRINK, n);
+ // Bei Abbruch alten Wert zurueckgeben
+ switch (ret[H_RETCODE]) {
+ case H_CANCELLED:
+ return old;
+ case H_ALTERED:
+ // sonst neuen Wert setzen
+ if(!intp(ret[H_RETDATA]))
+ raise_error(sprintf(
+ "_set_drink(): data from HookFlow() != <int>: %.50O\n",
+ ret[H_RETDATA]));
+ n = ret[H_RETDATA];
+ n = n < 0 ? 0 : n;
+
+ // H_NO_MOD is fallthrough
+ }
+
+ return Set(P_DRINK, n, F_VALUE);
+}
+
+static int _set_food(int n)
+{
+ if(!intp(n))
+ raise_error(sprintf(
+ "_set_food(): expected <int>, got %.50O\n", n));
+
+ if (QueryProp(P_GHOST))
+ return Query(P_FOOD, F_VALUE);
+
+ // nur Änderungen und Werte >=0 werden gesetzt...
+ n = n < 0 ? 0 : n;
+ int old = Query(P_FOOD, F_VALUE);
+ if ( old == n)
+ return old;
+
+ // Hooks aufrufen
+ int *ret = HookFlow(H_HOOK_FOOD, n);
+ // Bei Abbruch alten Wert zurueckgeben
+ switch (ret[H_RETCODE]) {
+ case H_CANCELLED:
+ return old;
+ case H_ALTERED:
+ // sonst neuen Wert setzen
+ if(!intp(ret[H_RETDATA]))
+ raise_error(sprintf(
+ "_set_food(): data from HookFlow() != <int>: %.50O\n",
+ ret[H_RETDATA]));
+ n = ret[H_RETDATA];
+ n = n < 0 ? 0 : n;
+
+ // H_NO_MOD is fallthrough
+ }
+
+ return Set(P_FOOD, n, F_VALUE);
+}
+
+static int _set_poison(int n)
+{
+ if(!intp(n))
+ raise_error(sprintf(
+ "_set_poison(): expected <int>, got %.50O\n", n));
+
+ if (QueryProp(P_GHOST))
+ return Query(P_POISON, F_VALUE);
+
+ int mp = QueryProp(P_MAX_POISON);
+ n = (n<0 ? 0 : (n>mp ? mp : n));
+
+ // nur >=0 zulassen.
+ n = n < 0 ? 0 : n;
+
+ int old = Query(P_POISON, F_VALUE);
+ if ( old == 0 && n == 0)
+ return old;
+
+ // Hooks aufrufen
+ int *ret = HookFlow(H_HOOK_POISON, n);
+ // Bei Abbruch alten Wert zurueckgeben
+ switch (ret[H_RETCODE]) {
+ case H_CANCELLED:
+ return old;
+ case H_ALTERED:
+ // sonst neuen Wert setzen
+ if(!intp(ret[H_RETDATA]))
+ raise_error(sprintf(
+ "_set_poison(): data from HookFlow() != <int>: %.50O\n",
+ ret[H_RETDATA]));
+ n = ret[H_RETDATA];
+ n = n < 0 ? 0 : n;
+
+ // H_NO_MOD is fallthrough
+ }
+
+ // Fuer die Selbstheilung.
+ switch(n) {
+ case 1:
+ drop_poison = 40+random(16);
+ break;
+ case 2:
+ drop_poison = 25+random(8);
+ break;
+ case 3:
+ drop_poison = 18+random(4);
+ break;
+ default:
+ // nur relevant fuer NPC, da Spieler bei >3 Gift nicht mehr regegenieren.
+ drop_poison = 22 - 2*n + random(43 - 3*n);
+ break;
+ }
+
+ // fuer Setzen der Prop von aussen ein Log schreiben
+ if (previous_object(1) != ME)
+ log_file("POISON", sprintf("%s - %s: %d von %O (%s)\n",
+ dtime(time())[5..],
+ (query_once_interactive(this_object()) ?
+ capitalize(geteuid(this_object())) :
+ capitalize(name(WER))),
+ n,
+ (previous_object(2) ? previous_object(2) : previous_object(1)),
+ (this_player() ? capitalize(geteuid(this_player())) : "???")));
+
+ return Set(P_POISON, n, F_VALUE);
+}
+
+static int _set_xp(int xp) { return Set(P_XP, xp < 0 ? 0 : xp, F_VALUE); }
+
+static mixed _set_die_hook(mixed hook)
+{
+ if(hook && query_once_interactive(this_object()))
+ log_file("DIE_HOOK",
+ sprintf("%s : DIE_HOOK gesetzt von %O in %O (%s)\n",
+ dtime(time())[5..],
+ (previous_object(2) ? previous_object(2):previous_object(1)),
+ this_object(),getuid(this_object())));
+ return Set(P_TMP_DIE_HOOK,hook, F_VALUE);
+}
+
+static mapping _query_enemy_damage()
+{
+ return copy(enemy_damage);
+}
+
+// nur ne Kopie liefern, sonst kann das jeder von aussen aendern.
+static mapping _query_timing_map() {
+ return copy(Query(P_TIMING_MAP));
+}
+
+/****************************************************************************
+ * Consume-Funktion, um zentral durch konsumierbare Dinge ausgeloeste
+ * Aenderungen des Gesundheitszustandes herbeizufuehren.
+ ***************************************************************************/
+
+/* Konsumiert etwas
+ *
+ * Rueckgabewert
+ * 1 erfolgreich konsumiert
+ * 0 fehlende oder falsche Parameter
+ * <0 Bedingung fuer konsumieren nicht erfuellt, Bitset aus:
+ * 1 Kann nichts mehr essen
+ * 2 Kann nichts mehr trinken
+ * 4 Kann nichts mehr saufen
+ * 8 Abgebrochen durch Hook H_HOOK_CONSUME
+ */
+public varargs int consume(mapping cinfo, int testonly)
+{
+ int retval = 0;
+ // nur was tun, wenn auch Infos reinkommen
+ if (mappingp(cinfo) && sizeof(cinfo)) {
+ // Hooks aufrufen, sie aendern ggf. noch was in cinfo.
+ mixed *hret = HookFlow(H_HOOK_CONSUME, ({cinfo, testonly}) );
+ switch(hret[H_RETCODE])
+ {
+ case H_CANCELLED:
+ return -HC_HOOK_CANCELLATION;
+ case H_ALTERED:
+ // testonly kann nicht geaendert werden.
+ cinfo = hret[H_RETDATA][0];
+ }
+ // Legacy-Mappings (flache) neben strukturierten Mappings zulassen
+ // flache Kopien erzeugen (TODO?: und fuer Teilmappings nicht relevante
+ // Eintraege loeschen)
+ mapping conditions;
+ if (mappingp(cinfo[H_CONDITIONS])) {
+ conditions = copy(cinfo[H_CONDITIONS]);
+ } else {
+ conditions = copy(cinfo);
+ }
+ mapping effects;
+ if (mappingp(cinfo[H_EFFECTS])) {
+ effects = filter(cinfo[H_EFFECTS], (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
+ } else {
+ effects = filter(cinfo, (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
+ }
+
+ // Bedingungen pruefen
+ if (mappingp(conditions) && sizeof(conditions)) {
+ // Bedingungen fuer Konsum auswerten
+ if (conditions[P_FOOD] && !eat_food(conditions[P_FOOD], 1))
+ retval |= HC_MAX_FOOD_REACHED;
+ else if (conditions[P_DRINK] && !drink_soft(conditions[P_DRINK], 1))
+ retval |= HC_MAX_DRINK_REACHED;
+ else if (conditions[P_ALCOHOL] && !drink_alcohol(conditions[P_ALCOHOL], 1))
+ retval |= HC_MAX_ALCOHOL_REACHED;
+ // retval negativ machen, damit Fehler leicht erkennbar ist
+ retval = -retval;
+ }
+ // Bedingungen wurden abgearbeitet, jetzt die Heilung durchfuehren
+ if (!retval) {
+ if (!testonly) {
+ // Bedingungen erfuellen, wenn alles passt und kein Test
+ if (conditions[P_ALCOHOL])
+ drink_alcohol(conditions[P_ALCOHOL]);
+ if (conditions[P_DRINK])
+ drink_soft(conditions[P_DRINK]);
+ if (conditions[P_FOOD])
+ eat_food(conditions[P_FOOD]);
+ // Und jetzt die Wirkungen
+ if (effects[P_POISON])
+ SetProp(P_POISON, QueryProp(P_POISON) + effects[P_POISON]);
+ // Und nun wirklich heilen
+ switch (cinfo[H_DISTRIBUTION]) {
+ case HD_INSTANT:
+ map(effects, (: SetProp($1, QueryProp($1) + $2) :));
+ break;
+ case 1..50:
+ buffer_hp(effects[P_HP], cinfo[H_DISTRIBUTION]);
+ buffer_sp(effects[P_SP], cinfo[H_DISTRIBUTION]);
+ break;
+ default:
+ buffer_hp(effects[P_HP], HD_STANDARD);
+ buffer_sp(effects[P_SP], HD_STANDARD);
+ break;
+ }
+ }
+ retval = 1;
+ }
+ }
+ return retval;
+}
diff --git a/std/living/light.c b/std/living/light.c
new file mode 100644
index 0000000..5b12e1f
--- /dev/null
+++ b/std/living/light.c
@@ -0,0 +1,55 @@
+// MorgenGrauen MUDlib
+//
+// living/description.c -- description for living objects
+//
+// $Id: description.c 7340 2009-11-19 21:44:51Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <living/description.h>
+#include <living/skills.h>
+#undef NEED_PROTOTYPES
+
+#include <player/viewcmd.h>
+#include <new_skills.h>
+#include <container.h>
+#include <player/base.h>
+#include <wizlevels.h>
+
+inherit "/std/container/light";
+
+protected void create() {
+ ::create();
+ SetProp(P_LIGHT_TRANSPARENCY, 0);
+}
+
+static int _query_player_light()
+{
+ if (environment())
+ return environment()->QueryProp(P_INT_LIGHT) + QueryProp(P_LIGHT_MODIFIER);
+}
+
+varargs int CannotSee(int silent)
+{
+ string is_blind;
+ if (is_blind = QueryProp(P_BLIND)) {
+ if (!silent) {
+ if (stringp(is_blind))
+ tell_object(this_object(), is_blind);
+ else tell_object(this_object(), "Du bist blind!\n");
+ }
+ return 2;
+ }
+ if (UseSkill(SK_NIGHTVISION)<=0 &&
+ environment() && QueryProp(P_PLAYER_LIGHT)<=0 &&
+ (!IS_LEARNER(this_object()) || !Query(P_WANTS_TO_LEARN)))
+ {
+ if (!silent) tell_object(this_object(), "Es ist zu dunkel!\n");
+ return 1;
+ }
+ return 0;
+}
diff --git a/std/living/moneyhandler.c b/std/living/moneyhandler.c
new file mode 100644
index 0000000..7a22226
--- /dev/null
+++ b/std/living/moneyhandler.c
@@ -0,0 +1,26 @@
+// MorgenGrauen MUDlib
+//
+// living/moneyhandler.c -- money handler for livings
+//
+// $Id: moneyhandler.c 6738 2008-02-19 18:46:14Z Humni $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/container/moneyhandler";
+
+// Funktionen sollen nur das Programm ersetzen, natuerlich nur in der
+// Blueprint _dieses_ Objektes, nicht in anderen. ;-) BTW: Normalerweise
+// sollte niemand hierdrin create() rufen, der das Ding hier erbt.
+protected void create_super() {
+ if (object_name(this_object()) == __FILE__[..<3])
+ replace_program();
+}
+
+// wird nicht von erbenden Objekten gerufen. (Wozu auch.)
+protected void create() {
+ create_super();
+}
+
diff --git a/std/living/moving.c b/std/living/moving.c
new file mode 100644
index 0000000..c6149ff
--- /dev/null
+++ b/std/living/moving.c
@@ -0,0 +1,457 @@
+// MorgenGrauen MUDlib
+//
+// living/moving.c -- moving of living objects
+//
+// $Id: moving.c 9448 2016-01-22 17:52:28Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/thing/moving";
+
+#define NEED_PROTOTYPES
+#include <hook.h>
+#include <living/moving.h>
+#include <living/skills.h>
+#include <thing/properties.h>
+#include <thing/description.h>
+#include <moving.h>
+#include <new_skills.h>
+#include <living.h>
+
+#undef NEED_PROTOTYPES
+
+#include <config.h>
+#include <properties.h>
+#include <language.h>
+#include <wizlevels.h>
+#include <defines.h>
+
+
+protected void create()
+{
+ if (object_name(this_object()) == __FILE__[0..<3])
+ {
+ return;
+ }
+ offerHook(H_HOOK_MOVE,1);
+}
+
+public void AddPursuer(object ob)
+{
+ mixed *pur;
+
+ if (!objectp(ob))
+ return;
+
+ if (!pointerp(pur=Query(P_PURSUERS)))
+ pur=({0,({})});
+ else if (member(pur[1],ob)!=-1)
+ return;
+
+ SetProp(P_PURSUERS,({ pur[0], pur[1]+({ob})-({0}) }));
+ ob->_SetPursued(ME);
+}
+
+public void RemovePursuer(object ob)
+{
+ mixed *pur;
+
+ if (pointerp(pur=Query(P_PURSUERS,F_VALUE))
+ && member(pur[1],ob)!=-1)
+ {
+ pur[1]-=({ob,0});
+ if (ob)
+ ob->_RemovePursued(ME);
+ if (!pur[0]&&!sizeof(pur[1]))
+ pur=0;
+ SetProp(P_PURSUERS,pur);
+ }
+}
+
+public void _SetPursued(object ob)
+{
+ mixed *pur;
+
+ if (!pointerp(pur=Query(P_PURSUERS)))
+ pur=({0,({})});
+ else
+ if (objectp(pur[0]))
+ pur[0]->RemovePursuer(ME);
+ pur[0]=ob;
+ pur[1]-=({0});
+ Set(P_PURSUERS,pur);
+}
+
+public void _RemovePursued(object ob)
+{
+ mixed *pur;
+
+ if (!pointerp(pur=Query(P_PURSUERS)) || pur[0]!=ob)
+ return;
+ pur[0]=0;
+ pur[1]-=({0});
+ if (!sizeof(pur[1]))
+ pur=0;
+ Set(P_PURSUERS,pur);
+}
+
+
+private void kampfende( object en ) {
+ if (!objectp(en)) return;
+ tell_object( ME, capitalize(en->name()) +
+ " ist jetzt hinter Dir her.\n" );
+ tell_object( en, "Du verfolgst jetzt " + name(WEN) + ".\n" );
+ en->InsertSingleEnemy(ME);
+}
+
+private int _is_learner(object pl) {
+ return IS_LEARNER(pl);
+}
+
+
+// a) Pruefungen, ob das move erlaubt ist.
+// b) zum Ueberschreiben
+protected int PreventMove(object dest, object oldenv, int method) {
+
+ // M_NOCHECK? -> Bewegung eh erlaubt (und Rueckgabewert wuerde ignoriert),
+ // aber PreventInsert/PreventLeave() rufen und ignorieren.
+ if ((method&M_NOCHECK)) {
+ // erst PreventLeaveLiving() rufen...
+ if(environment())
+ environment()->PreventLeaveLiving(this_object(), dest);
+ // dann PreventInsertLiving() im Ziel-Env.
+ dest->PreventInsertLiving(this_object());
+ // und raus...
+ return(0);
+ }
+
+ // bei Lebewesen muss die Bewegungsmethode M_GO und M_TPORT sein. Dies ist
+ // gleichzeigt die Restriktion gegen das Nehmen von Lebewesen, da dort
+ // M_GET/M_GIVE/M_PUT etc. verwendet wuerde. Bei M_GO und M_TPORT findet
+ // keine Pruefung statt, ob das Objekt ins Ziel 'reinpasst' (Gewicht, Anzahl
+ // Objekte usw.).
+ // Ich finde es etwas merkwuerdig gebaut (Zesstra).
+ if ( !(method & (M_GO | M_TPORT)) )
+ return ME_PLAYER;
+
+ // alte und neue Umgebung auf NO_TPORT pruefen.
+ if ( (method & M_TPORT) ) {
+ if ( environment() &&
+ (environment()->QueryProp(P_NO_TPORT) & (NO_TPORT_OUT|NO_TPORT)) )
+ return ME_CANT_TPORT_OUT;
+ else if ( dest->QueryProp(P_NO_TPORT) & (NO_TPORT_IN|NO_TPORT) )
+ return ME_CANT_TPORT_IN;
+ }
+
+ // erst PreventLeaveLiving() testen...
+ if( environment() && environment()->PreventLeaveLiving(this_object(), dest))
+ return ME_CANT_LEAVE_ENV;
+ // dann PreventInsertLiving() im Ziel-Env
+ if (dest->PreventInsertLiving(this_object()))
+ return ME_CANT_BE_INSERTED;
+
+ return 0;
+}
+
+// Krams nach dem Move machen und nebenbei zum Ueberschreiben.
+protected void NotifyMove(object dest, object oldenv, int method) {
+ mixed res;
+ object enem;
+
+ // Begruessungsschlag fuer die Gegener
+ if ( !(method & M_NO_ATTACK) )
+ InitAttack();
+
+ if (!objectp(ME)) return;
+
+ // Verfolger nachholen.
+ if ( pointerp(res = Query(P_PURSUERS)) && sizeof(res[1]) ) {
+ while ( remove_call_out( "TakeFollowers" ) >= 0 );
+
+ call_out( "TakeFollowers", 0 );
+ }
+
+ // und noch das Team nachholen.
+ if ( oldenv != dest
+ && objectp(ME)
+ && QueryProp(P_TEAM_AUTOFOLLOW)
+ && objectp( enem = IsTeamLeader() ) )
+ enem->StartFollow(oldenv); // Teamverfolgung
+
+}
+
+varargs public int move( object|string dest, int method, string direction,
+ string textout, string textin )
+{
+ int para, nightvis, invis, tmp;
+ object oldenv, *inv;
+ string fn,vc;
+ mixed res;
+ mixed hookData, hookRes;
+
+ if (!objectp(dest) && !stringp(dest))
+ raise_error(sprintf("Wrong argument 1 to move(). 'dest' must be a "
+ "string or object! Argument was: %.100O\n",
+ dest));
+
+ // altes Env erstmal merken.
+ oldenv = environment();
+
+ //erstmal den richtigen Zielraum suchen, bevor irgendwelche Checks gemacht
+ //werden...
+ // Ist der Spieler in einer Parallelwelt?
+ if ( (para = QueryProp(P_PARA)) && intp(para) ) {
+ fn = objectp(dest) ? object_name(dest) : dest;
+
+ // Falls der Zielraum nicht schon explizit in der Parallelwelt ist,
+ // neuen Zielraum suchen. Aber nur, wenn fn kein # enthaelt (also kein
+ // Clone ist), sonst wuerde eine Bewegung nach raum#42^para versucht,
+ // was dann buggt. ;-) Problem wird offenbar, wenn ein Para-Lebewesen
+ // im create() eines VC-Raums in Para in den Raum bewegt wird, da
+ // dieser dann noch nicht vom Driver umbenannt wurde und raum#42
+ // heisst.
+ if ( !sizeof(regexp( ({ fn }), "\\^[1-9][0-9]*$" )) &&
+ strrstr(fn,"#")==-1 )
+ {
+ fn += "^" + para;
+
+ // Der Parallelwelt-Raum muss existieren und fuer Spieler
+ // freigegeben sein, damit er zum neuen Ziel wird. Ansonsten
+ // duerfen nur NPCs, Testspieler und Magier herein.
+ if ( (find_object(fn)
+ || ((file_size(fn+".c")>0 ||
+ (file_size(vc=implode(explode(fn,"/")[0..<2],"/")+
+ "/virtual_compiler.c")>0 &&
+ !catch(tmp=(int)call_other(vc,"QueryValidObject",fn);
+ publish) && tmp>0)) &&
+ !catch(load_object(fn);publish) )) &&
+ (!interactive(ME) || !fn->QueryProp(P_NO_PLAYERS) ||
+ (method & M_NOCHECK) || IS_LEARNER(ME) ||
+ (stringp(res = QueryProp(P_TESTPLAYER)) &&
+ IS_LEARNER( lower_case(res) ))) )
+ {
+ dest = fn;
+ }
+ else
+ {
+ // Wir bleiben in der Normalwelt.
+ para = 0;
+ }
+ }
+ }
+
+ // jetzt erstmal Hooks abpruefen, da sie ggf. die Daten aendern.
+ // alten P_TMP_MOVE_HOOK pruefen.
+ if ( res = QueryProp(P_TMP_MOVE_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], dest, method, direction,
+ textout, textin ) ){
+ if ( pointerp(res) && sizeof(res) == 5 ){
+ dest = res[0];
+ method = res[1];
+ direction = res[2];
+ textout = res[3];
+ textin = res[4];
+ }
+ else if ( intp(res) && res == -1 )
+ return ME_CANT_LEAVE_ENV;
+ }
+ } else
+ SetProp( P_TMP_MOVE_HOOK, 0 );
+ }
+ // move hook nach neuem Hooksystem triggern.
+ hookData=({dest,method,direction,textout,textin});
+ hookRes=HookFlow(H_HOOK_MOVE,hookData);
+ if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA) {
+ if(hookRes[H_RETCODE]==H_CANCELLED) {
+ return ME_CANT_LEAVE_ENV;
+ }
+ else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA] &&
+ pointerp(hookRes[H_RETDATA]) && sizeof(hookRes[H_RETDATA])>=5 ){
+ dest = hookRes[H_RETDATA][0];
+ method = hookRes[H_RETDATA][1];
+ direction = hookRes[H_RETDATA][2];
+ textout = hookRes[H_RETDATA][3];
+ textin = hookRes[H_RETDATA][4];
+ }
+ }
+
+ // dest auf Object normieren
+ if (stringp(dest)) dest=load_object(dest);
+
+ // jetzt Checks durchfuehren, ob das Move durchgefuehrt werden darf.
+ if (tmp=PreventMove(dest, oldenv, method)) {
+ // auf gueltigen Fehler pruefen, wer weiss, was Magier da evtl.
+ // versehentlich zurueckgeben.
+ if (VALID_MOVE_ERROR(tmp))
+ return(tmp);
+ else
+ return(ME_DONT_WANT_TO_BE_MOVED);
+ }
+
+ if ( invis = QueryProp(P_INVIS) )
+ method |= M_SILENT;
+
+ if ( objectp(oldenv) ) {
+ if ( !(method & M_SILENT) ) {
+ string *mout;
+ if ( !textout ){
+ if ( method & M_TPORT )
+ textout = (string) QueryProp(P_MMSGOUT) ||
+ (string) QueryProp(P_MSGOUT);
+ else
+ textout = (mout = explode( (string)
+ QueryProp(P_MSGOUT) || "",
+ "#" ))[0]
+ || (string)QueryProp(P_MMSGOUT);
+ }
+
+ if ( !sizeof(direction) )
+ direction = 0;
+
+ inv = all_inventory(environment()) - ({ this_object() });
+ inv = filter( inv, #'living/*'*/ );
+ inv -= filter_objects( inv, "CannotSee", 1 );
+
+ filter( inv, #'tell_object/*'*/,
+ Name( WER, 2 ) + " " + textout +
+ (direction ? " " + direction : "") +
+ (sizeof(mout) > 1 ? mout[1] : "") + ".\n" );
+ }
+ // Magier sehen auch Bewegungen, die M_SILENT sind
+ else if ( interactive(ME) ){
+ inv = (all_inventory(environment()) & users())
+ - ({ this_object() });
+ inv = filter( inv, #'_is_learner/*'*/ );
+
+ if ( invis )
+ fn = "(" + capitalize(getuid(ME)) + ") verschwindet "
+ "unsichtbar.\n";
+ else
+ fn = capitalize(getuid(ME)) + " verschwindet ganz leise.\n";
+
+ filter( inv, #'tell_object/*'*/, fn );
+ }
+
+ // Nackenschlag beim Fluechten:
+ if ( !(method & M_NO_ATTACK) && objectp(ME) )
+ ExitAttack();
+ //falls nach ExitAttack() das Living nicht mehr existiert, muss das
+ //move() auch nicht mehr fortgesetzt werden. Weiter unten gibt es auch
+ //min. eine Stelle, die nicht prueft und ggf. buggt. Daher erfolgt
+ //hier ggf. ein Abbruch. 15.11.06 Zesstra
+ if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
+
+ // Nackenschlag kann ME in den Todesraum bewegt haben...
+ if ( oldenv == environment() ) {
+ // Fuer alle anwesenden gegner kampfende() aufrufen
+ filter((QueryEnemies()[0] & all_inventory(oldenv))-({0}),
+ #'kampfende);
+ // Bugs im exit() sind ohne catch() einfach mist.
+ catch(environment()->exit(ME, dest);publish);
+ }
+ }
+
+ // irgendwas kann das Objekt zerstoert haben, z.B. env->exit().
+ if (!objectp(ME)) return(ME_WAS_DESTRUCTED);
+
+ if ( oldenv != environment() )
+ // Der Nackenschlag oder exit() koennen einen schon bewegt haben.
+ // Und wenn es in den Todesraum ist. ;^)
+ return MOVE_OK;
+
+ SetProp( P_PREPARED_SPELL, 0 ); // Spruchvorbereitung abgebrochen
+ SetProp( P_LAST_MOVE, time() ); // Zeitpunkt der letzten Bewegung
+
+ move_object(ME, dest);
+ if (!objectp(ME))
+ return(ME_WAS_DESTRUCTED);
+
+ dest = environment();
+
+ nightvis = UseSkill(SK_NIGHTVISION);
+ // Meldungen an nicht-Blinde ausgeben, falls keine 'stille' Bewegung
+ if ( !(method & M_SILENT) ) {
+ if ( !textin ) {
+ if ( method & M_TPORT )
+ textin = (string) QueryProp(P_MMSGIN);
+ else
+ textin = (string) QueryProp(P_MSGIN);
+ }
+
+ inv = all_inventory(environment()) - ({ this_object() });
+ inv = filter( inv, #'living/*'*/ );
+ inv -= filter_objects( inv, "CannotSee", 1 );
+ filter( inv, #'tell_object/*'*/,
+ capitalize(name( WER, 0 )) + " " + textin + ".\n" );
+ }
+ // sonst: Magier sehen auch M_SILENT-Bewegungen, hier also nur an Magier
+ // ausgeben, alle anderen sehen eh nix.
+ else if ( interactive(ME) ) {
+ inv = (all_inventory(environment()) & users()) - ({this_object()});
+ inv = filter( inv, #'_is_learner/*'*/ );
+ if ( invis )
+ fn = "(" + capitalize(getuid(ME)) + ") taucht unsichtbar auf.\n";
+ else
+ fn = capitalize(getuid(ME)) + " schleicht leise herein.\n";
+ filter( inv, #'tell_object, fn );
+ }
+
+ // "Objekt" ueber das Move informieren.
+ NotifyMove(dest, oldenv, method);
+
+ // InitAttack() in NotifyMove() kann das Objekt zerstoert haben.
+ if (!objectp(ME))
+ return(ME_WAS_DESTRUCTED);
+
+ //scheint wohl geklappt zu haben.
+ return MOVE_OK;
+}
+
+public void TakeFollowers()
+{
+ mixed *f,env;
+ int meth,i,r;
+
+ f=Query(P_PURSUERS);
+ if (!pointerp(f))
+ return;
+ env=environment();
+ if(object_name(env) == "/room/netztot") return;
+ foreach(object follower: f[1]-({0}) ) {
+ // die pruefung auf objectp ist nicht verrueckt, es kann theo. sein, dass
+ // Verfolger im PreventFollow() oder in ihrem move/init andere Verfolger
+ // zerstoeren.
+ if (objectp(follower) && environment(follower)!=env) {
+ //meth=M_NOCHECK;
+ meth=M_GO;
+ if (follower->Query(P_FOLLOW_SILENT))
+ meth|=M_SILENT|M_NO_SHOW;
+ catch(r=follower->PreventFollow(env);publish);
+ if (!r)
+ follower->move(env,meth);
+ else if (r==2)
+ RemovePursuer(follower);
+ }
+ }
+}
+
+varargs public int remove()
+{ object team;
+
+ if (environment())
+ {
+ if ( objectp(team=Query(P_TEAM)) )
+ catch(team->RemoveMember(ME);publish);
+
+ environment()->NotifyRemove(ME);
+ }
+ destruct(ME);
+ return 1;
+}
+
diff --git a/std/living/put_and_get.c b/std/living/put_and_get.c
new file mode 100644
index 0000000..4e1a97f
--- /dev/null
+++ b/std/living/put_and_get.c
@@ -0,0 +1,1243 @@
+// MorgenGrauen MUDlib
+//
+// living/put_and_get.c -- taking and putting things
+//
+// $Id: put_and_get.c 8755 2014-04-26 13:13:40Z Zesstra $
+
+/*
+ Grundlegend neu strukturiert von Amynthor im April-Juni 2007
+
+Die eigentlichen Funktionen:
+
+ private string put_or_get(object o, object dest)
+ Bewegt ein einzelnes Objekt mit automatisch bestimmter Method. Gibt im
+ Erfolgsfall 0 zurueck, sonst die auszugebende Fehlermeldung.
+
+ varargs int drop(object o, mixed msg)
+ varargs int put(object o, object dest, mixed msg)
+ varargs int pick(object o, mixed msg)
+ varargs int give(object o, object dest, mixed msg)
+ varargs int show(object o, object dest, mixed msg)
+ Der Spieler nimmt/legt/gibt/zeigt/laesst ein Objekt fallen, wobei die
+ entsprechenden oder optional abweichende (im Format von P_XXX_MSG) oder
+ gar keine (msg == 1) Meldungen ausgegeben werden. Es liegt in der
+ Verantwortung des Rufenden, sinnvolle Werte zu uebergeben; insbesondere
+ wird nicht geprueft, ob sich die Objekte in der Reichweite des Spielers
+ befinden. Gibt 1 zurueck, wenn das Objekt bewegt wurde, sonst 0.
+
+Hilfsfunktionen:
+
+ private object *__find_objects(string *tokens, object env, int is_source)
+ object *find_objects(string what, object env, int is_source)
+ Sucht im Raum und im Spieler (oder alternativ in der angegebenen Umgebung)
+ nach den in tokens/what bezeichneten Objekten. is_source bestimmt die
+ erwartete grammatische Form (0 fuer "topf auf herd" und 1 fuer "topf von
+ herd", siehe Manpage).
+
+ varargs int drop_objects(string str, mixed msg)
+ varargs int put_objects(string str, int casus, string verb, mixed msg)
+ varargs int pick_objects(string str, mixed msg, int flag)
+ varargs int give_objects(string str, mixed msg)
+ varargs int show_objects(string str, mixed msg)
+ Ein Befehl wie "wirf waffen weg" resultiert in einem Aufruf von
+ drop_objects("waffen"). Diese Funktionen sind hauptsaechlich fuer die
+ Behandlung der jeweiligen Kommandos vorgesehen, koennen jedoch auch fuer
+ eigene Befehle verwendet werden. put_objects() erwartet ausserdem den
+ Kasus ("Du kannst nichts an DER Pinwand befestigen.") und das verwendete
+ Verb in der Gundform ("befestigen"). Das Flag fuer pick_objects() gibt
+ an, ob das Objekt auch einfach herumliegen darf ("nimm ...") oder nicht
+ ("hole ..."). Gibt bei Erfolg 1, sonst 0 zurueck.
+
+ object *moved_objects()
+ object moved_where()
+ Gibt die eben fallengelassenen/gesteckten/... Objekte zurueck und wohin
+ sie gesteckt/wem sie gegeben/gezeigt wurden. Fuer den Fall, dass man
+ anschliessend noch etwas mit ihnen machen moechte.
+
+Die einzelnen Kommandos:
+ static int fallenlassen(string str)
+ static int werfen(string str)
+ static int legen(string str)
+ static int stecken(string str)
+ static int holen(string str)
+ static int nehmen(string str)
+ static int geben(string str)
+ Minimale Wrapper fuer XXX_objects(), entfernen "fallen", "weg" bzw. "ab"
+ aus den Argumenten und setzen entsprechende Standard-Fehlermeldungen.
+
+ protected void add_put_and_get_commands()
+ Registriert obige Funktionen per add_action().
+
+Aus reinen Kompatibilitaetsgruenden weiterhin enthalten:
+
+ object* find_obs(string str, int meth)
+ int pick_obj(object ob)
+ int drop_obj(object ob)
+ int put_obj(object ob, object where)
+ int give_obj(object ob, object where)
+ siehe Manpages
+
+*/
+
+/*
+ 21. Okt 1998 komplette neu programmierung von put_and_get.c (Padreic)
+- die Gruppenauswahlen alles, waffen und ruestungen sind jetzt immer moeglich
+ die Gruppen sind sehr leicht erweiterbar und man sollte sich nicht scheuen
+ davon gebrauch zu machen...
+- mit "in mir" und "im raum" kann man den abzusuchenden Raum selbst eingrenzen
+- mit "alle|jede|jeden|jedes <id>" kann man auch ganze objektgruppen auswaehlen
+*/
+
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <language.h>
+#include <thing/description.h>
+#include <thing/properties.h>
+#include <moving.h>
+#include <container.h>
+#undef NEED_PROTOTYPES
+
+#include <defines.h>
+#include <properties.h>
+#include <wizlevels.h>
+
+#define TME(str) tell_object(this_object(), \
+ break_string(str, 78, 0, BS_LEAVE_MY_LFS))
+#define TOB(ob,str) tell_object(ob, break_string(str, 78, 0, BS_LEAVE_MY_LFS))
+#define SAY(str) tell_room(environment(), \
+ break_string(str, 78, 0, BS_LEAVE_MY_LFS), ({this_object()}))
+#define SAY2(obs, str) tell_room(environment(), \
+ break_string(str, 78, 0, BS_LEAVE_MY_LFS), ({this_object()}) + obs)
+#define NF(str) _notify_fail(break_string(str, 78, 0, BS_LEAVE_MY_LFS))
+
+private nosave closure cl;
+private nosave string wen0, wen1, wer0;
+private nosave object *last_moved_objects;
+private nosave object last_moved_where;
+
+
+/*********************** Die eigentlichen Funktionen ************************/
+
+private string put_or_get(object o, object dest)
+
+/* Bewegt ein einzelnes Objekt <o> in das Zielobjekt <dest>. Verwendet dazu
+ * je nach Umstaenden (Ziel ist der Spieler, die Umgebung, ein Lebewesen) den
+ * entsprechenden Wert fuer <method>. Gibt im Erfolgsfall 0 zurueck, erstellt
+ * sonst die auszugebende Fehlermeldung und gibt diese zurueck.
+ */
+
+{
+ int method, ret;
+ string str;
+
+ //if (living(o))
+ // raise_error(sprintf("Lebendes Argument fuer put_or_get: %O\n", o));
+
+ if (dest == this_object()) /* pick */
+ method = M_GET;
+ else if (dest == environment()) /* drop */
+ method = M_PUT;
+ else if (living(dest)) /* give */
+ method = M_GIVE;
+ else { /* put */
+ method = M_PUT | M_GET;
+ if (first_inventory(o))
+ return o->Name(WER, 1) + " ist nicht leer!";
+ }
+
+ if ((ret = o->move(dest, method)) > 0)
+ return 0;
+
+ switch (ret) {
+ case ME_TOO_HEAVY:
+ if (dest == this_object())
+ if (QueryProp(P_GHOST))
+ return "Als Geist kannst Du nichts mitnehmen.";
+ else
+ return "Du kannst " + wen1 + " nicht mehr tragen.";
+
+ if (stringp(str = dest->QueryProp(P_TOO_HEAVY_MSG)))
+ return capitalize(replace_personal(str, ({o, dest}), 1));
+
+ if (living(dest)) {
+ if (dest->QueryProp(P_GHOST))
+ return "Als Geist kann " + dest->name(WER, 1) +
+ " nichts mitnehmen.";
+ else
+ return dest->Name(WER, 1) + " kann " +
+ wen0 + " nicht mehr tragen.";
+ }
+
+ if (dest == environment())
+ return (stringp(str = dest->Name(WER, 1)) && sizeof(str) ?
+ str : "Der Raum") + " wuerde dann zu schwer werden.";
+
+ return capitalize(wer0 + " passt in " + dest->name(WEN, 1) +
+ " nicht mehr rein.");
+
+ case ME_CANT_BE_DROPPED:
+ if (o && stringp(str = o->QueryProp(P_NODROP)))
+ return str;
+
+ if (dest == environment())
+ return "Du kannst " + wen1 + " nicht wegwerfen!";
+
+ if (living(dest))
+ return "Du kannst " + wen1 + " nicht weggeben.";
+
+ return "So wirst Du " + wen1 + " nicht los...";
+
+ case ME_CANT_BE_TAKEN:
+ if (o && stringp(str = o->QueryProp(P_NOGET)))
+ return str;
+
+ if (stringp(str = environment(o)->QueryProp(P_NOLEAVE_MSG)))
+ return capitalize(
+ replace_personal(str, ({o, environment(o)}), 1));
+
+ //if (dest != environment())
+ // return "Du kannst " + wen1 + " nicht einmal nehmen.";
+
+ return "Du kannst " + wen1 + " nicht nehmen.";
+
+ case ME_CANT_BE_INSERTED:
+ if (stringp(str = dest->QueryProp(P_NOINSERT_MSG)))
+ return capitalize(replace_personal(str, ({o, dest}), 1));
+
+ if (dest == environment())
+ return "Das darfst Du hier nicht ablegen.";
+
+ if (dest == this_object())
+ return "Du kannst " + wen1 + " nicht nehmen.";
+
+ if (living(dest))
+ return "Das kannst Du " + dest->name(WEM, 1) + " nicht geben.";
+
+ return capitalize(wen0 + " kannst Du dort nicht hineinstecken.");
+
+ case ME_CANT_LEAVE_ENV:
+ // ME_CANT_LEAVE_ENV kann nur auftreten, wenn o ein Environment
+ // hat, deshalb kein Check dadrauf
+ if (stringp(str = environment(o)->QueryProp(P_NOLEAVE_MSG)))
+ return capitalize(
+ replace_personal(str, ({o, environment(o)}), 1));
+
+ if (environment(o) != this_object())
+ return "Du kannst " + wen1 + " nicht nehmen.";
+
+ if (dest == environment())
+ return "Du kannst " + wen1 + " nicht wegwerfen!";
+
+ if (living(dest))
+ return "Du kannst " + wen1 + " nicht weggeben.";
+
+ return "So wirst Du " + wen1 + " nicht los...";
+
+ case ME_TOO_HEAVY_FOR_ENV:
+ if (stringp(str = dest->QueryProp(P_ENV_TOO_HEAVY_MSG)))
+ return capitalize(replace_personal(str, ({o, dest}), 1));
+
+ if (environment(dest) == this_object())
+ return dest->Name(WER, 1) +
+ " wuerde Dir dann zu schwer werden.";
+
+ return (stringp(str = environment(dest)->Name(WER, 1))
+ && sizeof(str) ? str : "Der Raum") +
+ " wuerde dann zu schwer werden.";
+
+ case TOO_MANY_OBJECTS:
+ if (stringp(str = dest->QueryProp(P_TOO_MANY_MSG)))
+ return capitalize(replace_personal(str, ({o, dest}), 1));
+
+ if (dest == this_object())
+ return "Soviele Gegenstaende kannst Du unmoeglich tragen!";
+
+ if (dest == environment())
+ return "Dafuer ist hier nicht mehr genug Platz.";
+
+ if (living(dest))
+ return dest->Name(WER, 1) + " kann " + wen0 +
+ " nicht mehr tragen.";
+
+ return "Dafuer ist nicht mehr genug Platz in " +
+ dest->name(WEM, 1) + ".";
+
+ default:
+ if (dest == this_object())
+ return "Du kannst " + wen1 + " nicht nehmen.";
+
+ if (dest == environment())
+ return "Du kannst " + wen1 + " nicht wegwerfen!";
+
+ if (living(dest))
+ return "Du kannst " + wen1 + " nicht weggeben.";
+
+ return capitalize(wen0 + " kannst Du dort nicht hineinstecken.");
+ }
+ return 0; // NOT REACHED
+}
+
+
+/* varargs int drop(object o, mixed msg)
+ * varargs int put(object o, object dest, mixed msg)
+ * varargs int pick(object o, mixed msg)
+ * varargs int give(object o, object dest, mixed msg)
+ * varargs int show(object o, object dest, mixed msg)
+ *
+ * Der Spieler nimmt/legt/gibt/zeigt/laesst ein Objekt fallen, wobei die
+ * entsprechenden oder optional abweichende (im Format von P_XXX_MSG) oder
+ * gar keine (msg == 1) Meldungen ausgegeben werden. Es liegt in der
+ * Verantwortung des Rufenden, sinnvolle Werte zu uebergeben; insbesondere
+ * wird nicht geprueft, ob sich die Objekte in der Reichweite des Spielers
+ * befinden. Gibt 1 zurueck, wenn das Objekt bewegt wurde, sonst 0.
+ */
+
+varargs int drop(object o, mixed msg)
+{
+ string str;
+
+ // vorher speichern, falls das Objekt zerstoert wird
+ cl = symbol_function("name", o);
+ wen0 = funcall(cl, WEN, 0);
+ wen1 = funcall(cl, WEN, 1);
+ wer0 = 0;
+
+ if (!msg)
+ msg = o->QueryProp(P_DROP_MSG);
+
+ if (str = put_or_get(o, environment())) {
+ TME(str);
+ return 0;
+ }
+
+ if (!msg) {
+ TME("Du laesst " + wen1 + " fallen.");
+ SAY(Name(WER,1) + " laesst " + wen0 + " fallen.");
+ } else if (pointerp(msg))
+ switch (sizeof(msg)) {
+ // Wenn es zwei Strings gibt, geht die 2. ans Environment
+ case 2:
+ SAY(replace_personal(msg[1], ({this_object(), o||wen0}), 1));
+ case 1:
+ TME(replace_personal(msg[0], ({this_object(), o||wen1}), 1));
+ break;
+ default:
+ raise_error(sprintf(
+ "Falsches Format fuer P_DROP_MSG: %O\n", o||wen1));
+ }
+
+ return 1;
+}
+
+varargs int put(object o, object dest, mixed msg)
+{
+ string str;
+
+ // Falls das jemand von aussen ruft und Schrott uebergibt...
+ //if (living(dest))
+ // raise_error(sprintf("Lebendes Ziel fuer put(): %O\n", dest));
+ if (dest == environment())
+ raise_error("Ziel fuer put() ist Umgebung des Spielers\n");
+
+ // vorher speichern, falls das Objekt zerstoert wird
+ cl = symbol_function("name", o);
+ wen0 = funcall(cl, WEN, 0);
+ wen1 = funcall(cl, WEN, 1);
+ wer0 = funcall(cl, WER, 0);
+
+ if (!msg)
+ msg = o->QueryProp(P_PUT_MSG);
+
+ if (str = put_or_get(o, dest)) {
+ TME(str);
+ return 0;
+ }
+
+
+ if (!msg) {
+ TME("Du steckst " + wen1 + " in " + dest->name(WEN, 1) + ".");
+ if (environment())
+ SAY(Name(WER, 1) + " steckt " + wen0 +
+ " in " + dest->name(WEN, 0) + ".");
+ }
+ else if (pointerp(msg)) {
+ switch (sizeof(msg)) {
+ case 2:
+ if (environment())
+ SAY(replace_personal(msg[1], ({this_object(), o||wen0, dest}), 1));
+ case 1:
+ TME(replace_personal(msg[0], ({this_object(), o||wen1, dest}), 1));
+ break;
+ default:
+ raise_error(sprintf(
+ "Falsches Format fuer P_PUT_MSG: %O\n",o||wen1));
+ }
+ }
+
+ return 1;
+}
+
+varargs int pick(object o, mixed msg)
+{
+ string str;
+
+ // vorher speichern, falls das Objekt zerstoert wird
+ cl = symbol_function("name", o);
+ wen0 = 0;
+ wen1 = funcall(cl, WEN, 1);
+ wer0 = 0;
+
+ if (!msg)
+ msg = o->QueryProp(P_PICK_MSG);
+
+ if (str = put_or_get(o, this_object())) {
+ TME(str);
+ return 0;
+ }
+
+ if (!msg) {
+ TME("Du nimmst " + wen1 + ".");
+ SAY(Name(WER, 1) + " nimmt " + wen1 + ".");
+ } else if (pointerp(msg))
+ switch (sizeof(msg)) {
+ case 2:
+ SAY(replace_personal(msg[1], ({this_object(), o||wen1}), 1));
+ case 1:
+ TME(replace_personal(msg[0], ({this_object(), o||wen1}), 1));
+ break;
+ default:
+ raise_error(sprintf(
+ "Falsches Format fuer P_PICK_MSG: %O\n", o||wen1));
+ }
+
+ return 1;
+}
+
+varargs int give(object o, object dest, mixed msg)
+{
+ string zname, gname;
+ string str;
+
+ // Falls das jemand von aussen ruft und Schrott uebergibt...
+ if (!living(dest))
+ raise_error(sprintf("Totes Ziel fuer give(): %O\n", dest));
+
+ zname = dest->name(WEM, 1);
+ gname = Name(WER, 1);
+
+ // vorher speichern, falls das Objekt zerstoert wird
+ cl = symbol_function("name", o);
+ wen0 = funcall(cl, WEN, 0);
+ wen1 = funcall(cl, WEN, 1);
+ wer0 = 0;
+
+ if (!msg)
+ msg = o->QueryProp(P_GIVE_MSG);
+
+ if (str = put_or_get(o, dest)) {
+ TME(str);
+ return 0;
+ }
+
+ if (!msg) {
+ TME("Du gibst " + zname + " " + wen1 + ".");
+ TOB(dest, gname + " gibt Dir " + wen0 + ".");
+ SAY2(({dest}), gname + " gibt " + zname + " " + wen0 + ".");
+ } else if (pointerp(msg))
+ switch (sizeof(msg)) {
+ case 3:
+ TOB(dest, replace_personal(
+ msg[2], ({this_object(), o||wen0, dest||zname}), 1));
+ case 2:
+ SAY2(({dest, this_object()}), replace_personal(
+ msg[1], ({this_object(), o||wen0, dest||zname}), 1));
+ case 1:
+ TME(replace_personal(
+ msg[0], ({this_object(), o||wen1, dest||zname}), 1));
+ break;
+ default:
+ raise_error(sprintf(
+ "Falsches Format fuer P_GIVE_MSG: %O\n", o||wen1));
+ }
+
+ if (!query_once_interactive(dest))
+ dest->give_notify(o);
+
+ return 1;
+}
+
+varargs int show(object o, object whom, mixed msg)
+{
+ string zname, gname;
+ string wen0, wen2, long;
+
+ zname = whom ? whom->name(WEM, 1) : "allen";
+ gname = Name(WER, 1);
+
+ if (!msg)
+ msg = o->QueryProp(P_SHOW_MSG);
+
+ if (environment(o) == this_object() ||
+ environment(environment(o)) == this_object()) {
+ wen0 = o->name(WEN, 0);
+
+ /* Der Akkusativ muss mit dem unbestimmten Artikel gebildet werden,
+ * damit eventuelle Adjektive die richtige Endung besitzen.
+ * (ein kleines Schwert -> kleines Schwert -> Dein kleines Schwert)
+ */
+
+ if (o->QueryProp(P_ARTICLE) && member(wen0, ' ') >= 0) {
+ int obgender = o->QueryProp(P_GENDER);
+ int obnum = o->QueryProp(P_AMOUNT) > 1 ? PLURAL : SINGULAR;
+
+ // Wichtig: P_AMOUNT ist 0 fuer Objekte, die nicht von unit erben.
+ // Da unit.c kein P_AMOUNT==0 zulaesst, nehmen wir diesen Fall als
+ // singular an. *Rumata
+
+ wen2 = wen0[member(wen0, ' ')..];
+ wen0 = QueryPossPronoun(o, WEN, obnum) + wen2;
+
+ if (obnum == PLURAL || obgender == FEMALE)
+ wen2 = "Deine" + wen2;
+ else if (obgender == MALE)
+ wen2 = "Deinen" + wen2;
+ else
+ wen2 = "Dein" + wen2;
+ } else
+ wen2 = wen0;
+ } else
+ wen2 = wen0 = o->name(WEN, 1);
+
+ // vorher speichern, falls das Objekt im catch_tell() zerstoert wird
+ long = o->long(4);
+
+ if (!msg) {
+ TME("Du zeigst " + zname + " " + wen2 + ".");
+ if (!whom)
+ SAY(gname + " zeigt Dir " + wen0 + ".");
+ else {
+ TOB(whom, gname + " zeigt Dir " + wen0 + ".");
+ SAY2(({whom}), gname + " zeigt " + zname + " " + wen0 + ".");
+ }
+ } else if (pointerp(msg))
+ switch (sizeof(msg)) {
+ case 3:
+ if (whom)
+ TOB(whom, replace_personal(
+ msg[2], ({this_object(), o||wen0, whom||zname}), 1));
+ else
+ SAY(replace_personal(
+ msg[2], ({this_object(), o||wen0, whom||zname}), 1));
+ case 2:
+ if (whom)
+ SAY2(({whom, this_object()}), replace_personal(
+ msg[1], ({this_object(), o||wen0, whom||zname}), 1));
+ case 1:
+ TME(replace_personal(
+ msg[0], ({this_object(), o||wen2, whom||zname}), 1));
+ break;
+ default:
+ raise_error(sprintf(
+ "Falsches Format fuer P_SHOW_MSG: %O\n", o||wen0));
+ }
+
+ if (!whom)
+ SAY(long);
+ else {
+ TOB(whom, long);
+ if (!query_once_interactive(whom))
+ whom->show_notify(o);
+ }
+
+ return 1;
+}
+
+
+/***************************** Hilfsfunktionen *****************************/
+
+/* private object *__find_objects(string *tokens, object env, int is_source);
+ * object *find_objects(string what, object env, int is_source);
+ *
+ * Sucht im Raum und im Spieler (oder alternativ in der angegebenen Umgebung)
+ * nach den in tokens/what bezeichneten Objekten. is_source bestimmt die
+ * erwartete grammatische Form (0 fuer "topf auf herd" und 1 fuer "topf von
+ * herd", siehe Manpage).
+ */
+
+private object *__find_objects(string *tokens, object env, int is_source)
+{
+ object ob, *obs;
+
+ // is_source == 0: Objekt soll nicht bewegt werden ("topf auf herd")
+ // 1: Objekt soll bewegt werden ("topf von herd")
+ // 2: intern
+
+ if (!env && sizeof(tokens) > 1 && tokens[<1] == "hier") {
+ tokens = tokens[..<2];
+ env = environment();
+ }
+ else if (!env && sizeof(tokens) > 2 && tokens[<2] == "in")
+ {
+ if (tokens[<1] == "mir" ||
+ tokens[<1] == "dir") {
+ tokens = tokens[..<3];
+ env = this_object();
+ }
+ else if (tokens[<1] == "raum") {
+ tokens = tokens[..<3];
+ env = environment();
+ }
+ }
+
+ for (int i = sizeof(tokens)-1; i > 1; i--) {
+ if (env)
+ ob = present(implode(tokens[i..], " "), env);
+ else
+ ob = present(implode(tokens[i..], " "), environment()) ||
+ present(implode(tokens[i..], " "), this_object());
+
+ if (!ob)
+ continue;
+
+ if (living(ob)) {
+ NF("Aber " + ob->name(WER, 1) + " lebt doch!");
+ continue;
+ }
+
+ if (ob->QueryProp(P_CNT_STATUS)) {
+ NF("Aber " + ob->name(WER, 1) + " ist doch geschlossen!");
+ continue;
+ }
+
+ if (is_source != 0 &&
+ tokens[i-1] == ob->QueryProp(P_SOURCE_PREPOSITION))
+ return ob->present_objects(implode(tokens[..i-2], " "));
+
+ if (tokens[i-1] == ob->QueryProp(P_PREPOSITION))
+ return __find_objects(tokens[..i-2], ob, is_source ? 2 : 0);
+
+ NF("Du kannst nichts " + tokens[i-1] + " " +
+ ob->name(WEM, 1) + " nehmen.");
+ }
+
+ if (is_source == 2)
+ return ({});
+
+ if (env)
+ return env->present_objects(implode(tokens, " "));
+
+ if (environment() &&
+ sizeof(obs = environment()->present_objects(implode(tokens, " "))))
+ return obs;
+
+ return present_objects(implode(tokens, " "));
+}
+
+object *find_objects(string what, object env, int is_source)
+{
+ if (!stringp(what) || !sizeof(what))
+ return ({});
+ return __find_objects(explode(what, " "), env, is_source);
+}
+
+
+/* varargs int drop_objects(string str, mixed msg);
+ * varargs int put_objects(string str, int casus, string verb, mixed msg);
+ * varargs int pick_objects(string str, int flag, mixed msg);
+ * varargs int give_objects(string str, mixed msg);
+ * varargs int show_objects(string str, mixed msg);
+ *
+ * Ein Befehl wie "wirf waffen weg" resultiert in einem Aufruf von
+ * drop_objects("waffen"). Diese Funktionen sind hauptsaechlich fuer die
+ * Behandlung der jeweiligen Kommandos vorgesehen, koennen jedoch auch fuer
+ * eigene Befehle verwendet werden. put_objects() erwartet ausserdem den
+ * Kasus ("Du kannst nichts an DER Pinwand befestigen.") und das verwendete
+ * Verb in der Gundform ("befestigen"). Das Flag fuer pick_objects() gibt
+ * an, ob das Objekt auch einfach herumliegen darf ("nimm ...") oder nicht
+ * ("hole ..."). Gibt bei Erfolg 1, sonst 0 zurueck.
+ */
+
+varargs int drop_objects(string str, mixed msg)
+{
+ object *obs;
+
+ if (!sizeof(obs = find_objects(str, this_object(), 1)))
+ return 0;
+
+ foreach (object o: obs) {
+ if (objectp(o))
+ drop(o, msg);
+
+ if (get_eval_cost() < 100000) {
+ TME("Den Rest behaeltst Du erst mal.");
+ last_moved_objects = obs[..member(obs, o)];
+ last_moved_where = 0;
+ return 1;
+ }
+ }
+
+ last_moved_objects = obs;
+ last_moved_where = 0;
+ return 1;
+}
+
+varargs int put_objects(string str, int casus, string verb, mixed msg)
+{
+ object *obs, dest, *no_move;
+
+ if (!stringp(str) || !sizeof(str)) return 0;
+
+ string *tokens = explode(str, " ");
+ int allow_room = 1;
+ int allow_me = 1;
+
+ if (sizeof(tokens) > 1 && tokens[<1] == "hier") {
+ tokens = tokens[..<2];
+ allow_me = 0;
+ } else if (sizeof(tokens) > 2 && tokens[<2] == "in")
+ if (tokens[<1] == "mir" ||
+ tokens[<1] == "dir") {
+ tokens = tokens[..<3];
+ allow_room = 0;
+ } else if (tokens[<1] == "raum") {
+ tokens = tokens[..<3];
+ allow_me = 0;
+ }
+
+ for (int i = sizeof(tokens)-1; i > 1; i--) {
+ if (!(dest = allow_room && present(implode(tokens[i..], " "),
+ environment())) &&
+ !(dest = allow_me && present(implode(tokens[i..], " "),
+ this_object())))
+ continue;
+
+ if (living(dest)) {
+ NF("Aber " + dest->name(WER, 1) + " lebt doch!");
+ continue;
+ }
+/*
+ if (verb == "legen" && !dest->QueryProp(P_TRAY)) {
+ NF("Du kannst nichts auf " + dest->name(WEN, 1) + " legen.");
+ continue;
+ }
+*/
+ if (verb == "stecken" && !dest->QueryProp(P_CONTAINER)) {
+ NF("Du kannst in " + dest->name(WEN, 1) + " nichts reinstecken.");
+ continue;
+ }
+
+ if (dest->QueryProp(P_CNT_STATUS)) {
+ NF("Aber " + dest->name(WER, 1) + " ist doch geschlossen!");
+ continue;
+ }
+
+ if (tokens[i-1] != dest->QueryProp(P_DEST_PREPOSITION)) {
+ NF("Du kannst nichts " + tokens[i-1] + " " +
+ dest->name(casus, 1) + " " + verb + ".");
+ continue;
+ }
+
+ if (!sizeof(obs = __find_objects(tokens[..i-2], 0, 1) - ({ dest }))) {
+ NF("WAS moechtest Du " + tokens[i-1] + " " +
+ dest->name(casus, 1) + " " + verb + "?");
+ return 0;
+ }
+
+ if (sizeof(no_move = obs & all_inventory(dest))) {
+ TME(capitalize(CountUp(map_objects(no_move, "name", WER, 1))) +
+ (sizeof(no_move) == 1 ? " ist" : " sind") +
+ " doch bereits in " + dest->name(WEM,1) + ".");
+ if (!sizeof(obs -= no_move))
+ return 0;
+ }
+
+ foreach (object o: obs) {
+ if (objectp(o))
+ put(o, dest, msg);
+
+ if (get_eval_cost() < 100000) {
+ TME("Den Rest laesst Du erst mal, wo er ist.");
+ last_moved_objects = obs[..member(obs, o)];
+ last_moved_where = dest;
+ return 1;
+ }
+ }
+
+ last_moved_objects = obs;
+ last_moved_where = dest;
+ return 1;
+ }
+
+ return 0;
+}
+
+varargs int pick_objects(string str, int flag, mixed msg)
+{
+ object *obs;
+
+ if (((int)QueryProp(P_MAX_HANDS)) < 1){
+ NF("Ohne Haende kannst Du nichts nehmen.");
+ return 0;
+ }
+
+ if (!sizeof(obs = find_objects(str, 0, 1) - all_inventory()
+ - (flag ? all_inventory(environment()) : ({}))))
+ return 0;
+
+ foreach (object o: obs) {
+ if (objectp(o))
+ pick(o, msg);
+
+ if (get_eval_cost() < 100000) {
+ TME("Den Rest laesst Du erst mal liegen.");
+ last_moved_objects = obs[..member(obs, o)];
+ last_moved_where = 0;
+ return 1;
+ }
+ }
+
+ last_moved_objects = obs;
+ last_moved_where = 0;
+ return 1;
+}
+
+varargs int give_objects(string str, mixed msg)
+{
+ object *obs, dest;
+
+ if (!stringp(str) || !sizeof(str)) return 0;
+
+ string *tokens = explode(str, " ");
+
+ if (((int)QueryProp(P_MAX_HANDS)) < 1){
+ NF("Ohne Haende kannst Du nichts weggeben.");
+ return 0;
+ }
+
+ for (int i = 0; i < sizeof(tokens)-1; i++) {
+ if (!(dest = present(implode(tokens[..i], " "), environment())))
+ continue;
+
+ if (!living(dest)) {
+ NF("Aber " + dest->name(WER, 1) + " lebt doch gar nicht!");
+ dest = 0;
+ continue;
+ }
+
+ if (!sizeof(obs = __find_objects(tokens[i+1..], 0, 1))) {
+ NF("WAS moechtest Du " + dest->name(WEM, 1)+" geben?");
+ dest = 0;
+ } else
+ break;
+ }
+
+ if (!dest) {
+ int pos;
+
+ if ((pos = strrstr(str, " an ")) >= 0) {
+ dest = present(str[pos+4..], environment());
+ // zu gebende Objekte in Env + Living suchen
+ obs = find_objects(str[..pos-1], 0, 1);
+ }
+ }
+
+ if (!dest || !living(dest) || !sizeof(obs))
+ return 0;
+
+ foreach (object o: obs) {
+ if (objectp(o))
+ give(o, dest, msg);
+
+ if (get_eval_cost() < 100000) {
+ TME("Den Rest behaeltst Du erst mal.");
+ last_moved_objects = obs[..member(obs, o)];
+ last_moved_where = dest;
+ return 1;
+ }
+ }
+
+ last_moved_objects = obs;
+ last_moved_where = dest;
+ return 1;
+}
+
+varargs int show_objects(string str, mixed msg)
+{
+ object *obs, whom;
+
+ if (!stringp(str) || !sizeof(str))
+ return 0;
+
+ string *tokens = explode(str, " ");
+
+ for (int i = 0; i < sizeof(tokens)-1; i++) {
+ if (whom = present(implode(tokens[..i], " "), environment())) {
+ if (!living(whom)) {
+ NF("Aber " + whom->name(WER, 1) + " lebt doch gar nicht!");
+ continue;
+ }
+ } else {
+ if (i != 0 || tokens[0] != "allen")
+ continue;
+
+ if (!sizeof(filter(all_inventory(environment()) -
+ ({ this_object() }), #'living))) {
+ NF("Hier ist niemand, dem Du etwas zeigen koenntest!");
+ continue;
+ }
+ }
+
+ if (whom == this_object()) {
+ NF("Dazu solltest Du dann besser 'schau' benutzen!\n");
+ continue;
+ }
+
+ if (!sizeof(obs = __find_objects(tokens[i+1..], this_object(), 0)) &&
+ !sizeof(obs = __find_objects(tokens[i+1..], 0, 0)
+ - ({ this_object(), whom }))) {
+ NF("WAS moechtest Du " + (whom ? whom->name(WEM, 1) : "allen") +
+ " zeigen?");
+ continue;
+ }
+
+ foreach (object o: obs) {
+ if (objectp(o))
+ show(o, whom, msg);
+
+ if (get_eval_cost() < 100000) {
+ TME("Das reicht erst mal.");
+ last_moved_objects = obs[..member(obs, o)];
+ last_moved_where = whom;
+ return 1;
+ }
+ }
+
+ last_moved_objects = obs;
+ last_moved_where = whom;
+ return 1;
+ }
+
+ return 0;
+}
+
+object *moved_objects(void)
+{
+ return last_moved_objects;
+}
+
+object moved_where(void)
+{
+ return last_moved_where;
+}
+
+
+/************************* Die einzelnen Kommandos **************************/
+
+/* static int fallenlassen(string str)
+ * static int werfen(string str)
+ * static int legen(string str)
+ * static int stecken(string str)
+ * static int holen(string str)
+ * static int nehmen(string str)
+ * static int geben(string str)
+ * Minimale Wrapper fuer XXX_objects(), entfernen "fallen", "weg" bzw. "ab"
+ * aus den Argumenten und setzen entsprechende Standard-Fehlermeldungen.
+ *
+ * protected void add_put_and_get_commands()
+ * Registriert obige Funktionen per add_action().
+ */
+
+static int fallenlassen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts fallenlassen.\n");
+ return 0;
+ }
+
+ if (!str || str[<7..] != " fallen") {
+ _notify_fail("Lass etwas FALLEN, oder was meinst Du?\n");
+ return 0;
+ }
+
+ _notify_fail("WAS moechtest Du fallenlassen?\n");
+ return drop_objects(str[0..<8]);
+}
+
+static int werfen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts wegwerfen.\n");
+ return 0;
+ }
+
+ if (!str || str[<4..] != " weg") {
+ _notify_fail("Wirf etwas WEG, oder was meinst Du?\n");
+ return 0;
+ }
+
+ _notify_fail("WAS moechtest Du loswerden?\n");
+ return drop_objects(str[0..<5]);
+}
+
+static int legen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts weglegen.\n");
+ return 0;
+ }
+
+ if (!str) {
+ _notify_fail("Lege etwas AB, oder was meinst Du?\n");
+ return 0;
+ }
+
+ if (str[<3..] == " ab") {
+ _notify_fail("WAS moechtest Du ablegen?\n");
+ return drop_objects(str[0..<4]);
+ }
+
+ if (str[<4..] == " weg") {
+ _notify_fail("WAS moechtest Du weglegen?\n");
+ return drop_objects(str[0..<5]);
+ }
+
+ _notify_fail("WAS moechtest Du WOHIN legen?\n");
+ return put_objects(str, WEN, "legen");
+}
+
+static int stecken(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Das kannst Du in Deinem immateriellen Zustand nicht.\n");
+ return 0;
+ }
+
+ _notify_fail("WAS moechtest Du WOHIN stecken?\n");
+ return put_objects(str, WEN, "stecken");
+}
+
+static int holen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts nehmen.\n");
+ return 0;
+ }
+
+ _notify_fail("WAS moechtest Du aus WAS holen?\n");
+ return pick_objects(str, 1);
+}
+
+static int nehmen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts nehmen.\n");
+ return 0;
+ }
+
+ _notify_fail("WAS moechtest Du nehmen?\n");
+ return pick_objects(str, 0);
+}
+
+static int geben(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du nichts weggeben.\n");
+ return 0;
+ }
+
+ _notify_fail("WEM moechtest Du WAS geben?\n");
+ return give_objects(str);
+}
+
+static int zeigen(string str)
+{
+ if (QueryProp(P_GHOST)) {
+ _notify_fail("Als Geist kannst Du niemandem etwas zeigen.\n");
+ return 0;
+ }
+
+ _notify_fail("WEM moechtest Du WAS zeigen?\n");
+ return show_objects(str);
+}
+
+protected void add_put_and_get_commands(void)
+{
+ add_action("fallenlassen", "lass");
+ add_action("fallenlassen", "lasse");
+ add_action("werfen", "wirf");
+ add_action("werfen", "werf");
+ add_action("werfen", "werfe");
+ add_action("legen", "leg");
+ add_action("legen", "lege");
+ add_action("stecken", "steck");
+ add_action("stecken", "stecke");
+ add_action("holen", "hol");
+ add_action("holen", "hole");
+ add_action("nehmen", "nimm");
+ add_action("nehmen", "nehm");
+ add_action("nehmen", "nehme");
+ add_action("geben", "gebe");
+ add_action("geben", "gib");
+ add_action("zeigen", "zeig");
+ add_action("zeigen", "zeige");
+}
+
+
+/********** Aus reinen Kompatibilitaetsgruenden weiterhin enthalten *********/
+
+object* find_obs(string str, int meth)
+// gibt ein array zurueck mit allen Objekten die mit str angesprochen werden
+{
+ object inv;
+ if (!str) return 0;
+ if (str[<7..]==" in mir") {
+ inv=ME;
+ str=str[0..<8];
+ }
+ else if (str[<8..]==" in raum") {
+ if (meth & PUT_GET_DROP) { // man kann nichts aus dem Raum wegwerfen
+ _notify_fail("Du kannst nichts wegwerfen, das Du gar nicht hast.\n");
+ return 0;
+ }
+ inv=environment();
+ str=str[0..<9];
+ }
+ else if (meth & PUT_GET_DROP) inv=ME; // Raum bei drop uninteressant
+ // else kein besonderes inv ausgewaehlt also inv=0
+ if (!sizeof(str))
+ return 0; // hier passt die bereits gesetzte _notify_fail
+ else {
+ object *obs;
+ string con;
+ if (sscanf(str, "%s aus %s", str, con)==2 ||
+ sscanf(str, "%s in %s", str, con)==2 ||
+ sscanf(str, "%s von %s", str, con)==2 ||
+ sscanf(str, "%s vom %s", str, con)==2) {
+ if (!inv) {
+ if (!environment() || !(inv=present(con, environment())))
+ inv=present(con, ME); // sowohl im env als auch im inv suchen
+ }
+ else inv=present(con, inv); // nur in ausgewaehltem inv suchen
+ if (inv==ME) inv=0;
+ if (!inv || !(inv->short())) {
+ _notify_fail(break_string("Du hast hier aber kein '"+capitalize(con)
+ +"'.",78));
+ return 0;
+ }
+ if (living(inv)) {
+ _notify_fail(break_string("Aber "+inv->name(WER,1)+" lebt doch!",78));
+ return 0;
+ }
+ // wieso man aus Objekten die von std/tray abgeleitet werden etwas
+ // nehmen koennen soll, versteh ich zwar nicht so ganz...
+ if (!(inv->QueryProp(P_CONTAINER)) && !(inv->QueryProp(P_TRAY))) {
+ _notify_fail(break_string("Du kannst nichts aus "+inv->name(WEM,1)
+ +" nehmen.",78));
+ return 0;
+ }
+ if (inv->QueryProp(P_CNT_STATUS)) { // Container ist geschlossen
+ _notify_fail(break_string("Aber "+inv->name(WER,1)
+ +" ist doch geschlossen.", 78));
+ return 0;
+ }
+ }
+ else if (inv==ME && (meth & PUT_GET_TAKE)) { // nichts aus sich nehmen
+ _notify_fail("Du kannst nichts nehmen, "
+ "was Du schon bei Dir traegst.\n");
+ return 0;
+ }
+ if (!inv && (meth & PUT_GET_TAKE))
+ inv=environment(); // nichts nehmen was man schon hat
+
+ if (!inv) {
+ if (environment()) {
+ obs=(environment()->present_objects(str)||({}));
+ if (!sizeof(obs)) obs+=(ME->present_objects(str)||({}));
+ }
+ else obs=(ME->present_objects(str) || ({}));
+ }
+ else obs=(inv->present_objects(str) || ({}));
+ return obs-({ ME });
+ }
+ return(0);
+}
+
+int pick_obj(object ob)
+{
+ object env;
+
+ if (!ob || ob == this_object() || environment(ob) == this_object()) return 0;
+ if ((env=environment(ob)) != environment()) {
+ if (!env->QueryProp(P_CONTAINER) && !env->QueryProp(P_TRAY)) {
+ TME("Du kannst nichts aus " + env->name(WEM,1) + " nehmen.");
+ return 1;
+ }
+ else if (env->QueryProp(P_CNT_STATUS)) { // Container ist geschlossen
+ TME("Aber " + env->name(WER, 1) + " ist doch geschlossen.");
+ return 1;
+ }
+ }
+ if (ob->IsUnit() && ob->QueryProp(P_AMOUNT)<0) {
+ TME("Du kannst nicht mehr nehmen als da ist.");
+ return 1;
+ }
+ pick(ob);
+ return 1;
+}
+
+int drop_obj(object ob)
+{
+ if (!ob || ob==this_object() || environment(ob)!=this_object()) return 0;
+ drop(ob);
+ return 1;
+}
+
+int put_obj(object ob, object where)
+{
+ object env;
+
+ if (ob == this_object() || ob == where || environment(ob) == where) return 0;
+ env=environment(ob);
+ if (!where->QueryProp(P_CONTAINER)) {
+ TME("Du kannst in " + where->name(WEN,1) + " nix reinstecken.");
+ return 1;
+ }
+ if (where->QueryProp(P_CNT_STATUS)) { // Container ist geschlossen
+ TME("Aber " + where->name(WER, 1) + " ist doch geschlossen.");
+ return 1;
+ }
+ if (env!=environment(this_object()) && env!=this_object()) {
+ _notify_fail("Da kommst du so nicht ran.\n");
+ return 0;
+ }
+ put(ob, where);
+ return 1;
+}
+
+int give_obj(object ob, object where)
+{
+ object env;
+
+ if (environment(ob)!=this_object()) {
+ TME("Das solltest Du erstmal nehmen.");
+ return 1;
+ }
+ if (!ob || ob == this_object() || ob == where ||
+ environment(where)!=environment())
+ return 0;
+ if (environment(ob) == where) {
+ _notify_fail("Das Ziel ist in dem zu gebenden Object enthalten!\n");
+ return 0;
+ }
+ if (environment(ob)!=this_object()) {
+ TME("Das hast Du nicht.");
+ return 1;
+ }
+ give(ob, where);
+ return 1;
+}
diff --git a/std/living/skill_attributes.c b/std/living/skill_attributes.c
new file mode 100644
index 0000000..ad90aa0
--- /dev/null
+++ b/std/living/skill_attributes.c
@@ -0,0 +1,379 @@
+// MorgenGrauen MUDlib
+//
+// living/skills_attributes.c - Verwaltung der Skillattribute von Lebewesen
+//
+// $Id: skills.c 6673 2008-01-05 20:57:43Z Zesstra $
+#pragma strict_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/util/executer";
+
+#define NEED_PROTOTYPES
+#include <living/skill_attributes.h>
+#include <player/life.h>
+#include <thing/properties.h>
+#undef NEED_PROTOTYPES
+#include <thing/properties.h>
+#include <properties.h>
+#include <defines.h>
+
+//#define ZDEBUG(x) if (find_player("zesstra")) tell_object(find_player("zesstra"),x)
+#define ZDEBUG(x)
+//#define SASETLOG(x) log_file("SASET.LOG", x, 250000)
+
+//#define __DEBUG__
+
+// Variable fuer die Skill-Attribute, Datenstruktur:
+/* ([ SA_ATTR: ({Summe_Stat_Modifier, Zeitpunkt, AnzahlModifier, });
+ ([ ob1:value;duration,
+ ob2:value;duration, ...]); // stat. Modifier
+ ([ ob1:closure;duration,
+ ob2:closure;duration, ...]) // dyn. Modifier
+ ,
+ SA_ATTR2: ({...}); ([]); ([]),
+ ]) */
+private nosave mapping skillattrs;
+
+protected void create() {
+ Set(P_SKILL_ATTRIBUTES, SECURED, F_MODE_AS);
+}
+
+// von aussen Prop setzen ist nicht...
+mapping _set_skill_attr(mapping sa) {
+ return deep_copy(skillattrs);
+}
+// und auch beim Abfragen nur kopien liefern. ;-)
+mapping _query_skill_attr() {
+ return deep_copy(skillattrs);
+
+//TODO: Evtl. ext. Setzen von P_SKILL_ATTRIBUTE_OFFSETS mitloggen?
+}
+
+private void UpdateSACache(string *attrs) {
+#ifdef __DEBUG__
+ if (getuid(this_object()) == "zesstra")
+ ZDEBUG(sprintf("%O\n",skillattrs));
+#endif
+ if (!mappingp(skillattrs)) return;
+ if (!pointerp(attrs) || sizeof(attrs))
+ attrs = m_indices(skillattrs); // alle
+ // sonst schnittmenge aus existierenden und den uebergebenen.
+ attrs = m_indices(skillattrs) & attrs;
+
+ // und jetzt ueber alle gewuenschten SAs drueber
+ foreach(string attr : attrs) {
+ int *cache = skillattrs[attr, SAM_CACHE];
+ mapping stat = skillattrs[attr, SAM_STATIC];
+ mapping dyn = skillattrs[attr, SAM_DYNAMIC];
+ int sum = 0;
+ int timeout = __INT_MAX__;
+
+ // ueber stat. Mods iterieren und Aufsummieren, kleinsten Timeout
+ // ermitteln.
+ foreach(object ob, int value, int duration: stat) {
+ // gueltige Mods aufaddieren, abgelaufene rauswerfen
+ if (duration >= time()) {
+ sum += value;
+ if (duration < timeout)
+ timeout = duration;
+ }
+ else
+ m_delete(stat, ob); // ja, geht im foreach ;-)
+ }
+ // Ablaufzeiten der dyn. Mods pruefen, waere hier zwar nicht unbedingt
+ // noetig sondern koennte man ausschliesslich im QuerySkillAttribute()
+ // machen, aber dann waere die Ermittlung der Summe der Mods schwieriger.
+ foreach(object ob, closure value, int duration: dyn) {
+ if (duration < time())
+ m_delete(dyn, ob); // ungueltig, weg damit.
+ }
+ // gesamtzahl Mods?
+ cache[SAM_COUNT] = sizeof(stat) + sizeof(dyn);
+ if (!cache[SAM_COUNT]) {
+ // keine mods da, Submapping fuer dieses SA komplett loeschen.
+ m_delete(skillattrs, attr);
+ continue;
+ }
+ // sonst die anderen Cache-Werte setzen.
+ cache[SAM_SUM] = sum;
+ cache[SAM_CACHE_TIMEOUT] = timeout;
+ }
+ // wenn alle Mods geloescht wurden.
+ if (!sizeof(skillattrs)) skillattrs=0;
+#ifdef __DEBUG__
+ if (getuid(this_object()) == "zesstra")
+ ZDEBUG(sprintf("%O\n",skillattrs));
+#endif
+}
+
+private int InternalModifySkillAttribute(object caster, string atrname,
+ mixed value, int duration) {
+ int zeit = utime()[1];
+ int ticks = get_eval_cost();
+
+ // nur existierende SAs...
+ if (!stringp(atrname)
+ || member(VALID_SKILL_ATTRIBUTES, atrname) == -1)
+ return SA_MOD_INVALID_ATTR;
+
+ if (!objectp(caster)) return SA_MOD_INVALID_OBJECT;
+
+ if (!mappingp(skillattrs)) skillattrs=m_allocate(1,3);
+
+ if (!member(skillattrs, atrname)) {
+ skillattrs[atrname,SAM_CACHE] = ({0, 0, 0});
+ // die meisten Mods sind statisch, daher auf Verdacht hier fuer einen
+ // Eintrag Platz reservieren, aber nicht fuer den dyn. Teil.
+ skillattrs[atrname,SAM_STATIC] = m_allocate(1,2);
+ skillattrs[atrname,SAM_DYNAMIC] = m_allocate(0,2);
+ }
+ // pruefen, ob Maximalzahl an Eintraegen drin ist
+ else if (skillattrs[atrname,SAM_CACHE][SAM_COUNT] >= SAM_MAX_MODS) {
+ // letzte Chance: destructete Objekte drin?
+ skillattrs[atrname,SAM_CACHE][SAM_COUNT] =
+ sizeof(skillattrs[atrname,SAM_STATIC])
+ +sizeof(skillattrs[atrname,SAM_DYNAMIC]);
+ // es kann sein, dass noch abgelaufene Objekte drinstehen,
+ // aber die Pruefung ist mir gerade zu teuer. TODO
+ // nochmal gucken (rest vom cache wird ggf. unten geprueft.)
+ if (skillattrs[atrname,SAM_CACHE][SAM_COUNT] >= SAM_MAX_MODS)
+ return SA_TOO_MANY_MODS; // dann nicht.
+ }
+
+ // Dauer darf nur ein int sein.
+ if (!intp(duration))
+ raise_error(sprintf("Wrong argument 3 to ModifySkillAttribute: "
+ "expected 'int', got %.10O\n", duration));
+ // Zeitstempel ermitteln
+ duration += time();
+
+ // statischer oder dyn. Modifier?
+ if (intp(value)) {
+ // Mod darf nicht zu gross oder zu klein sein. TODO: Grenzen?
+ if (value < -1000)
+ return SA_MOD_TOO_SMALL;
+ else if (value > 1000)
+ return SA_MOD_TOO_BIG;
+ else if (!value)
+ return SA_MOD_INVALID_VALUE;
+ // jedes Objekt darf nur einen mod haben. Wenn dieses schon einen dyn.
+ // hat, muss der geloescht werden (stat. werden ja eh ersetzt).
+ if (member(skillattrs[atrname,SAM_DYNAMIC], caster))
+ m_delete(skillattrs[atrname,SAM_DYNAMIC], caster);
+ // sonst eintragen
+ skillattrs[atrname, SAM_STATIC] += ([caster: value; duration]);
+ }
+ else if (closurep(value)) {
+ // nur ein Mod pro Objekt, s.o.
+ if (member(skillattrs[atrname,SAM_STATIC], caster))
+ m_delete(skillattrs[atrname,SAM_STATIC], caster);
+ // direkt ohne weitere Pruefung eintragen
+ skillattrs[atrname, SAM_DYNAMIC] += ([caster: value; duration]);
+ }
+ else
+ raise_error(sprintf("Wrong argument 2 to ModifySkillAttribute(): "
+ "expected 'int' or 'closure', got %.10O\n",value));
+
+#ifdef SASETLOG
+ if (query_once_interactive(this_object()))
+ SASETLOG(sprintf("%s: %O, %s, %O, %O\n",
+ strftime("%y%m%d-%H%M%S"), this_object(), atrname, caster, value));
+#endif
+#ifdef SASTATD
+ object daemon;
+ if (query_once_interactive(ME)
+ && objectp(daemon=find_object(SASTATD)))
+ daemon->LogModifier(caster, atrname, value, duration);
+#endif
+ // noch den Cache fuer dieses SA neu berechnen
+ // TODO: Cache nur invalidieren, damit er erst bei der naechsten Abfrage
+ // aktualisiert wird. Spart Zeit, wenn bis dahin mehrere Mods
+ // entfernt/addiert werden. Dafuer ist die gecache Anzahl an Mods
+ // inkonsistent.
+ UpdateSACache( ({atrname}) );
+
+ ZDEBUG(sprintf("MSA: %O, Zeit: %d, Ticks: %d\n",
+ this_object(),
+ utime()[1]-zeit, ticks-get_eval_cost()));
+
+ return SA_MOD_OK;
+}
+
+public int ModifySkillAttribute(string atrname, mixed value,
+ int duration) {
+ return InternalModifySkillAttribute(
+ (extern_call()?previous_object():ME), atrname, value, duration);
+}
+
+public int RemoveSkillAttributeModifier(object caster, string attrname) {
+ if (!stringp(attrname) || !mappingp(skillattrs) || !objectp(caster)
+ || !member(skillattrs, attrname))
+ return SA_MOD_NOT_FOUND;
+ // TODO: Berechtigung pruefen. ;-)
+
+ if (member(skillattrs[attrname, SAM_STATIC], caster)) {
+ m_delete(skillattrs[attrname, SAM_STATIC], caster);
+ }
+ else if (member(skillattrs[attrname, SAM_DYNAMIC], caster)) {
+ m_delete(skillattrs[attrname, SAM_DYNAMIC], caster);
+ }
+ else
+ return SA_MOD_NOT_FOUND;
+
+ // TODO: Cache nur invalidieren, damit er erst bei der naechsten Abfrage
+ // aktualisiert wird. Spart Zeit, wenn bis dahin mehrere Mods
+ // entfernt/addiert werden. Dafuer ist die gecache Anzahl an Mods
+ // inkonsistent.
+ UpdateSACache( ({attrname}) );
+
+ return SA_MOD_REMOVED;
+}
+
+public int QuerySkillAttribute(string atrname)
+{
+ mixed offsets, attr;
+ int modsumme, qual, ret, cval;
+
+ if (!stringp(atrname)) // VALID_SKILL_ATTRIBUTES beruecksichtigen?
+ return 100;
+
+ // wenn nicht SA_QUALITY gefragt ist, erstmal jenes ermitteln, weil es den
+ // ersten Modifier auf alle anderen SAs darstellt. Sonst den Startwert fuer
+ // SA_QUALITY (100) modifiziert durch evtl. Todesfolgen ermitteln.
+ if ( atrname != SA_QUALITY )
+ qual = QuerySkillAttribute(SA_QUALITY);
+ else
+ // bei SA_QUALITY gehen die Todesfolgen ein
+ qual = to_int(100 * pow(0.9, death_suffering()/10.0));
+
+ // Die Offsets sind sozusagen der Basiswert der SAs. Als erstes verwursten,
+ // sofern vorhanden, nen int drinsteht und der offset != 0 ist.
+ if ( mappingp(offsets = Query(P_SKILL_ATTRIBUTE_OFFSETS))
+ && intp(attr=offsets[atrname])
+ && attr)
+ ret = attr;
+ else
+ ret = 100;
+
+ // wenn keine Mods gesetzt sind, wars das jetzt. ;-)
+ if ( !mappingp(skillattrs)
+ || !member(skillattrs, atrname) )
+ return (ret*qual)/100;
+
+ // wenn Cache der stat. Mods abgelaufen oder offenbar Objekte zerstoert
+ // wurden, muss der Cache neu berechnet werden.
+ if ( skillattrs[atrname,SAM_CACHE][SAM_CACHE_TIMEOUT] < time()
+ || sizeof(skillattrs[atrname,SAM_STATIC])
+ +sizeof(skillattrs[atrname,SAM_DYNAMIC]) !=
+ skillattrs[atrname,SAM_CACHE][SAM_COUNT] )
+ {
+ UpdateSACache( ({atrname}) );
+ // UpdateSACache() loescht uU das SA-Mapping oder Eintraege daraus.
+ if ( !mappingp(skillattrs)
+ || !member(skillattrs, atrname) )
+ return (ret*qual)/100;
+ }
+ // Summe der statischen Mods.
+ modsumme = skillattrs[atrname,SAM_CACHE][SAM_SUM];
+
+ // TODO! Evtl. andere Daten als ME an die Funktion uebergeben
+ // TODO! bereits nach Addition des Funktionsrueckgabewertes pruefen, ob der
+ // Wertebereich des SA-Modifiers ueberschritten ist, oder freien
+ // Wertebereich erlauben und erst am Ende deckeln (aktuelle Variante)
+ // Dynamische Modifier auswerten
+ foreach( object ob, closure cl, int duration:
+ skillattrs[atrname,SAM_DYNAMIC] )
+ {
+ if ( duration > time() // Noch nicht abgelaufen und
+ && intp(cval=funcall(cl, ME)) ) // Funktion liefert int zurueck
+ modsumme += cval;
+ else {
+ m_delete(skillattrs[atrname,SAM_DYNAMIC], ob);
+ skillattrs[atrname,SAM_CACHE][SAM_COUNT]--;
+ }
+ }
+ ret = ((ret+modsumme)*qual)/100;
+ if ( ret < 10 )
+ ret = 10;
+ else if ( ret > 1000 )
+ ret = 1000;
+
+ return ret;
+}
+
+public varargs mapping QuerySkillAttributeModifier(object caster,
+ string *attrnames) {
+
+ // auf abgelaufene Modifikatoren pruefen
+ if (!pointerp(attrnames))
+ UpdateSACache( ({}) );
+ else
+ UpdateSACache(attrnames);
+
+ if (!mappingp(skillattrs))
+ return ([]);
+ if (!pointerp(attrnames) || !sizeof(attrnames))
+ attrnames = m_indices(skillattrs); // alle durchsuchen
+ else // schnittmenge der gew. und vorhandenen bilden
+ attrnames = m_indices(skillattrs) & attrnames;
+
+ mapping res=m_allocate(sizeof(attrnames), 1);
+
+ foreach(string atr: attrnames) {
+ res[atr] = m_allocate(5, 2); // mal fuer 5 Werte Platz reservieren
+ if (!objectp(caster)) {
+ // wenn kein bestimmter caster angefragt ist, alle mods liefern
+ foreach(object c, int value, int dur: skillattrs[atr, SAM_STATIC]) {
+ res[atr] += ([c: value; dur]);
+ }
+ foreach(object c, closure value, int dur: skillattrs[atr, SAM_DYNAMIC]) {
+ res[atr] += ([c: value; dur]);
+ }
+ }
+ else {
+ // sonst nur den Mod von caster
+ if (member(skillattrs[atr, SAM_STATIC], caster)) {
+ res[atr] += ([caster:
+ skillattrs[atr, SAM_STATIC][caster, SAM_VALUE];
+ skillattrs[atr, SAM_STATIC][caster, SAM_DURATION]
+ ]);
+ }
+ else if (member(skillattrs[atr, SAM_DYNAMIC], caster)) {
+ res[atr] += ([caster:
+ skillattrs[atr, SAM_DYNAMIC][caster, SAM_VALUE];
+ skillattrs[atr, SAM_DYNAMIC][caster, SAM_DURATION]
+ ]);
+ }
+ }
+ }
+ return res;
+}
+
+// Kompatibilitaetsfunktion mit altem Interface. Ist nur ein Wrapper, der
+// value umrechnet und 'alte' Rueckgabewerte liefert.
+
+public varargs int ModifySkillAttributeOld(object caster, string atrname,
+ int value, int duration, mixed fun) {
+ int res;
+ // Caller ermitteln
+ if (extern_call()) caster=previous_object();
+ else caster=ME;
+
+ // Closures koennen via ModifySkillAttributeOld() nicht mehr gesetzt werden,
+ // da deren Rueckgabewert nicht sinnvoll umgerechnet werden koennen. (Man
+ // weiss nicht, ob es eine neue oder alte Closure ist.)
+ if (pointerp(fun) || closurep(fun))
+ raise_error(sprintf("Closures for SA modifiers can't be set by "
+ "ModifySkillAttributeOld()! Use ModifySkillAttribute()!\n"));
+
+ res = InternalModifySkillAttribute(caster, atrname, value-100, duration);
+ // die alte funktion hatte nur 0 fuer ungueltigen Wert und < 0 fuer zu
+ // kleines Level als Rueckgabewert. Zu kleines Level gibt nicht mehr, also
+ // bleibt nur 0 als Sammel-Fehlercode uebrig. *seufz*
+ if (res < 0) return 0;
+ return res;
+}
+
diff --git a/std/living/skill_utils.c b/std/living/skill_utils.c
new file mode 100644
index 0000000..24caf75
--- /dev/null
+++ b/std/living/skill_utils.c
@@ -0,0 +1,35 @@
+// MorgenGrauen MUDlib
+//
+// living/skill_utils -- some helper functions for manipulating skill data
+// needed in more than one program.
+//
+// $Id: skill_utils.c 6738 2008-02-19 18:46:14Z Humni $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#include <new_skills.h>
+
+protected void SkillResTransfer(mapping from_M, mapping to_M)
+{
+ if ( !mappingp(from_M) || !mappingp(to_M) )
+ return;
+
+ if ( member(from_M,SI_SKILLDAMAGE) )
+ to_M[SI_SKILLDAMAGE] = to_int(from_M[SI_SKILLDAMAGE]);
+
+ if ( member(from_M,SI_SKILLDAMAGE_MSG) )
+ to_M[SI_SKILLDAMAGE_MSG] = to_string(from_M[SI_SKILLDAMAGE_MSG]);
+
+ if ( member(from_M,SI_SKILLDAMAGE_MSG2) )
+ to_M[SI_SKILLDAMAGE_MSG2] = to_string(from_M[SI_SKILLDAMAGE_MSG2]);
+
+ if ( member(from_M,SI_SKILLDAMAGE_TYPE) )
+ to_M[SI_SKILLDAMAGE_TYPE] = from_M[SI_SKILLDAMAGE_TYPE];
+
+ if ( member(from_M,SI_SPELL) )
+ to_M[SI_SPELL] = from_M[SI_SPELL];
+}
+
diff --git a/std/living/skills.c b/std/living/skills.c
new file mode 100644
index 0000000..93c9dcc
--- /dev/null
+++ b/std/living/skills.c
@@ -0,0 +1,595 @@
+// MorgenGrauen MUDlib
+//
+// living/skills.c -- Gilden-, Skill- und Spellfunktionen fuer Lebewesen
+//
+// $Id: skills.c 8755 2014-04-26 13:13:40Z Zesstra $
+#pragma strict_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+inherit "/std/living/std_skills";
+
+#define NEED_PROTOTYPES
+#include <living/skills.h>
+#include <living/skill_attributes.h>
+#include <player/life.h>
+#undef NEED_PROTOTYPES
+
+#include <thing/properties.h>
+#include <properties.h>
+#include <new_skills.h>
+#include <wizlevels.h>
+
+// speichert die Spell-Fatigues (global, Spruchgruppen, Einzelsprueche)
+private mapping spell_fatigues = ([]);
+
+// Prototypen
+private void expire_spell_fatigues();
+
+protected void create()
+{
+ // mainly necessary for players, but there may be some NPC with savefiles.
+ // Additionally, it simplifies expiration of old keys to have it here.
+ call_out(#'expire_spell_fatigues, 4);
+}
+
+
+// Diese - hier scheinbar sinnlose - Funktion wird von /std/player/skills.c dann
+// ueberladen.
+public int is_deactivated_skill(string sname, string gilde)
+{
+ return 0;
+}
+
+
+
+static string _query_visible_guild()
+{ string res;
+
+ if ( stringp(res=Query(P_VISIBLE_GUILD)) )
+ return res;
+
+ return QueryProp(P_GUILD);
+}
+
+static string _query_visible_subguild_title()
+{ string res;
+
+ if ( stringp(res=Query(P_VISIBLE_SUBGUILD_TITLE)) )
+ return res;
+
+ return QueryProp(P_SUBGUILD_TITLE);
+}
+
+static mixed _query_guild_prepareblock()
+{ mapping res;
+ string gilde;
+
+ if ( !stringp(gilde=QueryProp(P_GUILD)) )
+ return 0;
+ if ( !mappingp(res=Query(P_GUILD_PREPAREBLOCK))
+ || !member(res,gilde) )
+ return 0;
+ return res[gilde];
+}
+
+static mixed _set_guild_prepareblock(mixed arg)
+{ mapping res;
+ string gilde;
+
+ if ( !stringp(gilde=QueryProp(P_GUILD)) )
+ return 0;
+ if ( !mappingp(res=Query(P_GUILD_PREPAREBLOCK)) )
+ res=([]);
+
+ res[gilde]=arg;
+ Set(P_GUILD_PREPAREBLOCK,res);
+
+ return arg;
+}
+
+
+private nosave int valid_setskills_override;
+// Man sollte eigentlich ja nicht Parameter als globale Variablen
+// uebergeben, aber hier ging es nicht anders
+nomask private int valid_setskills(string gilde)
+{ string fn;
+
+ if ( !query_once_interactive(this_object()) )
+ return 1; // Monster duerfen sich selber Skills setzen :)
+
+ if ( QueryProp(P_TESTPLAYER) || IS_WIZARD(this_object()) )
+ return 1; // Testspieler und Magier sind schutzlose Opfer ;-)
+
+ if ( previous_object() )
+ {
+ if ( previous_object()==this_object()
+ && this_interactive()==this_object() )
+ return 1;
+
+ fn=object_name(previous_object());
+ if ( fn[0..7]=="/gilden/"
+ || fn[0..11]=="/spellbooks/"
+ || fn[0..7]=="/secure/"
+ || fn[0..11]=="/p/zauberer/" )
+ return 1; // Die sollten problemlos aendern duerfen
+
+ if ( file_size("/gilden/access_rights")>0
+ && call_other("/gilden/access_rights",
+ "access_rights",
+ getuid(previous_object()),
+ gilde+".c"))
+ return 1; // Setzendes Objekt kommt vom Gildenprogrammierer
+
+ if ( file_size("/gilden/"+gilde+".c")>0
+ && call_other("/gilden/"+gilde,
+ "valid_setskills",
+ explode(fn,"#")[0]) )
+ return 1; // Die Gilde selber kann Ausnahmen zulassen
+ }
+
+ if (valid_setskills_override)
+ {
+ valid_setskills_override=0;
+ return 1; // Fuers Setzen der Closure
+ }
+
+ if ( this_interactive() )
+ {
+ if ( IS_ARCH(this_interactive()) )
+ return 1; // Erzmagier duerfen immer aendern
+
+ if ( call_other("/gilden/access_rights",
+ "access_rights",
+ getuid(this_interactive()),
+ gilde+".c"))
+ return 1; // Der Gildenprogrammierer selber auch
+ }
+
+ // Fuer die Waffenskills, die sollen sich selbst auch setzen duerfen
+ if (!this_interactive() && this_object()==previous_object())
+ return 1;
+
+
+ log_file("SETSKILLS",sprintf("*****\n%s PO:%O->TO:%O TI:%O\n GUILD:%s VERB_ARGS:'%s'\n",
+ ctime(time())[4..15],
+ previous_object(),
+ this_object(),
+ this_interactive(),
+ gilde,
+ ( this_interactive() ? query_verb() + " " +
+ this_interactive()->_unparsed_args() : "") ));
+
+ return 0;
+}
+
+// Nur interne Verwendung, value wird nicht weiter prueft, muss ok sein.
+// Es wird keine Kopie von value gemacht, wenn es ins Mapping geschrieben
+// wird!
+private mapping internal_set_newskills(mapping value, string gilde) {
+ mapping skills;
+
+ // in der richtigen Gilde setzen.
+ if ( !gilde && !(gilde=QueryProp(P_GUILD)) )
+ gilde="ANY";
+
+ // Query(), hier ist eine Kopie nicht sinnvoll.
+ if ( !mappingp(skills=Query(P_NEWSKILLS,F_VALUE)) ) {
+ skills=([]);
+ Set(P_NEWSKILLS, skills, F_VALUE);
+ }
+
+ // Falls dies hier mal ausgewertet werden sollte, nicht vergessen, dass
+ // einige Funktion hier im File die Prop evtl. via
+ // internal_query_newskills() abrufen und direkt aendern...
+ valid_setskills(gilde); // Sicherheitsueberpruefung
+
+ // Skills setzen. Set() unnoetig, weil wir das von Query() gelieferte
+ // Mapping aendern und das ja via Referenz bekommen haben.
+ skills[gilde]=value;
+ //Set(P_NEWSKILLS,skills);
+
+ return(value);
+}
+
+// nur zur internen Verwendung, es wird keine Kopie des Skillmappings gemacht!
+private mapping internal_query_newskills(string gilde) {
+ mapping skills;
+
+ // richtige Gilde abfragen.
+ if ( !gilde && !(gilde=QueryProp(P_GUILD)) )
+ gilde="ANY";
+
+ skills=Query(P_NEWSKILLS);
+
+ if (!mappingp(skills) || !mappingp(skills=skills[gilde]) )
+ return ([]);
+
+ return(skills);
+}
+
+// Eigentlich sollte man den _query-Funktionen keine Parameter geben...
+static varargs mapping _query_newskills(string gilde) {
+
+ // sonst Kopie des spellmappings liefern! Kostet zwar, aber verhindert
+ // einige andere Bugs und versehentliche Aenderungen an den Skills!
+ return(deep_copy(internal_query_newskills(gilde)));
+}
+
+// Eigentlich sollte man den _set-Funktionen keine weiteren Parameter geben
+static varargs mapping _set_newskills(mapping value, string gilde) {
+
+ // value auf Mappings normalisieren, ggf. Kopieren
+ if ( !mappingp(value) )
+ value=([]);
+ else
+ //zur Sicherheit, wer weiss, was der setzende noch damit macht...
+ value=deep_copy(value);
+
+ // und setzen...
+ internal_set_newskills(value, gilde);
+
+ // und noch ne Kopie von dem Liefern, was wir gesetzt haben (keine Referenz,
+ // sonst koennte der Aufrufende ja noch im Nachhinein aendern).
+ return(_query_newskills(gilde));
+}
+
+private mapping InternalQuerySkill(string sname, string gilde) {
+ mixed skill, skills;
+ // In is_any wird gespeichert, ob es ein gildenunabhaengier Skill ist,
+ // fuer die is_deactivate_skill-Abfrage.
+ int is_any;
+
+ // Skills komplett abfragen, keine spez. Gilde
+ if (!mappingp(skills=Query(P_NEWSKILLS,F_VALUE)))
+ return 0;
+
+ if (stringp(gilde) && sizeof(gilde)) {
+ //bestimmte Gilde angegeben, gut, dort gucken.
+ if (mappingp(skills[gilde]))
+ skill=skills[gilde][sname];
+ }
+ else {
+ gilde=QueryProp(P_GUILD); //reale Gilde holen
+ if (gilde && mappingp(skills[gilde]) &&
+ (skill=skills[gilde][sname])) {
+ // gibt es den Spell in der Gilde des Spielers?
+ // dann hier nix machen...
+ }
+ else if (mappingp(skills["ANY"])) {
+ // Zum Schluss: Gibt es den Skill vielleicht Gildenunabhaengig?
+ skill=skills["ANY"][sname];
+ // wenn man hier reinkommt, dann spaeter mit is_deactivated_skill()
+ // pruefen!
+ is_any=1;
+ }
+ }
+
+ // wenn kein Skill gefunden, mit 0 direkt raus
+ if (!skill) return 0;
+
+ // Bei gildenunabhaengigen auch im Skillmapping vermerken
+ if ( is_any ) {
+ skill+=([SI_GUILD:"ANY"]);
+ // Ist er vielleicht in der Gilde des Spielers deaktiviert?
+ // Dies kann nur der Fall sein, wenn es kein Gildenskill ist.
+ if (is_deactivated_skill(sname,gilde)) {
+ return 0;
+ }
+ }
+
+ return(skill);
+}
+
+public varargs mapping QuerySkill(string sname, string gilde) {
+
+ if (!stringp(sname) || !sizeof(sname))
+ return 0;
+
+ //Kopie zurueckliefern
+ return(deep_copy(InternalQuerySkill(sname,gilde)));
+}
+
+#define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y))
+public varargs int QuerySkillAbility(string sname, string gilde)
+{ mapping skill;
+ string skill2;
+
+ if ( !(skill=InternalQuerySkill(sname, gilde)) )
+ return 0;
+
+ int val=skill[SI_SKILLABILITY];
+
+ if (skill2=skill[SI_INHERIT])
+ {
+ int val2;
+ val2=QuerySkillAbility(skill2);
+ val=(val*MAX_ABILITY+SMUL(val,val2))/(2*MAX_ABILITY);
+ }
+
+ return val;
+}
+
+protected varargs mixed LimitAbility(mapping sinfo, int diff)
+{ mixed abil;
+ int max,old,d2;
+
+ abil=sinfo[SI_SKILLABILITY];
+
+ if ( !intp(abil) )
+ return sinfo;
+ old=abil;
+
+ // Beim Spieler eingetragene Schwierigkeit gilt vor angegebener.
+ if ( (d2=sinfo[SI_DIFFICULTY]) )
+ diff=d2;
+
+ // diff <-100 soll nicht hemmen und macht keinen Sinn
+ diff=(diff<(-100))?(-100):diff;
+
+ max=MAX_ABILITY-(diff+100)*(35-QueryProp(P_LEVEL));
+
+// diff|lvl 1:| 3:| 7:| 10:| 13:| 16:| 19:| 22:| 25:| 28:| 31:| 34:|
+// ----+------+-----+-----+----+----+----+----+----+----+----+----+----+
+// -50| 83%| 84%| 86%| 87%| 89%| 90%| 92%| 93%| 95%| 96%| 98%| 99%|
+// -10| 69%| 72%| 74%| 77%| 80%| 82%| 85%| 88%| 91%| 93%| 96%| 99%|
+// 0| 66%| 69%| 72%| 75%| 78%| 81%| 84%| 87%| 90%| 93%| 96%| 99%|
+// 10| 62%| 65%| 69%| 72%| 75%| 79%| 82%| 85%| 89%| 92%| 95%| 98%|
+// 20| 59%| 62%| 66%| 70%| 73%| 77%| 80%| 84%| 88%| 91%| 95%| 98%|
+// 30| 55%| 59%| 63%| 67%| 71%| 75%| 79%| 83%| 87%| 90%| 94%| 98%|
+// 40| 52%| 56%| 60%| 65%| 69%| 73%| 77%| 81%| 86%| 90%| 94%| 98%|
+// 50| 49%| 53%| 58%| 62%| 67%| 71%| 76%| 80%| 85%| 89%| 94%| 98%|
+// 100| 32%| 38%| 44%| 50%| 56%| 62%| 68%| 74%| 80%| 86%| 92%| 98%|
+// 150| 15%| 22%| 30%| 37%| 45%| 52%| 60%| 67%| 75%| 82%| 90%| 97%|
+// 200| -2%| 7%| 16%| 25%| 34%| 43%| 52%| 61%| 70%| 79%| 88%| 97%|
+// 250| -19%| -8%| 2%| 12%| 23%| 33%| 44%| 54%| 65%| 75%| 86%| 96%|
+// 300| -36%| -24%| -12%| 0%| 12%| 24%| 36%| 48%| 60%| 72%| 84%| 96%|
+// 400| -70%| -55%| -40%|-25%|-10%| 5%| 20%| 35%| 50%| 65%| 80%| 95%|
+// 500| -104%| -86%| -68%|-50%|-32%|-14%| 4%| 22%| 40%| 58%| 76%| 94%|
+// 600| -138%|-117%| -96%|-75%|-54%|-33%|-12%| 9%| 30%| 51%| 72%| 93%|
+
+ if ( abil>max )
+ abil=max;
+ if ( abil>MAX_ABILITY
+ ) abil=MAX_ABILITY;
+ else if ( abil<-MAX_ABILITY )
+ abil=-MAX_ABILITY;
+
+ if ( old && !abil )
+ abil=1;
+ // Faehigkeiten sollen nicht durch die Begrenzung verschwinden
+
+ sinfo[SI_SKILLABILITY]=abil;
+
+ return sinfo;
+}
+
+public varargs void ModifySkill(string sname, mixed val, int diff, string gilde)
+{
+ mapping skills;
+ mixed skill;
+
+ if ( !stringp(sname) || !sizeof(sname) )
+ return;
+
+ // internal_query_newskills() macht keine Kopie
+ skills=internal_query_newskills(gilde);
+
+ // Skill ermitteln, wenn nicht existiert, wird er angelegt.
+ if (!skill=skills[sname]) {
+ skill=([]);
+ }
+
+ // Zur Sicherheit mal das Mapping kopieren, wer weiss, was der
+ // Aufrufende dieser Funktion selber spaeter damit noch macht.
+ // ist ok, wenn val kein Mapping ist, dann macht deep_copy nix.
+ val=deep_copy(val);
+
+ // Skill und val vereinigen
+ if ( mappingp(val) )
+ skill+=val;
+ else if (intp(val))
+ skill[SI_SKILLABILITY]=val;
+ else
+ raise_error(sprintf("Bad arg 2 to ModifySkill(): expected 'int', "
+ "got %.10O.\n",val));
+
+ // Lernen entsprechend SI_DIFFICULTY begrenzen.
+ if(diff && !member(skill,SI_DIFFICULTY))
+ skill[SI_DIFFICULTY]=diff;
+ skill=LimitAbility(skill,diff || skill[SI_DIFFICULTY]);
+
+ // schliesslich im Skillmapping vermerken. Im Normalfall ist der Skill jetzt
+ // schon geaendert, nicht erst nach dem internal_set_newskills().
+ skills[sname]=skill;
+
+ // explizites Abspeichern fast ueberfluessig, weil wir oben eine Referenz
+ // auf das Skillmapping gekriegt haben...
+ // Aber es koennte sein, dass dies der erste Skill fuer diese Gilde ist,
+ // dann ist es noetig. Zum anderen wird internal_set_newskills() nochmal
+ // geloggt.
+ internal_set_newskills(skills,gilde);
+}
+
+public varargs void LearnSkill(string sname, int add, int diff)
+{ mapping skill;
+ string skill2,gilde;
+ int val;
+
+ // Spieler sollen nur lernen, wenn sie interactive sind. Das soll
+ // natuerlich nur fuer Spieler gelten.
+ if (query_once_interactive(this_object()) && !interactive())
+ return;
+
+ if ( add>MAX_SKILLEARN )
+ add=MAX_SKILLEARN;
+ else if ( add<1 )
+ add=1;
+
+ // Skillmapping ermitteln (hier kommt keine Kopie zurueck)
+ skill=InternalQuerySkill(sname, 0);
+ // wenn kein Skill, dann Abbruch
+ if (!skill) return;
+
+ val=skill[SI_SKILLABILITY];
+ gilde=skill[SI_GUILD];
+
+ val+=add;
+
+ ModifySkill(sname,val,diff,gilde);
+ if ( skill2=skill[SI_INHERIT] )
+ LearnSkill(skill2,add/3,diff);
+}
+
+public varargs int UseSpell(string str, string spell)
+{ string gilde,sbook;
+ mapping sinfo;
+ closure cl;
+
+ if ( !spell && !(spell=query_verb()) )
+ return 0;
+
+ spell=lower_case(spell);
+
+ // QuerySkill() liefert eine Kopie des Skillmappings.
+ // wenn skill unbekannt oder Ability <= 0, ist der Spell nicht nutzbar.
+ if ( !(sinfo=QuerySkill(spell,0))
+ || sinfo[SI_SKILLABILITY] <= 0 )
+ return 0;
+
+ sinfo[SI_SKILLARG]=str; // Argument eintragen
+
+ if ( !closurep(cl=sinfo[SI_CLOSURE]) )
+ {
+ // Wenn ein Spellbook angegeben ist wird der Spell direkt ausgefuehrt
+ if ( stringp(sbook=sinfo[SI_SPELLBOOK]) )
+ cl=symbol_function("UseSpell",SPELLBOOK_DIR+sbook);
+
+ // Wenn der Spieler in einer Gilde ist, so weiss diese, in welchem
+ // Spellbook der Spell zu finden ist...
+ else if ( (gilde=QueryProp(P_GUILD)) &&
+ ( find_object(GUILD_DIR+gilde) || file_size(GUILD_DIR+gilde+".c")>-1))
+ cl=symbol_function("UseSpell",GUILD_DIR+gilde);
+ else
+ cl=function int () {return 0;};
+
+ sinfo[SI_CLOSURE]=cl;
+ valid_setskills_override=1;
+ ModifySkill(spell,([SI_CLOSURE:cl]),0,sinfo[SI_GUILD]);
+ valid_setskills_override=0;
+ }
+ return funcall(cl,this_object(),spell,sinfo);
+}
+
+public varargs mixed UseSkill(string skill, mapping args)
+{ mapping sinfo;
+ string gilde, func,skill2;
+ mixed res;
+ closure cl;
+
+ if ( !skill ||
+ QueryProp(P_GHOST))
+ return 0;
+
+ skill=capitalize(skill);
+ // QuerySkill() liefert eine Kopie des Skillmappings
+ if ( !(sinfo=QuerySkill(skill,0)) )
+ return 0;
+
+ if (args)
+ sinfo+=args;
+
+ if ( !closurep(cl=sinfo[SI_CLOSURE]) )
+ {
+ if ( !(func=sinfo[SI_SKILLFUNC]) // Keine Funktion angegeben?
+ || !(gilde=QueryProp(P_GUILD))) // Keine Gilde angegeben?
+ {
+ // Dann Standard-Funktion nehmen, wenn es die nicht gibt, den
+ // Ability-Wert zurueckliefern.
+ if (!closurep(cl = symbol_function("StdSkill_"+skill,this_object())))
+ cl=function int (object ob, string sname)
+ {return QuerySkillAbility(sname);} ;
+ }
+ else
+ {
+ // Sonst diese Funktion im Gildenobjekt aufrufen
+ cl=symbol_function(func,GUILD_DIR+gilde);
+ }
+
+ sinfo[SI_CLOSURE]=cl;
+ valid_setskills_override=1;
+ ModifySkill(skill,([SI_CLOSURE:cl]),0,sinfo[SI_GUILD]);
+ valid_setskills_override=0;
+ }
+
+ res=funcall(cl,this_object(),skill,sinfo);
+ if ( (skill2=sinfo[SI_INHERIT]) && mappingp(res) )
+ res=UseSkill(skill2,res); // Fuer Skills, die von anderen abhaengen
+
+ return res;
+}
+
+// ************** Spellfatigues ***************
+
+/* Prueft die Spellfatigue fuer Spruch(gruppe) <key>.
+ * <key> darf 0 sein (globale Spruchsperre).
+ * Liefert 0, wenn keine Sperre und die Ablaufzeit, wenn eine Sperre noch
+ * gueltig. ist.
+ */
+public varargs int CheckSpellFatigue(string key) {
+ // key==0 is the (default) global spellfatigue.
+ if (spell_fatigues[key] > time())
+ return spell_fatigues[key]; // Ablaufzeit zurueckgeben.
+
+ return 0; // ok, keine Sperre.
+}
+
+/** Speichert eine Spellfatigue von <duration> Sekunden fuer <key>.
+ * <key> darf 0 sein und bezeichnet das globale Spellfatigue.
+ * Rueckgabewert: Ablaufzeit der gesetzten Sperre
+ -1, wenn noch eine nicht-abgelaufene Sperre auf dem <key> lag.
+ 0, wenn duration 0 ist.
+ */
+public varargs int SetSpellFatigue(int duration, string key) {
+ // aktuelle Sperre abgelaufen?
+ if (CheckSpellFatigue(key))
+ return -1; // alte Sperre noch aktiv.
+
+ duration += time();
+ // 0 is OK for <key>, it is the key for global spell fatigues
+ spell_fatigues[key] = duration;
+ return duration;
+}
+
+/* Prueft die Spellfatigue fuer Spruch(gruppe) <key>.
+ * <key> darf fuer diese Funktion 0 (globale Spruchsperre) sein, aber man
+ * darf das Argument nicht weglassen, damit nicht ein verpeilter Magier
+ * versehentlich die globale Spruchsperre nullt.
+ */
+public void DeleteSpellFatigue(string key) {
+ // key==0 is the (default) global spellfatigue.
+ m_delete(spell_fatigues, key);
+}
+
+/** Loescht abgelaufene Keys aus dem spell_fatigue mapping.
+ */
+private void expire_spell_fatigues() {
+ foreach(string key, int endtime: spell_fatigues) {
+ if (endtime <= time())
+ m_delete(spell_fatigues, key);
+ }
+}
+
+/** Setmethode fuer P_NEXT_SPELL_TIME.
+ */
+static int _set_next_spell(int fatigue) {
+ return SetSpellFatigue(fatigue - time());
+}
+/** Querymethode fuer P_NEXT_SPELL_TIME.
+ */
+static int _query_next_spell() {
+ return CheckSpellFatigue();
+}
+
diff --git a/std/living/std_skills.c b/std/living/std_skills.c
new file mode 100644
index 0000000..d132efc
--- /dev/null
+++ b/std/living/std_skills.c
@@ -0,0 +1,104 @@
+// MorgenGrauen MUDlib
+//
+// living/std_skills.c -- Standardfaehigkeiten
+//
+// $Id: std_skills.c 6673 2008-01-05 20:57:43Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+#include <living/skills.h>
+#include <living/skill_attributes.h>
+#include <thing/properties.h>
+#include <attributes.h>
+#undef NEED_PROTOTYPES
+#include <properties.h>
+#include <new_skills.h>
+
+#define SIG(x) (x?(x>0?1:-1):0)
+
+protected int StdSkill_Nightvision(object me, string sname, mixed sinfo) {
+ int abil,light,llt,dt,res;
+
+ if (!sinfo || !environment()) return 0;
+ if (intp(sinfo)) sinfo=([SI_SKILLABILITY:sinfo]);
+ if (!mappingp(sinfo)) return 0;
+ if ((light=QueryProp(P_PLAYER_LIGHT))>0) {
+ ModifySkill(sname,([SI_LASTLIGHT:time()]));
+ return light;
+ }
+ abil=sinfo[SI_SKILLABILITY];
+ if (!(llt=sinfo[SI_LASTLIGHT])) {
+ ModifySkill(sname,([SI_LASTLIGHT:time()]));
+ dt=0;
+ } else {
+ dt=time()-llt;
+ if (dt<0) dt=0;
+ if (dt>86400) dt=86400;
+ }
+
+ res=(abil*dt)/(20*MAX_ABILITY)+light;
+ if (res<=0) {
+ res--; // Wert muss !=0 sein
+ if (res<-MAX_ABILITY) res=-MAX_ABILITY;
+ } else {
+ if (res>MAX_ABILITY) res=MAX_ABILITY;
+ }
+ return res;
+}
+
+protected mapping StdSkill_Bihand(object me, string sname, mapping sinfo) {
+ int abil,val;
+
+ // printf("Bihand: %O\n",sinfo);
+ if (!sinfo) return 0;
+ abil=sinfo[SI_SKILLABILITY];
+ val=(abil*(QueryAttribute(A_STR)+33))/MAX_ABILITY;
+ val=(val*QuerySkillAttribute(SA_DAMAGE))/100;
+ sinfo[SI_SKILLDAMAGE]+=val;
+ // + max. 53
+ return sinfo;
+}
+
+protected mapping StdSkill_Fight_sword(object me, string sname, mapping sinfo) {
+ int abil,asig,val;
+
+ abil=sinfo[SI_SKILLABILITY];asig=SIG(abil);
+ val=(abil*(QueryAttribute(A_STR) +
+ QueryAttribute(A_DEX)*asig +
+ 33))/MAX_ABILITY;
+ val=(val*QuerySkillAttribute(SA_DAMAGE))/100;
+ sinfo[SI_SKILLDAMAGE]+=val;
+ // + max. 73
+ return sinfo;
+}
+
+protected mapping StdSkill_Fight_hands(object me, string sname, mapping sinfo) {
+ int abil,asig,val;
+
+ if (!sinfo) return 0;
+ abil=sinfo[SI_SKILLABILITY];asig=SIG(abil);
+ val=(abil*(QueryAttribute(A_STR) +
+ QueryAttribute(A_DEX)*3*asig +
+ 100))/MAX_ABILITY;
+ val=(val*QuerySkillAttribute(SA_DAMAGE))/100;
+ sinfo[SI_SKILLDAMAGE]+=val;
+ // + max. 180
+ return sinfo;
+}
+
+protected int StdSkill_Booze(object me, string sname, mapping sinfo) {
+ int abil,val;
+
+ val=0;
+ if (!sinfo || (val=sinfo[SI_SKILLARG])<=0)
+ return val;
+ abil=sinfo[SI_SKILLABILITY];
+ val-=(val*abil)/(MAX_ABILITY+2500); // Bis zu 80% Abzug bei Alkoholikern.
+ if (val<=0) val=1;
+ return val;
+}
+
diff --git a/std/living/team.c b/std/living/team.c
new file mode 100644
index 0000000..2b31db9
--- /dev/null
+++ b/std/living/team.c
@@ -0,0 +1,650 @@
+// MorgenGrauen MUDlib
+//
+// living/team.c
+//
+// $Id: team.c 9138 2015-02-03 21:46:56Z Zesstra $
+#pragma strong_types
+#pragma save_types
+#pragma range_check
+#pragma no_clone
+#pragma pedantic
+
+#define NEED_PROTOTYPES
+
+#include <properties.h>
+#include <thing/properties.h>
+#include <living/combat.h>
+#include <combat.h>
+#include <living/team.h>
+#include <wizlevels.h>
+#include <hook.h>
+
+#define ME this_object()
+#define TP this_player()
+#define PO previous_object()
+#define ENV environment()
+
+private nosave string team_attack_cmd;
+private nosave mapping team_follow_todo;
+private nosave int team_autofollow;
+private nosave object teammove;
+
+void create() {
+ Set(P_TEAM_ATTACK_CMD,-1,F_SET_METHOD);
+ Set(P_TEAM_ATTACK_CMD,PROTECTED,F_MODE_AS);
+ Set(P_TEAM_AUTOFOLLOW,-1,F_SET_METHOD);
+ Set(P_TEAM_AUTOFOLLOW,PROTECTED,F_MODE_AS);
+ teammove=0;
+ offerHook(H_HOOK_TEAMROWCHANGE, 1);
+}
+
+void add_team_commands() {
+ add_action("teamcmd","gruppe");
+ add_action("teamcmd","g");
+ add_action("teamcmd","team");
+}
+
+string _query_team_attack_cmd() {
+ return Set(P_TEAM_ATTACK_CMD,team_attack_cmd);
+}
+
+int _query_team_autofollow() {
+ return Set(P_TEAM_AUTOFOLLOW,team_autofollow);
+}
+
+private int team_help() {
+ // Syntax-Kompatiblitaet (Avalon) ist ganz nett :-)
+ write("\
+(Befehle des Teamleiters sind mit * gekennzeichnet\n\
+\n\
+* team angriff\n\
+ team angriffsbefehl <befehl>\n\
+* team aufnahme <name>\n\
+ team autof[olge] <ein/aus>\n\
+* team autoi[nfo] <ein/aus> [+[lp]] [+[kp]] [sofort]\n\
+* team entlasse <name>\n\
+ team farben lp_rot lp_gelb kp_rot kp_gelb\n\
+ team flucht[reihe] <reihe>\n\
+ team folge <name>\n\
+* team formation <min[-max]> [<min[-max]> ...]\n\
+ team hilfe|?\n\
+ team [info] [sortiert|alphabetisch]\n\
+ team [kampf]reihe <reihe>\n\
+* team leiter[in] <name>\n\
+ team liste\n\
+* team name <gruppenname>\n\
+ team orte [alle]\n\
+ team ruf[e]\n\
+ team uebersicht\n\
+ team verlasse\n");
+ return 1;
+}
+
+object IsTeamLeader() {
+ object team;
+
+ if (!objectp(team=Query(P_TEAM))
+ || team!=Query(P_TEAM_LEADER)
+ || team->Leader()!=ME)
+ return 0;
+ return team;
+}
+
+object *TeamMembers() {
+ object team;
+
+ if (!objectp(team=Query(P_TEAM)))
+ return ({ME});
+ return team->Members();
+}
+
+string TeamPrefix() {
+ object team;
+
+ if (!objectp(team=Query(P_TEAM)))
+ return "";
+ return "["+team->Name()+"] ";
+}
+
+
+private int team_aufnahmewunsch(string arg) {
+ object pl;
+
+ if ((!objectp(pl=find_player(arg)) && !objectp(pl=present(arg,ENV)))
+ || pl->QueryProp(P_INVIS) || environment(pl)!=ENV)
+ return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
+ if (!living(pl))
+ return notify_fail(pl->Name(WER)+" ist etwas zu inaktiv.\n"),0;
+ if (pl==ME)
+ return notify_fail("Du bist eine Person zu wenig fuer ein Team.\n"),0;
+ SetProp(P_TEAM_NEWMEMBER,pl);
+ if (pl->IsTeamLeader()) {
+ write("Du bittest "+pl->name(WEN)+" um Aufnahme ins Team.\n");
+ tell_object(pl,TP->Name(WER)+" bittet Dich um Aufnahme ins Team.\n");
+ } else {
+ write("Du bittest "+pl->name(WEN)+" um Gruendung eines Teams.\n");
+ tell_object(pl,TP->Name(WER)+" bittet Dich um Gruendung eines Teams.\n");
+ }
+ return 1;
+}
+
+private int team_aufnahme(string arg) {
+ object pl,team;
+ int res;
+
+ if ((!objectp(pl=find_player(arg)) && !objectp(pl=present(arg,ENV)))
+ || pl->QueryProp(P_INVIS) || environment(pl)!=ENV)
+ return notify_fail(capitalize(arg)+" nicht gefunden.\n"),0;
+ if (pl->QueryProp(P_TEAM_NEWMEMBER)!=ME)
+ return notify_fail(pl->Name(WER)+" hat Dich nicht um Aufnahme gebeten.\n"),
+ 0;
+ if (pl==ME)
+ return notify_fail("Du bist eine Person zu wenig fuer ein Team.\n"),0;
+ if (!objectp(team=QueryProp(P_TEAM)))
+ team=clone_object(TEAM_OBJECT);
+ res=team->AddMember(pl);
+ if (!sizeof(team->Members()))
+ team->remove();
+ return res;
+}
+
+object IsTeamMove() {
+ if (!objectp(teammove) || (teammove!=Query(P_TEAM)))
+ teammove=0;
+ return teammove;
+}
+
+static void DoTeamAttack(object env, object callbackto) {
+ if (env==ENV && stringp(team_attack_cmd) && !IS_LEARNER(ME)
+ && (interactive(ME) || !query_once_interactive(ME))
+ && objectp(callbackto) && callbackto==Query(P_TEAM)) {
+ teammove=callbackto;
+ command(team_attack_cmd);
+ }
+ if (objectp(callbackto))
+ callbackto->TeamAttackExecuted_Callback(teammove?1:0);
+ teammove=0;
+}
+
+int CallTeamAttack(object env) {
+ if (stringp(team_attack_cmd)
+ && find_call_out("DoTeamAttack")<0
+ && PO
+ && PO==Query(P_TEAM))
+ return call_out("DoTeamAttack",0,env,PO),1;
+ return 0;
+}
+
+static int DoTeamFollow() {
+ string cmd;
+
+ if (!team_autofollow
+ || (!interactive(ME) && query_once_interactive(ME))
+ || IS_LEARNER(ME)
+ || !mappingp(team_follow_todo))
+ return 0;
+ if (!stringp(cmd=team_follow_todo[ENV]))
+ return team_follow_todo=0;
+
+ do {
+ m_delete(team_follow_todo,ENV);
+ tell_object(ME,sprintf("Du folgst Deinem Team mit \"%s\".\n",cmd));
+ command(cmd);
+ } while (get_eval_cost()>900000 && random(1000)>20 && objectp(ME)
+ && stringp(cmd=team_follow_todo[ENV]));
+
+ // Ist Spieler in Umgebung gelandet, fuer die noch ein
+ // Befehl auszufuehren ist?
+ if (!objectp(ME) || !stringp(team_follow_todo[ENV]))
+ return team_follow_todo=0;
+ while (remove_call_out("DoTeamFollow")!=-1) ;
+ call_out("DoTeamFollow",0);
+ return 0;
+}
+
+int CallTeamFollow(object env, string cmd) {
+ if (!team_autofollow
+ || PO!=Query(P_TEAM)
+ || !PO
+ || !objectp(env)
+ || !stringp(cmd))
+ return 0;
+ if (!mappingp(team_follow_todo))
+ team_follow_todo=([]);
+ if (ENV!=env && !team_follow_todo[ENV])
+ return 0;
+ team_follow_todo[env]=cmd;
+ if (find_call_out("DoTeamFollow")<0)
+ call_out("DoTeamFollow",0);
+ return 1;
+}
+
+int ClearTeamFollow() {
+ if (PO!=Query(P_TEAM) || !PO)
+ return 0;
+ team_follow_todo=([]);
+ return 1;
+}
+
+mixed *PresentTeamRows() {
+ object team;
+ mixed *res;
+ int i;
+
+ if (!objectp(team=Query(P_TEAM))) {
+ res=EMPTY_TEAMARRAY;
+ res[0]=({ME});
+ return res;
+ }
+ res=team->PresentRows(ENV);
+ for (i=0;i<MAX_TEAMROWS;i++)
+ if (member(res[i],ME)>=0)
+ return res;
+ res[0]+=({ME});
+ return res;
+}
+
+varargs mixed *PresentEnemyRows(object *here) {
+ mixed *res,*rows;
+ mapping added_teams;
+ int i,j;
+ object ob,team;
+
+ added_teams=([Query(P_TEAM):1]); // Nicht auf eigenes Team hauen
+ res=EMPTY_TEAMARRAY;
+ if (!pointerp(here))
+ here=PresentEnemies();
+ for (i=sizeof(here)-1;i>=0;i--) {
+ if (!objectp(ob=here[i]))
+ continue;
+ if (!objectp(team=ob->QueryProp(P_TEAM))) {
+ res[0]+=({ob});
+ continue;
+ }
+ if (added_teams[team])
+ continue;
+ added_teams[team]=1;
+ rows=team->PresentRows(ENV);
+ for (j=0;j<MAX_TEAMROWS;j++)
+ res[j]+=rows[j];
+ }
+ return res;
+}
+
+varargs object SelectNearEnemy(object *here, int forcefrom) {
+ object ob,en,team;
+ mixed *rows;
+ int *prob,prot,i,r,sz,upsz,sum;
+
+ if (!pointerp(here))
+ here=PresentEnemies();
+ if (!objectp(ob=SelectEnemy(here)))
+ return 0;
+ en=ob->QueryProp(P_TEAM); // Feindliches Team
+ if (objectp(team=Query(P_TEAM))) { // Eigenes Team
+ if (en==team) // Feind im eigenen Team, kein ANDERES Mitglied waehlen.
+ return ob; // Aber auch ausserhalb Reihe 1 draufhauen
+ rows=team->PresentRows(ENV);
+ if (member(rows[0],ME)<0) // Stehe ich in der ersten Reihe?
+ return 0; // Falls nein ist auch kein Gegner nahe.
+ }
+ if (!objectp(en))
+ return ob; // Ist nicht in einem Team, also drauf.
+ rows=en->PresentRows(environment(ob));
+ prob=({1,0,0,0,0});
+ prot=sum=0;
+ for (i=0;i<MAX_TEAMROWS;i++) {
+ if (prot>0) prot--; // Schutzkegel nimmt ab.
+ if (!sz=sizeof(rows[i])) continue; // Gegner in dieser Reihe
+ upsz=sz-prot;if (upsz<0) continue; // Anzahl ungeschuetzter Gegner
+ prob[i]+=(upsz+sum); // Wahrscheinlichkeit += ungeschuetzt
+ sum=prob[i]; // Summe bisheriger Wahrscheinlichkeiten
+ if (sz>prot) prot=sz; // Neuer Schutzkegel
+ }
+ r=random(sum);
+ for (i=0;i<MAX_TEAMROWS;i++)
+ if (r<prob[i])
+ break;
+ if (i>=MAX_TEAMROWS)
+ i=0;
+ if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
+ return en;
+ if (i && objectp(en=SelectEnemy(forcefrom?(here&rows[0]):rows[0])))
+ return en;
+ return ob;
+}
+
+varargs object SelectFarEnemy(object *here, int min, int max, int forcefrom) {
+ mixed *rows;
+ int *prob,i,r,sum;
+ object en;
+
+ if (max<0 || min>=MAX_TEAMROWS || max<min)
+ return 0;
+ if (min<0) min=0;
+ if (max>=MAX_TEAMROWS) max=MAX_TEAMROWS-1;
+ if (!pointerp(here))
+ here=PresentEnemies();
+ rows=PresentEnemyRows(here);
+ prob=({0,0,0,0,0});
+ sum=0;
+ for (i=min;i<=max;i++)
+ sum=prob[i]=sum+sizeof(rows[i])+max-i;
+
+ r=random(sum);
+ for (i=min;i<=max;i++)
+ if (r<prob[i])
+ break;
+ if (i>max)
+ i=min;
+ if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
+ return en;
+ for (i=min;i<=max;i++)
+ if (objectp(en=SelectEnemy(forcefrom?(here&rows[i]):rows[i])))
+ return en;
+ return 0;
+}
+
+mixed _query_friend() {
+ mixed res;
+
+ if (res=Query(P_FRIEND))
+ return res;
+ if (objectp(res=Query(P_TEAM_ASSOC_MEMBERS))
+ && query_once_interactive(res))
+ return res;
+ return 0;
+}
+
+int DeAssocMember(object npc) {
+ mixed obs;
+ object team;
+
+ if (extern_call() && PO!=npc &&
+ member(({"gilden","spellbooks"}),
+ explode(object_name(PO),"/")[1])<0)
+ return 0;
+ obs=QueryProp(P_TEAM_ASSOC_MEMBERS);
+ if (!pointerp(obs))
+ return 0;
+ obs-=({npc,0});
+ SetProp(P_TEAM_ASSOC_MEMBERS,obs);
+ if (objectp(team=QueryProp(P_TEAM)))
+ team->RemoveAssocMember(ME,npc);
+ return 1;
+}
+
+int AssocMember(object npc) {
+ mixed obs;
+ object team;
+
+ if (extern_call() && PO!=npc &&
+ member(({"gilden","spellbooks"}),
+ explode(object_name(PO),"/")[1])<0)
+ return 0;
+ if (!objectp(npc)
+ || npc->QueryProp(P_TEAM_ASSOC_MEMBERS)
+ || IsEnemy(npc)
+ || npc==ME
+ || query_once_interactive(npc))
+ return 0;
+ obs=QueryProp(P_TEAM_ASSOC_MEMBERS);
+ if (objectp(obs))
+ return 0;
+ if (!pointerp(obs))
+ obs=({});
+ obs=(obs-({npc,0}))+({npc});
+ SetProp(P_TEAM_ASSOC_MEMBERS,obs);
+ npc->SetProp(P_TEAM_ASSOC_MEMBERS,ME);
+ if (objectp(team=QueryProp(P_TEAM)))
+ team->AddAssocMember(ME,npc);
+ return 1;
+}
+
+varargs void InsertEnemyTeam(mixed ens, int rek) {
+ object *obs,ob,eteam,team;
+ int i;
+
+ team=Query(P_TEAM);
+ // Alle Teammitglieder des Gegners sind Feind:
+ if (objectp(ens)) {
+ if (objectp(eteam=ens->QueryProp(P_TEAM))) {
+ if (eteam==team) // feindliches Team = eigenes Team?
+ return; // also nicht alle Teammitglieder gegeneinander hetzen
+ ens=eteam->Members();
+ } else {
+ ens=({ens});
+ }
+ }
+ if (!pointerp(ens))
+ return;
+ ens-=({ME});
+
+ // Interactives sollen keine Interactives durch Team angreifen:
+ if (query_once_interactive(ME)) {
+ for (i=sizeof(ens)-1;i>=0;i--)
+ if (objectp(ob=ens[i]) && environment(ob)==environment()
+ && !query_once_interactive(ob))
+ InsertSingleEnemy(ob);
+ } else {
+ for (i=sizeof(ens)-1;i>=0;i--)
+ if (objectp(ob=ens[i]) && environment(ob)==environment())
+ InsertSingleEnemy(ob);
+ }
+
+ // Alle anderen Teammitglieder Informieren:
+ if (rek || !objectp(team) || !pointerp(obs=team->Members()))
+ return;
+ obs-=({ME});
+ obs-=ens;
+ for (i=sizeof(obs)-1;i>=0;i--)
+ if (objectp(ob=obs[i]))
+ ob->InsertEnemyTeam(ens,1);
+}
+
+int TeamFlee() {
+ object team;
+
+ if (Query(P_TEAM_WIMPY_ROW)<2 || !objectp(team=Query(P_TEAM)))
+ return 0;
+ if (!team->FleeToRow(ME))
+ return 0;
+ if (Query(P_TEAM_LEADER)==team) {
+ if (team_autofollow)
+ tell_object(ME,"Du versuchst zu fliehen, "+
+ "Dein Team folgt Dir nicht mehr.\n");
+ team_autofollow=0;
+ }
+ return 1;
+}
+
+varargs mapping PresentTeamPositions(mixed pres_rows) {
+ mapping res;
+ int i,j;
+ object *obs,ob;
+
+ res=([]);
+ if (!pointerp(pres_rows))
+ pres_rows=PresentTeamRows();
+ for (i=0;i<MAX_TEAMROWS;i++) {
+ obs=pres_rows[i];
+ for (j=sizeof(obs)-1;j>=0;j--)
+ if (objectp(ob=obs[j]) && !res[ob])
+ res[ob]=i+1;
+ }
+ return res;
+}
+
+varargs int PresentPosition(mixed pmap) {
+ object team;
+ int i;
+
+ if (!objectp(team=Query(P_TEAM)))
+ return 1;
+ if (mappingp(pmap))
+ return pmap[ME];
+ if (!pointerp(pmap))
+ pmap=team->PresentRows(ENV);
+ for (i=1;i<MAX_TEAMROWS;i++)
+ if (member(pmap[i],ME)>=0)
+ return i+1;
+ return 1;
+}
+
+#define FILLSTRING " "
+varargs private string center_string(string str, int w) {
+ return (FILLSTRING[0..((w-sizeof(str))/2-1)]+str+FILLSTRING)[0..(w-1)];
+}
+
+private int ShowTeamRows() {
+ int i,j,sz;
+ mixed *pres_rows;
+ object *obs,ob;
+ string str;
+
+ pres_rows=PresentEnemyRows();
+ for (sz=MAX_TEAMROWS-1;sz>=0;sz--)
+ if (sizeof(pres_rows[sz]))
+ break;
+ for (i=sz;i>=0;i--) {
+ obs=pres_rows[i];str="";
+ for (j=sizeof(obs)-1;j>=0;j--)
+ if (objectp(ob=obs[j])) {
+ if (str!="") str+=" / ";
+ str+=ob->Name(RAW);
+ }
+ printf("%d. %s\n",i+1,center_string(str,75));
+ }
+ if (sz>=0)
+ write(" ---------------------------------------------------------------------------\n");
+ pres_rows=PresentTeamRows();
+ for (sz=MAX_TEAMROWS-1;sz>0;sz--)
+ if (sizeof(pres_rows[sz]))
+ break;
+ for (i=0;i<=sz;i++) {
+ obs=pres_rows[i];str="";
+ for (j=sizeof(obs)-1;j>=0;j--)
+ if (objectp(ob=obs[j])) {
+ if (str!="") str+=" / ";
+ str+=ob->Name(RAW);
+ }
+ printf("%d. %s\n",i+1,center_string(str,75));
+ }
+ return 1;
+}
+
+varargs int team_list(string arg) {
+ object *tobs,*obs,tob,ob,ld;
+ string *nms,*tnms,str;
+ int i,j;
+
+ if (!pointerp(tobs=TEAM_MASTER->ListTeamObjects())) return 0;
+ if (arg!="alle") arg=0;
+ tnms=({});
+ for (i=sizeof(tobs)-1;i>=0;i--) {
+ if (!objectp(tob=tobs[i])
+ || !objectp(ld=tob->Leader())
+ || (!query_once_interactive(ld) && !arg)
+ || !pointerp(obs=tob->Members()))
+ continue;
+ nms=({});
+ for (j=sizeof(obs)-1;j>=0;j--) {
+ if (!objectp(ob=obs[j])
+ || (!query_once_interactive(ob) &&!arg))
+ continue;
+ if (!stringp(str=ob->Name(WER))) str="?";
+ if (ob==ld) str+="(*)";
+ nms+=({str});
+ nms=sort_array(nms,#'>);
+ }
+ if (!stringp(str=tob->Name())) str="Team ?";
+ str+=": ";
+ tnms+=({break_string(implode(nms,", "),78,str)});
+ tnms=sort_array(tnms,#'<);
+ }
+ if (sizeof(tnms))
+ tell_object(ME, sprintf("%@s\n", tnms));
+ else
+ tell_object(ME, "Keine Teams gefunden.\n");
+
+ return 1;
+}
+
+varargs int teamcmd(string arg) {
+ string *words,narg;
+ object team;
+
+ if (!arg)
+ arg="";
+ if (!stringp(narg=TP->_unparsed_args()))
+ narg = arg;
+ if (!sizeof(words=explode(arg," ")))
+ return 0;
+
+ if (sizeof(words) > 1) {
+ arg=implode(words[1..]," ");
+ narg = implode(explode(narg, " ")[1..], " ");
+ }
+ else
+ arg = narg = "";
+
+ switch(words[0]) { // Befehle die keine Mitgliedschaft erfordern:
+ case "aufnahme":
+ return team_aufnahme(arg);
+ case "folge":
+ return team_aufnahmewunsch(arg);
+ case "?":
+ case "hilfe":
+ return team_help();
+ case "liste":
+ return team_list(arg);
+ case "uebersicht":
+ return ShowTeamRows();
+ default:;
+ }
+
+ if (!objectp(team=QueryProp(P_TEAM)))
+ return notify_fail("Du bist in keinem Team.\n"),0;
+
+ switch(words[0]) {
+ case "angriffsbefehl":
+ if (narg=="") narg=0;
+ team_attack_cmd=narg;
+ if (stringp(narg))
+ write("Du beginnst den Kampf mit \""+narg+"\"\n");
+ else
+ write("Du hast den Teamangriffsbefehl deaktiviert.\n");
+ break; // NICHT return!
+ case "autofolge":
+ case "autof":
+ if (arg=="ein" || arg=="an") {
+ team_autofollow=1;
+ if (IsTeamLeader())
+ write("Dein Team folgt Dir.\n");
+ else
+ write("Du folgst jetzt dem Teamleiter.\n");
+ } else {
+ team_autofollow=0;
+ if (IsTeamLeader())
+ write("Dein Team folgt Dir nicht mehr.\n");
+ else
+ write("Du folgst jetzt nicht mehr dem Teamleiter.\n");
+ }
+ break; // NICHT return!
+ default: ;
+ }
+ return team->TeamCmd(words[0],narg); // Befehle die Mitgliedschaft erfordern:
+}
+
+varargs void InformRowChange(int from, int to, object caster) {
+
+ if (caster) return; // Fuer den Fall, dass Gildenobjekt==ME ist
+ if (PO!=Query(P_TEAM)) return;
+#if __BOOT_TIME__ < 1281904437
+ mixed gilde = QueryProp(P_GUILD);
+ if (!stringp(gilde)) return;
+ if (!objectp(gilde=find_object("/gilden/"+gilde))) return;
+ gilde->InformRowChange(from,to,ME);
+#endif
+ HookFlow(H_HOOK_TEAMROWCHANGE, ({from,to}) );
+}