blob: ad90aa055d40a75674fbae582c9fa97d6f1b8971 [file] [log] [blame]
// 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;
}