blob: 93c9dccd6495bc6dc1c6f343821845548fc96296 [file] [log] [blame]
// 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();
}