| // 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(); |
| } |
| |