blob: f7077a625f8f2de22e2cad802b5b885fab9210fe [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// living/skills.c -- Gilden-, Skill- und Spellfunktionen fuer Lebewesen
4//
5// $Id: skills.c 8755 2014-04-26 13:13:40Z Zesstra $
6#pragma strict_types
7#pragma save_types
8#pragma range_check
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010
11inherit "/std/living/std_skills";
12
13#define NEED_PROTOTYPES
14#include <living/skills.h>
15#include <living/skill_attributes.h>
16#include <player/life.h>
17#undef NEED_PROTOTYPES
18
19#include <thing/properties.h>
20#include <properties.h>
21#include <new_skills.h>
22#include <wizlevels.h>
23
24// speichert die Spell-Fatigues (global, Spruchgruppen, Einzelsprueche)
25private mapping spell_fatigues = ([]);
26
27// Prototypen
28private void expire_spell_fatigues();
29
30protected void create()
31{
32 // mainly necessary for players, but there may be some NPC with savefiles.
33 // Additionally, it simplifies expiration of old keys to have it here.
34 call_out(#'expire_spell_fatigues, 4);
35}
36
37
38// Diese - hier scheinbar sinnlose - Funktion wird von /std/player/skills.c dann
39// ueberladen.
40public int is_deactivated_skill(string sname, string gilde)
41{
42 return 0;
43}
44
45
46
47static string _query_visible_guild()
48{ string res;
49
50 if ( stringp(res=Query(P_VISIBLE_GUILD)) )
51 return res;
52
53 return QueryProp(P_GUILD);
54}
55
56static string _query_visible_subguild_title()
57{ string res;
58
59 if ( stringp(res=Query(P_VISIBLE_SUBGUILD_TITLE)) )
60 return res;
61
62 return QueryProp(P_SUBGUILD_TITLE);
63}
64
65static mixed _query_guild_prepareblock()
66{ mapping res;
67 string gilde;
68
69 if ( !stringp(gilde=QueryProp(P_GUILD)) )
70 return 0;
71 if ( !mappingp(res=Query(P_GUILD_PREPAREBLOCK))
72 || !member(res,gilde) )
73 return 0;
74 return res[gilde];
75}
76
77static mixed _set_guild_prepareblock(mixed arg)
78{ mapping res;
79 string gilde;
80
81 if ( !stringp(gilde=QueryProp(P_GUILD)) )
82 return 0;
83 if ( !mappingp(res=Query(P_GUILD_PREPAREBLOCK)) )
84 res=([]);
85
86 res[gilde]=arg;
87 Set(P_GUILD_PREPAREBLOCK,res);
88
89 return arg;
90}
91
92
93private nosave int valid_setskills_override;
94// Man sollte eigentlich ja nicht Parameter als globale Variablen
95// uebergeben, aber hier ging es nicht anders
96nomask private int valid_setskills(string gilde)
97{ string fn;
98
99 if ( !query_once_interactive(this_object()) )
100 return 1; // Monster duerfen sich selber Skills setzen :)
101
102 if ( QueryProp(P_TESTPLAYER) || IS_WIZARD(this_object()) )
103 return 1; // Testspieler und Magier sind schutzlose Opfer ;-)
104
105 if ( previous_object() )
106 {
107 if ( previous_object()==this_object()
108 && this_interactive()==this_object() )
109 return 1;
110
111 fn=object_name(previous_object());
112 if ( fn[0..7]=="/gilden/"
113 || fn[0..11]=="/spellbooks/"
114 || fn[0..7]=="/secure/"
115 || fn[0..11]=="/p/zauberer/" )
116 return 1; // Die sollten problemlos aendern duerfen
117
118 if ( file_size("/gilden/access_rights")>0
bugfixd94d0932020-04-08 11:27:13 +0200119 && ({int})call_other("/gilden/access_rights",
MG Mud User88f12472016-06-24 23:31:02 +0200120 "access_rights",
121 getuid(previous_object()),
122 gilde+".c"))
123 return 1; // Setzendes Objekt kommt vom Gildenprogrammierer
124
125 if ( file_size("/gilden/"+gilde+".c")>0
bugfixd94d0932020-04-08 11:27:13 +0200126 && ({int})call_other("/gilden/"+gilde,
MG Mud User88f12472016-06-24 23:31:02 +0200127 "valid_setskills",
128 explode(fn,"#")[0]) )
129 return 1; // Die Gilde selber kann Ausnahmen zulassen
130 }
131
132 if (valid_setskills_override)
133 {
134 valid_setskills_override=0;
135 return 1; // Fuers Setzen der Closure
136 }
137
138 if ( this_interactive() )
139 {
140 if ( IS_ARCH(this_interactive()) )
141 return 1; // Erzmagier duerfen immer aendern
142
bugfixd94d0932020-04-08 11:27:13 +0200143 if ( ({int})call_other("/gilden/access_rights",
MG Mud User88f12472016-06-24 23:31:02 +0200144 "access_rights",
145 getuid(this_interactive()),
146 gilde+".c"))
147 return 1; // Der Gildenprogrammierer selber auch
148 }
149
150 // Fuer die Waffenskills, die sollen sich selbst auch setzen duerfen
151 if (!this_interactive() && this_object()==previous_object())
152 return 1;
153
154
155 log_file("SETSKILLS",sprintf("*****\n%s PO:%O->TO:%O TI:%O\n GUILD:%s VERB_ARGS:'%s'\n",
156 ctime(time())[4..15],
157 previous_object(),
158 this_object(),
159 this_interactive(),
160 gilde,
161 ( this_interactive() ? query_verb() + " " +
bugfixd94d0932020-04-08 11:27:13 +0200162 ({string})this_interactive()->_unparsed_args() : "") ));
MG Mud User88f12472016-06-24 23:31:02 +0200163
164 return 0;
165}
166
167// Nur interne Verwendung, value wird nicht weiter prueft, muss ok sein.
168// Es wird keine Kopie von value gemacht, wenn es ins Mapping geschrieben
169// wird!
170private mapping internal_set_newskills(mapping value, string gilde) {
171 mapping skills;
172
173 // in der richtigen Gilde setzen.
174 if ( !gilde && !(gilde=QueryProp(P_GUILD)) )
175 gilde="ANY";
176
177 // Query(), hier ist eine Kopie nicht sinnvoll.
178 if ( !mappingp(skills=Query(P_NEWSKILLS,F_VALUE)) ) {
179 skills=([]);
180 Set(P_NEWSKILLS, skills, F_VALUE);
181 }
182
183 // Falls dies hier mal ausgewertet werden sollte, nicht vergessen, dass
184 // einige Funktion hier im File die Prop evtl. via
185 // internal_query_newskills() abrufen und direkt aendern...
186 valid_setskills(gilde); // Sicherheitsueberpruefung
187
188 // Skills setzen. Set() unnoetig, weil wir das von Query() gelieferte
189 // Mapping aendern und das ja via Referenz bekommen haben.
190 skills[gilde]=value;
191 //Set(P_NEWSKILLS,skills);
192
193 return(value);
194}
195
196// nur zur internen Verwendung, es wird keine Kopie des Skillmappings gemacht!
197private mapping internal_query_newskills(string gilde) {
198 mapping skills;
199
200 // richtige Gilde abfragen.
201 if ( !gilde && !(gilde=QueryProp(P_GUILD)) )
202 gilde="ANY";
203
204 skills=Query(P_NEWSKILLS);
205
206 if (!mappingp(skills) || !mappingp(skills=skills[gilde]) )
207 return ([]);
208
209 return(skills);
210}
211
212// Eigentlich sollte man den _query-Funktionen keine Parameter geben...
213static varargs mapping _query_newskills(string gilde) {
214
215 // sonst Kopie des spellmappings liefern! Kostet zwar, aber verhindert
216 // einige andere Bugs und versehentliche Aenderungen an den Skills!
217 return(deep_copy(internal_query_newskills(gilde)));
218}
219
220// Eigentlich sollte man den _set-Funktionen keine weiteren Parameter geben
221static varargs mapping _set_newskills(mapping value, string gilde) {
222
223 // value auf Mappings normalisieren, ggf. Kopieren
224 if ( !mappingp(value) )
225 value=([]);
226 else
227 //zur Sicherheit, wer weiss, was der setzende noch damit macht...
228 value=deep_copy(value);
229
230 // und setzen...
231 internal_set_newskills(value, gilde);
232
233 // und noch ne Kopie von dem Liefern, was wir gesetzt haben (keine Referenz,
234 // sonst koennte der Aufrufende ja noch im Nachhinein aendern).
235 return(_query_newskills(gilde));
236}
237
238private mapping InternalQuerySkill(string sname, string gilde) {
239 mixed skill, skills;
240 // In is_any wird gespeichert, ob es ein gildenunabhaengier Skill ist,
241 // fuer die is_deactivate_skill-Abfrage.
242 int is_any;
243
244 // Skills komplett abfragen, keine spez. Gilde
245 if (!mappingp(skills=Query(P_NEWSKILLS,F_VALUE)))
246 return 0;
247
248 if (stringp(gilde) && sizeof(gilde)) {
249 //bestimmte Gilde angegeben, gut, dort gucken.
250 if (mappingp(skills[gilde]))
251 skill=skills[gilde][sname];
252 }
253 else {
254 gilde=QueryProp(P_GUILD); //reale Gilde holen
255 if (gilde && mappingp(skills[gilde]) &&
256 (skill=skills[gilde][sname])) {
257 // gibt es den Spell in der Gilde des Spielers?
258 // dann hier nix machen...
259 }
260 else if (mappingp(skills["ANY"])) {
261 // Zum Schluss: Gibt es den Skill vielleicht Gildenunabhaengig?
262 skill=skills["ANY"][sname];
263 // wenn man hier reinkommt, dann spaeter mit is_deactivated_skill()
264 // pruefen!
265 is_any=1;
266 }
267 }
268
269 // wenn kein Skill gefunden, mit 0 direkt raus
270 if (!skill) return 0;
271
272 // Bei gildenunabhaengigen auch im Skillmapping vermerken
273 if ( is_any ) {
274 skill+=([SI_GUILD:"ANY"]);
275 // Ist er vielleicht in der Gilde des Spielers deaktiviert?
276 // Dies kann nur der Fall sein, wenn es kein Gildenskill ist.
277 if (is_deactivated_skill(sname,gilde)) {
278 return 0;
279 }
280 }
281
282 return(skill);
283}
284
285public varargs mapping QuerySkill(string sname, string gilde) {
286
287 if (!stringp(sname) || !sizeof(sname))
288 return 0;
289
290 //Kopie zurueckliefern
291 return(deep_copy(InternalQuerySkill(sname,gilde)));
292}
293
294#define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y))
295public varargs int QuerySkillAbility(string sname, string gilde)
296{ mapping skill;
297 string skill2;
298
299 if ( !(skill=InternalQuerySkill(sname, gilde)) )
300 return 0;
301
302 int val=skill[SI_SKILLABILITY];
303
304 if (skill2=skill[SI_INHERIT])
305 {
306 int val2;
307 val2=QuerySkillAbility(skill2);
308 val=(val*MAX_ABILITY+SMUL(val,val2))/(2*MAX_ABILITY);
309 }
310
311 return val;
312}
313
314protected varargs mixed LimitAbility(mapping sinfo, int diff)
315{ mixed abil;
316 int max,old,d2;
317
318 abil=sinfo[SI_SKILLABILITY];
319
320 if ( !intp(abil) )
321 return sinfo;
322 old=abil;
323
324 // Beim Spieler eingetragene Schwierigkeit gilt vor angegebener.
325 if ( (d2=sinfo[SI_DIFFICULTY]) )
326 diff=d2;
327
328 // diff <-100 soll nicht hemmen und macht keinen Sinn
329 diff=(diff<(-100))?(-100):diff;
330
331 max=MAX_ABILITY-(diff+100)*(35-QueryProp(P_LEVEL));
332
333// diff|lvl 1:| 3:| 7:| 10:| 13:| 16:| 19:| 22:| 25:| 28:| 31:| 34:|
334// ----+------+-----+-----+----+----+----+----+----+----+----+----+----+
335// -50| 83%| 84%| 86%| 87%| 89%| 90%| 92%| 93%| 95%| 96%| 98%| 99%|
336// -10| 69%| 72%| 74%| 77%| 80%| 82%| 85%| 88%| 91%| 93%| 96%| 99%|
337// 0| 66%| 69%| 72%| 75%| 78%| 81%| 84%| 87%| 90%| 93%| 96%| 99%|
338// 10| 62%| 65%| 69%| 72%| 75%| 79%| 82%| 85%| 89%| 92%| 95%| 98%|
339// 20| 59%| 62%| 66%| 70%| 73%| 77%| 80%| 84%| 88%| 91%| 95%| 98%|
340// 30| 55%| 59%| 63%| 67%| 71%| 75%| 79%| 83%| 87%| 90%| 94%| 98%|
341// 40| 52%| 56%| 60%| 65%| 69%| 73%| 77%| 81%| 86%| 90%| 94%| 98%|
342// 50| 49%| 53%| 58%| 62%| 67%| 71%| 76%| 80%| 85%| 89%| 94%| 98%|
343// 100| 32%| 38%| 44%| 50%| 56%| 62%| 68%| 74%| 80%| 86%| 92%| 98%|
344// 150| 15%| 22%| 30%| 37%| 45%| 52%| 60%| 67%| 75%| 82%| 90%| 97%|
345// 200| -2%| 7%| 16%| 25%| 34%| 43%| 52%| 61%| 70%| 79%| 88%| 97%|
346// 250| -19%| -8%| 2%| 12%| 23%| 33%| 44%| 54%| 65%| 75%| 86%| 96%|
347// 300| -36%| -24%| -12%| 0%| 12%| 24%| 36%| 48%| 60%| 72%| 84%| 96%|
348// 400| -70%| -55%| -40%|-25%|-10%| 5%| 20%| 35%| 50%| 65%| 80%| 95%|
349// 500| -104%| -86%| -68%|-50%|-32%|-14%| 4%| 22%| 40%| 58%| 76%| 94%|
350// 600| -138%|-117%| -96%|-75%|-54%|-33%|-12%| 9%| 30%| 51%| 72%| 93%|
351
352 if ( abil>max )
353 abil=max;
354 if ( abil>MAX_ABILITY
355 ) abil=MAX_ABILITY;
356 else if ( abil<-MAX_ABILITY )
357 abil=-MAX_ABILITY;
358
359 if ( old && !abil )
360 abil=1;
361 // Faehigkeiten sollen nicht durch die Begrenzung verschwinden
362
363 sinfo[SI_SKILLABILITY]=abil;
364
365 return sinfo;
366}
367
368public varargs void ModifySkill(string sname, mixed val, int diff, string gilde)
369{
370 mapping skills;
371 mixed skill;
372
373 if ( !stringp(sname) || !sizeof(sname) )
374 return;
375
376 // internal_query_newskills() macht keine Kopie
377 skills=internal_query_newskills(gilde);
378
379 // Skill ermitteln, wenn nicht existiert, wird er angelegt.
380 if (!skill=skills[sname]) {
381 skill=([]);
382 }
383
384 // Zur Sicherheit mal das Mapping kopieren, wer weiss, was der
385 // Aufrufende dieser Funktion selber spaeter damit noch macht.
386 // ist ok, wenn val kein Mapping ist, dann macht deep_copy nix.
387 val=deep_copy(val);
388
389 // Skill und val vereinigen
390 if ( mappingp(val) )
391 skill+=val;
392 else if (intp(val))
393 skill[SI_SKILLABILITY]=val;
394 else
395 raise_error(sprintf("Bad arg 2 to ModifySkill(): expected 'int', "
396 "got %.10O.\n",val));
397
398 // Lernen entsprechend SI_DIFFICULTY begrenzen.
399 if(diff && !member(skill,SI_DIFFICULTY))
400 skill[SI_DIFFICULTY]=diff;
401 skill=LimitAbility(skill,diff || skill[SI_DIFFICULTY]);
402
403 // schliesslich im Skillmapping vermerken. Im Normalfall ist der Skill jetzt
404 // schon geaendert, nicht erst nach dem internal_set_newskills().
405 skills[sname]=skill;
406
407 // explizites Abspeichern fast ueberfluessig, weil wir oben eine Referenz
408 // auf das Skillmapping gekriegt haben...
409 // Aber es koennte sein, dass dies der erste Skill fuer diese Gilde ist,
410 // dann ist es noetig. Zum anderen wird internal_set_newskills() nochmal
411 // geloggt.
412 internal_set_newskills(skills,gilde);
413}
414
415public varargs void LearnSkill(string sname, int add, int diff)
416{ mapping skill;
417 string skill2,gilde;
418 int val;
419
420 // Spieler sollen nur lernen, wenn sie interactive sind. Das soll
421 // natuerlich nur fuer Spieler gelten.
422 if (query_once_interactive(this_object()) && !interactive())
423 return;
424
425 if ( add>MAX_SKILLEARN )
426 add=MAX_SKILLEARN;
427 else if ( add<1 )
428 add=1;
429
430 // Skillmapping ermitteln (hier kommt keine Kopie zurueck)
431 skill=InternalQuerySkill(sname, 0);
432 // wenn kein Skill, dann Abbruch
433 if (!skill) return;
434
435 val=skill[SI_SKILLABILITY];
436 gilde=skill[SI_GUILD];
437
438 val+=add;
439
440 ModifySkill(sname,val,diff,gilde);
441 if ( skill2=skill[SI_INHERIT] )
442 LearnSkill(skill2,add/3,diff);
443}
444
445public varargs int UseSpell(string str, string spell)
446{ string gilde,sbook;
447 mapping sinfo;
448 closure cl;
449
450 if ( !spell && !(spell=query_verb()) )
451 return 0;
452
453 spell=lower_case(spell);
454
455 // QuerySkill() liefert eine Kopie des Skillmappings.
456 // wenn skill unbekannt oder Ability <= 0, ist der Spell nicht nutzbar.
457 if ( !(sinfo=QuerySkill(spell,0))
458 || sinfo[SI_SKILLABILITY] <= 0 )
459 return 0;
460
461 sinfo[SI_SKILLARG]=str; // Argument eintragen
462
463 if ( !closurep(cl=sinfo[SI_CLOSURE]) )
464 {
465 // Wenn ein Spellbook angegeben ist wird der Spell direkt ausgefuehrt
466 if ( stringp(sbook=sinfo[SI_SPELLBOOK]) )
467 cl=symbol_function("UseSpell",SPELLBOOK_DIR+sbook);
468
469 // Wenn der Spieler in einer Gilde ist, so weiss diese, in welchem
470 // Spellbook der Spell zu finden ist...
471 else if ( (gilde=QueryProp(P_GUILD)) &&
472 ( find_object(GUILD_DIR+gilde) || file_size(GUILD_DIR+gilde+".c")>-1))
473 cl=symbol_function("UseSpell",GUILD_DIR+gilde);
474 else
475 cl=function int () {return 0;};
476
477 sinfo[SI_CLOSURE]=cl;
478 valid_setskills_override=1;
479 ModifySkill(spell,([SI_CLOSURE:cl]),0,sinfo[SI_GUILD]);
480 valid_setskills_override=0;
481 }
482 return funcall(cl,this_object(),spell,sinfo);
483}
484
485public varargs mixed UseSkill(string skill, mapping args)
486{ mapping sinfo;
487 string gilde, func,skill2;
488 mixed res;
489 closure cl;
490
491 if ( !skill ||
492 QueryProp(P_GHOST))
493 return 0;
494
495 skill=capitalize(skill);
496 // QuerySkill() liefert eine Kopie des Skillmappings
497 if ( !(sinfo=QuerySkill(skill,0)) )
498 return 0;
499
500 if (args)
501 sinfo+=args;
502
503 if ( !closurep(cl=sinfo[SI_CLOSURE]) )
504 {
505 if ( !(func=sinfo[SI_SKILLFUNC]) // Keine Funktion angegeben?
506 || !(gilde=QueryProp(P_GUILD))) // Keine Gilde angegeben?
507 {
508 // Dann Standard-Funktion nehmen, wenn es die nicht gibt, den
509 // Ability-Wert zurueckliefern.
510 if (!closurep(cl = symbol_function("StdSkill_"+skill,this_object())))
511 cl=function int (object ob, string sname)
512 {return QuerySkillAbility(sname);} ;
513 }
514 else
515 {
516 // Sonst diese Funktion im Gildenobjekt aufrufen
517 cl=symbol_function(func,GUILD_DIR+gilde);
518 }
519
520 sinfo[SI_CLOSURE]=cl;
521 valid_setskills_override=1;
522 ModifySkill(skill,([SI_CLOSURE:cl]),0,sinfo[SI_GUILD]);
523 valid_setskills_override=0;
524 }
525
526 res=funcall(cl,this_object(),skill,sinfo);
527 if ( (skill2=sinfo[SI_INHERIT]) && mappingp(res) )
528 res=UseSkill(skill2,res); // Fuer Skills, die von anderen abhaengen
529
530 return res;
531}
532
533// ************** Spellfatigues ***************
534
535/* Prueft die Spellfatigue fuer Spruch(gruppe) <key>.
536 * <key> darf 0 sein (globale Spruchsperre).
537 * Liefert 0, wenn keine Sperre und die Ablaufzeit, wenn eine Sperre noch
538 * gueltig. ist.
539 */
540public varargs int CheckSpellFatigue(string key) {
541 // key==0 is the (default) global spellfatigue.
542 if (spell_fatigues[key] > time())
543 return spell_fatigues[key]; // Ablaufzeit zurueckgeben.
544
545 return 0; // ok, keine Sperre.
546}
547
548/** Speichert eine Spellfatigue von <duration> Sekunden fuer <key>.
549 * <key> darf 0 sein und bezeichnet das globale Spellfatigue.
550 * Rueckgabewert: Ablaufzeit der gesetzten Sperre
551 -1, wenn noch eine nicht-abgelaufene Sperre auf dem <key> lag.
552 0, wenn duration 0 ist.
553 */
554public varargs int SetSpellFatigue(int duration, string key) {
555 // aktuelle Sperre abgelaufen?
556 if (CheckSpellFatigue(key))
557 return -1; // alte Sperre noch aktiv.
558
559 duration += time();
560 // 0 is OK for <key>, it is the key for global spell fatigues
561 spell_fatigues[key] = duration;
562 return duration;
563}
564
565/* Prueft die Spellfatigue fuer Spruch(gruppe) <key>.
566 * <key> darf fuer diese Funktion 0 (globale Spruchsperre) sein, aber man
567 * darf das Argument nicht weglassen, damit nicht ein verpeilter Magier
568 * versehentlich die globale Spruchsperre nullt.
569 */
570public void DeleteSpellFatigue(string key) {
571 // key==0 is the (default) global spellfatigue.
572 m_delete(spell_fatigues, key);
573}
574
575/** Loescht abgelaufene Keys aus dem spell_fatigue mapping.
576 */
577private void expire_spell_fatigues() {
578 foreach(string key, int endtime: spell_fatigues) {
579 if (endtime <= time())
580 m_delete(spell_fatigues, key);
581 }
582}
583
584/** Setmethode fuer P_NEXT_SPELL_TIME.
585 */
586static int _set_next_spell(int fatigue) {
587 return SetSpellFatigue(fatigue - time());
588}
589/** Querymethode fuer P_NEXT_SPELL_TIME.
590 */
591static int _query_next_spell() {
592 return CheckSpellFatigue();
593}
594