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