blob: 8ef27d159205553985d660a5ff125c975748c487 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// spellbook.c -- Grundlegende Funktionen fuer Zaubersprueche
4//
5// $Id: spellbook.c 9142 2015-02-04 22:17:29Z Zesstra $
6#pragma strict_types
7#pragma save_types
8#pragma no_shadow
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010#pragma range_check
11
12//#define NEED_PROTOTYPES
13inherit "/std/thing";
14inherit "/std/restriction_checker";
15inherit "/std/player/pklog";
16
17#include <thing/properties.h>
18#include <properties.h>
19#include <wizlevels.h>
20#include <new_skills.h>
21#include <spellbook.h>
22#define ME this_object()
23
24void create() {
25 seteuid(getuid());
26 // diese Prop sollte von aussen nicht direkt geaendert werden koennen.
27 Set(P_SB_SPELLS,([]),F_VALUE);
28 Set(P_SB_SPELLS,PROTECTED,F_MODE_AS);
29
30 ::create();
31}
32
33mapping _query_sb_spells() {
34 // Kopie liefern, da sonst versehentlich schnell eine Aenderung des
35 // originalen Mappings hier passieren kann.
36 return(deep_copy(Query(P_SB_SPELLS, F_VALUE)));
37}
38
39mapping QuerySpell(string spell) {
40 mapping spi;
41
42 //Query-Methode umgehen, damit nur ein Spellmapping kopiert werden muss und
43 //nicht das ganzen Mapping mit allen Spells.
44 if (!spell
45 || !(spi=Query(P_SB_SPELLS))
46 || !(spi=spi[spell]))
47 return 0;
48 return deep_copy(spi);
49}
50
51varargs int
52AddSpell(string verb, int kosten, mixed ski) {
53 int level;
54 mapping spells;
55
56 if (!verb || kosten<0)
57 return 0;
58
59 verb=lower_case(verb);
60 level=(intp(ski))?ski:0;
61
62 if (!mappingp(ski)) ski=([]);
63 // Zur Sicherheit eine Kopie machen, wer weiss, was da uebergeben wird, bzw.
64 // was der Setzende mit ski hinterher noch macht.
65 else ski=deep_copy(ski);
66
67 ski=AddSkillMappings(QueryProp(P_GLOBAL_SKILLPROPS),ski);
68 ski+=([SI_SPELLCOST:kosten]);
69 // SI_SPELL-Submapping hinzufuegen, wenn nicht da.
70 if (!member(ski,SI_SPELL))
71 ski+=([SI_SPELL:([]) ]);
72 // ski[SI_SPELL][SP_NAME] ergaenzen, ggf. aus ski verschieben
73 if (!stringp(ski[SI_SPELL][SP_NAME]) && stringp(ski[SP_NAME])) {
74 ski[SI_SPELL][SP_NAME] = ski[SP_NAME];
75 m_delete(ski, SP_NAME);
76 }
77 else if (!stringp(ski[SI_SPELL][SP_NAME])) {
78 ski[SI_SPELL][SP_NAME] = verb;
79 }
80 if (stringp(ski[SP_NAME])) {
81 m_delete(ski, SP_NAME);
82 }
83
84 if (level)
85 ski=AddSkillMappings(ski,([SI_SKILLRESTR_LEARN:([P_LEVEL:level])]));
86
87 // Abfrage per Query(), um das Mapping als Referenz, nicht als Kopie zu
88 // bekommen, das spart etwas Zeit.
89 if (!(spells=Query(P_SB_SPELLS,F_VALUE))) {
90 spells=([]);
91 SetProp(P_SB_SPELLS,spells);
92 }
93 // Spell setzen...
94 spells[verb]=ski;
95 // Set() nicht noetig, da Query() an spell das Originalmapping geliefert hat
96 // und dieses bereits jetzt geaendert wurde. ;-)
97 //Set(P_SB_SPELLS,spells+([verb:ski]),F_VALUE);
98
99 // hat wohl geklappt...
100 return(1);
101}
102
103int
104TryAttackSpell(object victim, int damage, mixed dtypes,
105 mixed is_spell, object caster, mapping sinfo) {
106 mixed no_attack;
107 int nomag;
108
bugfixaf2be4f2020-03-22 19:13:07 +0100109 if (no_attack = ({int|string})victim->QueryProp(P_NO_ATTACK)) {
MG Mud User88f12472016-06-24 23:31:02 +0200110 if (stringp(no_attack))
bugfixaf2be4f2020-03-22 19:13:07 +0100111 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100112 no_attack,
113 MT_NOTIFICATION,
114 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200115 else
bugfixaf2be4f2020-03-22 19:13:07 +0100116 ({int})caster->ReceiveMsg(
117 ({string})victim->Name(WER,1)+" laesst sich nicht angreifen!",
heull0018720c5a2018-02-08 12:29:49 +0100118 MT_NOTIFICATION,
119 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200120 return 0;
121 }
bugfixaf2be4f2020-03-22 19:13:07 +0100122 if (({int})victim->QueryProp(P_GHOST)) {
123 ({int})caster->ReceiveMsg(
124 ({string})victim->Name(WER,1)+" ist ein Geist!",
heull0018720c5a2018-02-08 12:29:49 +0100125 MT_NOTIFICATION,
126 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200127 return 0;
128 }
129
bugfixaf2be4f2020-03-22 19:13:07 +0100130 damage=(damage*({int})caster->QuerySkillAttribute(SA_DAMAGE))/100;
MG Mud User88f12472016-06-24 23:31:02 +0200131
132 // ggf. Loggen von PK-Versuchen und ggf. ausserdem Angriff abbrechen.
133 // BTW: Aufruf von InsertEnemy() gibt 0 zurueck, wenn der Gegner nicht
134 // eingetragen wurde. Allerdings tut es das auch, wenn der Gegner schon
135 // Feind war. Daher muss noch geprueft werden, ob nach dem InsertEnemy() die
136 // beiden Spieler Feinde sind. Wenn nicht, wird der Spell auch abgebrochen
137 // und eine Meldung ausgegeben.
138 if (query_once_interactive(caster) && query_once_interactive(victim)
139 && CheckPlayerAttack(caster, victim,
140 sprintf("Spellbook: %O, Spell: %O\n",
141 object_name(this_object()),
142 ((mappingp(sinfo) && is_spell) ? sinfo[SP_NAME] : "")))
bugfixaf2be4f2020-03-22 19:13:07 +0100143 && !({int})caster->IsEnemy(victim)
144 && !({int})caster->InsertEnemy(victim)
MG Mud User88f12472016-06-24 23:31:02 +0200145 )
146 {
147 tell_object(ME, "Ein goettlicher Einfluss schuetzt Deinen Gegner.\n");
148 return 0;
149 }
150
bugfixaf2be4f2020-03-22 19:13:07 +0100151 nomag=({int})victim->QueryProp(P_NOMAGIC);
MG Mud User88f12472016-06-24 23:31:02 +0200152 if ((sinfo[SI_NOMAGIC] < nomag) &&
bugfixaf2be4f2020-03-22 19:13:07 +0100153 nomag*100 > random(100)*({int})caster->QuerySkillAttribute(SA_ENEMY_SAVE)) {
154 printf("%s wehrt Deinen Zauber ab.\n", capitalize(({string})victim->name(WER, 1)));
MG Mud User88f12472016-06-24 23:31:02 +0200155 return 0;
156 }
157 else {
bugfixaf2be4f2020-03-22 19:13:07 +0100158 return ({int})victim->Defend(damage, dtypes, is_spell, caster);
MG Mud User88f12472016-06-24 23:31:02 +0200159 }
160}
161
162varargs int
163TryDefaultAttackSpell(object victim, object caster, mapping sinfo,
164 mixed si_spell) {
MG Mud User88f12472016-06-24 23:31:02 +0200165 int row;
166
167 if (!si_spell) si_spell=sinfo[SI_SPELL];
168 // Wenn der Spieler in einem Team ist, die Teamreihen-Boni
169 // beruecksichtigen. Wenn nicht, eben nicht.
Arathornb3051452021-05-13 21:13:03 +0200170 if (!({object})caster->QueryProp(P_TEAM))
MG Mud User88f12472016-06-24 23:31:02 +0200171 return TryAttackSpell(victim,
172 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster),
173 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
174 si_spell,
175 caster,
176 sinfo);
177 else
178 {
bugfixaf2be4f2020-03-22 19:13:07 +0100179 row=({int})caster->PresentPosition();
MG Mud User88f12472016-06-24 23:31:02 +0200180 return TryAttackSpell(victim,
181 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)+
182 // Nur wenn SI_SKILLDAMAGE_BY_ROW ein mapping ist,
183 // randomisiert addieren
184 (mappingp(sinfo[SI_SKILLDAMAGE_BY_ROW])?
185 random(sinfo[SI_SKILLDAMAGE_BY_ROW][row]):0)+
186 // Nur wenn das OFFSET davon ein mapping ist,
187 // nicht randomisiert addieren.
188 (mappingp(sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)])?
189 sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)][row] : 0),
190 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
191 si_spell,caster,sinfo);
192 }
193}
194
195#define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y))
196int
197SpellSuccess(object caster, mapping sinfo) {
198 int cval,abil,res;
199
200 abil=sinfo[SI_SKILLABILITY];
bugfixaf2be4f2020-03-22 19:13:07 +0100201 cval=({int})caster->UseSkill(SK_CASTING);
MG Mud User88f12472016-06-24 23:31:02 +0200202 res=abil + (SMUL(abil,cval)) / MAX_ABILITY - random(MAX_ABILITY);
203 if (cval && res>MAX_ABILITY) // besonders gut gelungen?
bugfixaf2be4f2020-03-22 19:13:07 +0100204 ({void})caster->LearnSkill(SK_CASTING,1+(res-MAX_ABILITY)/2000);
MG Mud User88f12472016-06-24 23:31:02 +0200205 return res;
206}
207
208int
209CanTrySpell(object caster, mapping sinfo) {
bugfixaf2be4f2020-03-22 19:13:07 +0100210 if (({int})caster->QueryProp(P_GHOST)) {
211 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100212 "Als Geist kannst Du nicht zaubern.",
213 MT_NOTIFICATION,
214 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200215 return 0;
216 }
Zesstra6a8465b2018-02-13 20:44:51 +0100217 string res;
218 mapping rmap=sinfo[SI_SKILLRESTR_USE];
219 if (mappingp(rmap)
Arathorne7dbb072018-02-10 20:07:23 +0100220 && (res=check_restrictions(caster,rmap)))
221 {
bugfixaf2be4f2020-03-22 19:13:07 +0100222 ({int})caster->ReceiveMsg(res, MT_NOTIFICATION|MSG_DONT_WRAP, MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200223 return 0;
224 }
225 return 1;
226}
227
228void
229Learn(object caster, string spell, mapping sinfo) {
230 int val,diff,attval;
231 mapping attr;
232
233 // gar nicht lernen?
234 if (sinfo[SI_NO_LEARN])
235 return;
236
237 // Attribut sollte int sein, wenn nicht anders angegeben
238 if (mappingp(sinfo[SI_LEARN_ATTRIBUTE]))
239 {
240 attr=sinfo[SI_LEARN_ATTRIBUTE];
241 // Direkt in einem durch berechnen.
242 // Ich gehe davon aus, dass wir nie nie nie
243 // ein weiteres Attribut ins MG einfuegen.
244 // Wir berechnen den Mittelwert
bugfixaf2be4f2020-03-22 19:13:07 +0100245 attval=(({int})caster->QueryAttribute(A_INT)*attr[A_INT]+
246 ({int})caster->QueryAttribute(A_DEX)*attr[A_DEX]+
247 ({int})caster->QueryAttribute(A_STR)*attr[A_STR]+
248 ({int})caster->QueryAttribute(A_CON)*attr[A_CON])
MG Mud User88f12472016-06-24 23:31:02 +0200249 /(attr[A_CON]+attr[A_INT]+attr[A_STR]+attr[A_DEX]);
250
251
252 } else {
bugfixaf2be4f2020-03-22 19:13:07 +0100253 attval=({int})caster->QueryAttribute(A_INT);
MG Mud User88f12472016-06-24 23:31:02 +0200254 }
255
256
257 val=((attval/2)*GetFValue(SI_SKILLLEARN,sinfo,caster)+
258 GetOffset(SI_SKILLLEARN,sinfo,caster));
259 if (!(diff=GetFValueO(SI_DIFFICULTY,sinfo,caster)))
260 diff=GetFValueO(SI_SPELLCOST,sinfo,caster);
bugfixaf2be4f2020-03-22 19:13:07 +0100261 ({void})caster->LearnSkill(spell,val,diff);
MG Mud User88f12472016-06-24 23:31:02 +0200262}
263
264void
265Erfolg(object caster, string spell, mapping sinfo) {
266 object env;
267 if(env=environment(caster))
bugfixaf2be4f2020-03-22 19:13:07 +0100268 ({void})env->SpellInform(caster,spell,sinfo);
MG Mud User88f12472016-06-24 23:31:02 +0200269}
270
271void
272Misserfolg(object caster, string spell, mapping sinfo) {
bugfixaf2be4f2020-03-22 19:13:07 +0100273 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100274 "Der Zauberspruch ist missglueckt.\n"
275 "Du lernst aus Deinem Fehler.\n",
276 MT_NOTIFICATION|MSG_DONT_WRAP,
277 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200278 Learn(caster,spell,sinfo);
279}
280
281string
282SelectSpell(string spell, mapping sinfo) {
283 if (sinfo && sinfo[SI_SKILLFUNC])
284 return sinfo[SI_SKILLFUNC];
285 return spell;
286}
287
288varargs void
289prepare_spell(object caster, string spell, mapping sinfo) {
290 string txt;
291
292 if (!caster || !spell) return ;
293 if (!mappingp(sinfo) || !stringp(txt=sinfo[SI_PREPARE_MSG]))
294 txt="Du bereitest Dich auf den Spruch ``%s'' vor.\n";
295 tell_object(caster,sprintf(txt,spell));
296}
297
298varargs int
299UseSpell(object caster, string spell, mapping sinfo) {
300 mapping ski,tmp;
Arathornb3051452021-05-13 21:13:03 +0200301 string sname,fname,txt;
MG Mud User88f12472016-06-24 23:31:02 +0200302 int res,fat,cost;
303 mixed ps;
304
305 if (!caster || !spell)
306 return 0;
307 // Spell kann in der Gilde anderen Namen haben
308 sname=SelectSpell(spell,sinfo);
309 if (!(ski=QuerySpell(sname))) // Existiert dieser Spell?
310 return 0;
311 // Gildeneigenschaften sollen Spelleigenschaften ueberschreiben koennen
312 ski=AddSkillMappings(ski,sinfo);
313 // Fuer verschiedene Rassen unterschiedliche Eigenschaften
314 ski=race_modifier(caster,ski);
315
316 // printf("Spellinfo: %O\n",ski);
317
318 if (!CanTrySpell(caster, ski))
319 return 1;
320
bugfixaf2be4f2020-03-22 19:13:07 +0100321 if (({int})caster->QueryProp(P_SP) < (cost=GetFValueO(SI_SPELLCOST,ski,caster))) {
heull0018720c5a2018-02-08 12:29:49 +0100322 if(txt=ski[SI_SP_LOW_MSG])
bugfixaf2be4f2020-03-22 19:13:07 +0100323 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100324 txt,
325 MT_NOTIFICATION,
326 MA_SPELL);
327 else
bugfixaf2be4f2020-03-22 19:13:07 +0100328 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100329 "Du hast zu wenig Zauberpunkte fuer diesen Spruch.",
330 MT_NOTIFICATION,
331 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200332 return 1;
333 }
334 // printf("cost: %d\n",cost);
335
336 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
337 // fuer jeden Key die Spellfatigue pruefen, wenn das Mapping hinterher
338 // nicht leer ist, ist man zu erschoepft.
339 tmp = filter(ski[SI_X_SPELLFATIGUE],
340 function int (string key, int val)
bugfixaf2be4f2020-03-22 19:13:07 +0100341 { return ({int})caster->CheckSpellFatigue(key); } );
MG Mud User88f12472016-06-24 23:31:02 +0200342 if (sizeof(tmp)) {
bugfixaf2be4f2020-03-22 19:13:07 +0100343 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100344 ski[SI_TIME_MSG] ||
345 "Du bist noch zu erschoepft von Deinem letzten Spruch.",
346 MT_NOTIFICATION,
347 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200348 return 1;
349 }
350 }
351 else {
bugfixaf2be4f2020-03-22 19:13:07 +0100352 if (({int})caster->CheckSpellFatigue()) {
353 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100354 ski[SI_TIME_MSG] ||
355 "Du bist noch zu erschoepft von Deinem letzten Spruch.",
356 MT_NOTIFICATION,
357 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200358 return 1;
359 }
360 }
361
Zesstraaeee4df2021-05-03 13:48:09 +0200362 // Auf P_NO_ATTACK pruefen (Default), es sei denn, das ist im Skill nicht
363 // gewuenscht.
MG Mud User88f12472016-06-24 23:31:02 +0200364 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY) &&
bugfixaf2be4f2020-03-22 19:13:07 +0100365 ({int})caster->QueryProp(P_ATTACK_BUSY)) {
heull0018720c5a2018-02-08 12:29:49 +0100366 if (txt=ski[SI_ATTACK_BUSY_MSG])
bugfixaf2be4f2020-03-22 19:13:07 +0100367 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100368 txt,
369 MT_NOTIFICATION,
370 MA_SPELL);
371 else
bugfixaf2be4f2020-03-22 19:13:07 +0100372 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100373 "Du bist schon zu sehr beschaeftigt.",
374 MT_NOTIFICATION,
375 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200376 return 1;
377 }
378
379 // Spruchvorbereitung
bugfixaf2be4f2020-03-22 19:13:07 +0100380 if (pointerp(ps=({<int|mapping|string>*})caster->QueryProp(P_PREPARED_SPELL)) // Ausstehender Spruch
MG Mud User88f12472016-06-24 23:31:02 +0200381 && sizeof(ps)>=3 && intp(ps[0] && stringp(ps[1]))) {
382 if (ps[1]==spell) { // Dieser Spruch wird noch vorbereitet
383 if (time()<ps[0]) {
384 if (!stringp(txt=ski[SI_PREPARE_BUSY_MSG]))
385 txt="Du bist noch mit der Spruchvorbereitung beschaeftigt.\n";
bugfixaf2be4f2020-03-22 19:13:07 +0100386 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100387 txt,
388 MT_NOTIFICATION,
389 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200390 return 1;
391 }
392 }
393 else { // Andere Sprueche brechen die Vorbereitung ab
394 if (!mappingp(tmp=QuerySpell(ps[1])) || // richtige Meldung holen...
395 !stringp(txt=tmp[SI_PREPARE_ABORT_MSG]))
396 txt="Du brichst die Spruchvorbereitung fuer `%s' ab.\n";
397 printf(txt,ps[1]);
398 if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) {
399 // Spruch braucht vorbereitungszeit
bugfixaf2be4f2020-03-22 19:13:07 +0100400 ({<int|mapping|string>*})caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
MG Mud User88f12472016-06-24 23:31:02 +0200401 prepare_spell(caster,spell,ski);
402 return 1;
403 }
404 }
405 }
406 else {
407 if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) {
408 // Spruch braucht vorbereitungszeit
bugfixaf2be4f2020-03-22 19:13:07 +0100409 ({<int|mapping|string>*})caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
MG Mud User88f12472016-06-24 23:31:02 +0200410 prepare_spell(caster,spell,ski);
411 return 1;
412 }
413 }
414 if (ps)
bugfixaf2be4f2020-03-22 19:13:07 +0100415 ({<int|mapping|string>*})caster->SetProp(P_PREPARED_SPELL,0);
MG Mud User88f12472016-06-24 23:31:02 +0200416
417 // Funktion kann anderen Namen haben als Spell
418 if (!(fname=sinfo[SI_SKILLFUNC]))
419 fname=sname;
420
bugfixaf2be4f2020-03-22 19:13:07 +0100421 if((ski[SI_NOMAGIC] < ({int})environment(caster)->QueryProp(P_NOMAGIC)) &&
422 random(100) < ({int})environment(caster)->QueryProp(P_NOMAGIC)) {
MG Mud User88f12472016-06-24 23:31:02 +0200423 if (txt=ski[SI_NOMAGIC_MSG])
bugfixaf2be4f2020-03-22 19:13:07 +0100424 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100425 txt,
426 MT_NOTIFICATION,
427 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200428 else
bugfixaf2be4f2020-03-22 19:13:07 +0100429 ({int})caster->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100430 "Dein Zauberspruch verpufft im Nichts.",
431 MT_NOTIFICATION,
432 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200433 res=ABGEWEHRT;
434 }
435 else {
436 // Spruch ausfuehren.
Bugfix19646f82020-04-26 16:54:13 +0200437 res=funcall(symbol_function(fname,ME),caster,ski);
MG Mud User88f12472016-06-24 23:31:02 +0200438 }
Gloinsoncd564772016-08-03 21:57:12 +0200439 if (!res || !caster)
MG Mud User88f12472016-06-24 23:31:02 +0200440 return 1;
MG Mud User88f12472016-06-24 23:31:02 +0200441
Bugfixdd8ce3f2016-08-13 19:54:02 +0200442 if(res==NICHT_ZUSTAENDIG)
443 return 0;
444
Zesstraaeee4df2021-05-03 13:48:09 +0200445 // P_NO_ATTACK setzen (Default), es sei denn, das ist vom Skill nicht
446 // gewuenscht.
447 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_SET))
MG Mud User88f12472016-06-24 23:31:02 +0200448 {
449 if (!ski[SI_ATTACK_BUSY_AMOUNT])
bugfixaf2be4f2020-03-22 19:13:07 +0100450 ({int})caster->SetProp(P_ATTACK_BUSY,1);
MG Mud User88f12472016-06-24 23:31:02 +0200451 else
bugfixaf2be4f2020-03-22 19:13:07 +0100452 ({int})caster->SetProp(P_ATTACK_BUSY,ski[SI_ATTACK_BUSY_AMOUNT]);
MG Mud User88f12472016-06-24 23:31:02 +0200453 }
454
bugfixaf2be4f2020-03-22 19:13:07 +0100455 ({void})caster->restore_spell_points(-1*cost);
MG Mud User88f12472016-06-24 23:31:02 +0200456
457 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
458 // fuer jeden Key die Spellfatigue setzen. Keys mit Dauer 0 loesen keine
459 // Spellfatigue aus.
460 filter(ski[SI_X_SPELLFATIGUE],
461 function int (string key, int val)
bugfixaf2be4f2020-03-22 19:13:07 +0100462 { return ({int})caster->SetSpellFatigue(val, key); } );
MG Mud User88f12472016-06-24 23:31:02 +0200463 }
464 else {
465 if ((fat=GetFValueO(SI_SPELLFATIGUE,ski,caster))<0)
466 fat=1;
bugfixaf2be4f2020-03-22 19:13:07 +0100467 ({int})caster->SetSpellFatigue(fat);
MG Mud User88f12472016-06-24 23:31:02 +0200468 }
469
470
471 if (res==ERFOLG)
472 Erfolg(caster,spell,ski);
473 else
474 if (res==MISSERFOLG)
475 Misserfolg(caster,spell,ski);
476 return 1;
477}
478
479object *
480FindGroup(object pl, int who) {
481 object *res,ob,env,team;
482 int p1,p2;
483 closure qp;
484
485 res=({});
486 if (!pl || !(env=environment(pl))) return res;
487 p1 = query_once_interactive(pl) ? 1 : -1;
bugfixaf2be4f2020-03-22 19:13:07 +0100488 team=({object})pl->QueryProp(P_TEAM);
MG Mud User88f12472016-06-24 23:31:02 +0200489 for (ob=first_inventory(env);ob;ob=next_inventory(ob)) {
490 if (!living(ob)) continue;
491 qp=symbol_function("QueryProp",ob);
bugfixaf2be4f2020-03-22 19:13:07 +0100492 if (({int})pl->IsEnemy(ob)) // Feinde sind immer Gegner
MG Mud User88f12472016-06-24 23:31:02 +0200493 p2=-1*p1;
494 else if (objectp(team) && funcall(qp,P_TEAM)==team)
495 p2=p1; // Teammitglieder sind immer auf Seite des Spielers
496 else
497 p2 = (query_once_interactive(ob)||funcall(qp,P_FRIEND)) ? 1 : -1;
498 if (p2>0 && !interactive(ob) && query_once_interactive(ob))
499 continue; // keine Netztoten.
500 if (funcall(qp,P_GHOST))
501 continue;
502 if ( who<0 && (funcall(qp,P_NO_ATTACK) || funcall(qp,P_NO_GLOBAL_ATTACK)) )
503 continue;
504 if (IS_LEARNING(ob) &&
bugfixaf2be4f2020-03-22 19:13:07 +0100505 (funcall(qp,P_INVIS) || (who<0 && !({int})pl->IsEnemy(ob))))
MG Mud User88f12472016-06-24 23:31:02 +0200506 continue;
507 if (p1*p2*who >=0)
508 res+=({ob});
509 }
510 return res;
511}
512
513object *
514FindGroupN(object pl, int who, int n) {
515 if (!pl) return ({});
bugfixaf2be4f2020-03-22 19:13:07 +0100516 n=(n*({int})pl->QuerySkillAttribute(SA_EXTENSION))/100;
MG Mud User88f12472016-06-24 23:31:02 +0200517 if (n<1) n=1;
518 return FindGroup(pl,who)[0..(n-1)];
519}
520
521object *
522FindGroupP(object pl, int who, int pr) {
523 object *res,*nres;
524 int i;
525
526 nres=({});
527 if (!pl) return nres;
bugfixaf2be4f2020-03-22 19:13:07 +0100528 pr=(pr*({int})pl->QuerySkillAttribute(SA_EXTENSION))/100;
MG Mud User88f12472016-06-24 23:31:02 +0200529 if (pr<0) return nres;
530 res=FindGroup(pl,who);
531 for (i=sizeof(res)-1;i>=0;i--)
532 if (pr>=random(100))
533 nres+=({res[i]});
534 return nres;
535}
536
537// Findet feindliche und freundliche GRUPPEN im angegebenen Bereich.
538varargs mixed
539FindDistantGroups(object pl, int dist, int dy, int dx) {
540 mapping pos;
541 object ob,enteam,myteam;
542 int p1,min,max,i;
543 mixed *b_rows,x;
544 closure is_enemy, qp;
545
546 if (!objectp(pl) || !environment(pl))
547 return ({({}),({})});
548 if (!dy) dy=100;
549 if (!dx) dx=MAX_TEAM_ROWLEN*100;
bugfixaf2be4f2020-03-22 19:13:07 +0100550 x=({int})pl->QuerySkillAttribute(SA_EXTENSION);
MG Mud User88f12472016-06-24 23:31:02 +0200551 dx=(dx*x)/100;dy=(dy*x)/100;
bugfixaf2be4f2020-03-22 19:13:07 +0100552 dist=(dist*({int})pl->QuerySkillAttribute(SA_RANGE))/100;
MG Mud User88f12472016-06-24 23:31:02 +0200553 min=dist-dy/2;
554 max=dist+dy/2;
555
556 pos=([]);
557 p1=query_once_interactive(pl) ? 1 : -1;
558 is_enemy=symbol_function("IsEnemy",pl); // zur Beschleunigung
bugfixaf2be4f2020-03-22 19:13:07 +0100559 myteam=({object})pl->QueryProp(P_TEAM);
MG Mud User88f12472016-06-24 23:31:02 +0200560 for (ob=first_inventory(environment(pl));ob;ob=next_inventory(ob)) {
561 if (!living(ob)) continue;
562 qp=symbol_function("QueryProp",ob); // zur Beschleunigung
563
564 // Zuerst mal die Position feststellen:
565 if (!objectp(enteam=funcall(qp,P_TEAM)))
566 pos[ob]=1;
bugfixaf2be4f2020-03-22 19:13:07 +0100567 else if (!pos[ob] && mappingp(x=({mapping})ob->PresentTeamPositions()))
MG Mud User88f12472016-06-24 23:31:02 +0200568 pos+=x;
569 // PresentTeamPositions wird nur einmal pro Team ausgerechnet, weil
570 // anschliessend jedes anwesende Teammitglied pos[ob]!=0 hat.
571
572 pos[ob]=(2*pos[ob])-1;
573 // Reihen sollen Abstand 2 haben, auch Reihen 1 und -1 (nach Umrechnung)
574
575 // Feindliche Reihen an Y-Achse spiegeln, also gegenueber hinstellen:
576 if (funcall(is_enemy,ob))
577 pos[ob]*=-1; // Ist auf jeden Fall Feind
578 else if (objectp(myteam) && myteam==enteam)
579 ; // Teammitglieder sind immer auf eigener Seite
580 else
bugfixaf2be4f2020-03-22 19:13:07 +0100581 pos[ob]*=(p1*((query_once_interactive(ob)||
MG Mud User88f12472016-06-24 23:31:02 +0200582 funcall(qp,P_FRIEND))?1:-1));
583
584 // Den Spieler auf keinen Fall entfernen
585 if (ob==pl)
586 continue;
587 // Netztote, Geister und unsichtbare Magier nicht beruecksichtigen,
588 // nicht (global) angreifbare Monster und nicht-feindliche Magier
589 // von feindlicher Seite entfernen.
590 if ((!interactive(ob) && query_once_interactive(ob)) // Netztote raus
591 || funcall(qp,P_GHOST)
592 || (IS_LEARNING(ob) && funcall(qp,P_INVIS)) // Bin nicht da :-)
593 || (pos[ob]<0 && (funcall(qp,P_NO_GLOBAL_ATTACK) // Nicht angreifen
594 || funcall(qp,P_NO_ATTACK)
595 || (IS_LEARNING(ob) && !funcall(is_enemy,ob)))))
596 m_delete(pos,ob);
597 }
598
599 // Reihen aufstellen
600 // Kampfreihe: | 5 4 3 2 1 || 1 2 3 4 5|
601 // Arrays: |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19|
602 // |<----------- Freunde ------->||<--------- Feinde -------->|
603 b_rows=EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY;
604 x=m_indices(pos);
605 for (i=sizeof(x)-1;i>=0;i--) {
606 p1=2*MAX_TEAMROWS-pos[ob=x[i]];
607 pos[ob]=p1;
608 if (p1>=0 && p1<4*MAX_TEAMROWS) {
609 if (random(100)<50) // Damit gut gemischt wird...
610 b_rows[p1]=b_rows[p1]+({ob}); // zufaellig hinten anfuegen
611 else
612 b_rows[p1]=({ob})+b_rows[p1]; // oder vorne anfuegen.
613 }
614 }
615
616 // Umrechnung von Min, Max auf Reihen
617 // ... -2: -124..-75, -1:-74..-25, 0:-24..24, 1:25..74 2:75..124 ...
618 // Das muss man machen, wenn man keine vernuenftige Rundungsfunktion hat:
619 if (min<0) min=-1*((25-min)/50); else min=(25+min)/50;
620 if (max<0) max=-1*((25-max)/50); else max=(25+max)/50;
621 // Relativ zur Position des Spielers verschieben:
622 p1=pos[pl];min+=p1;max+=p1;
623 // Umrechnung von Breite auf Anzahl:
624 dx=((dx+50)/100)-1;if (dx<0) dx=0;
625
626 x=({({}),({})});
627 for (i=0;i<2*MAX_TEAMROWS;i++)
628 if (i>=min && i<=max)
629 x[1]+=b_rows[i][0..dx]; // Freunde
630 for (i=2*MAX_TEAMROWS+1;i<4*MAX_TEAMROWS;i++)
631 if (i>=min && i<=max)
632 x[0]+=b_rows[i][0..dx]; // Feinde
633
634 return x;
635}
636
637// Findet feindliche oder freundliche Gruppe (oder Summe beider) im
638// angegebenen Bereich.
639varargs object *
640FindDistantGroup(object pl, int who, int dist, int dy, int dx) {
641 mixed *x;
642
643 x=FindDistantGroups(pl,dist,dy,dx);
644 if (who<0)
645 return x[0];
646 else if (who>0)
647 return x[1];
648 return x[0]+x[1];
649}
650
651
652static varargs object
653find_victim(string wen, object pl) {
654 object victim;
655
656 if (!pl) return 0;
657 if (!sizeof(wen)) {
bugfixaf2be4f2020-03-22 19:13:07 +0100658 if (victim = ({object})pl->SelectEnemy())
MG Mud User88f12472016-06-24 23:31:02 +0200659 return victim;
660 else
661 return 0;
662 }
663 if (victim = present(wen, environment(pl)))
664 return victim;
665 else if (victim = present(wen, pl))
666 return victim;
667 else
668 return 0;
669}
670varargs object
671FindVictim(string wen, object pl, string msg) {
672 object vic;
673
674 if (!(vic=find_victim(wen,pl)) && msg)
bugfixaf2be4f2020-03-22 19:13:07 +0100675 ({int})pl->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100676 msg,
677 MT_NOTIFICATION,
678 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200679 return vic;
680}
681varargs object
682FindLivingVictim(string wen, object pl, string msg) {
683 object vic;
684
685 if (!(vic=FindVictim(wen,pl,msg)))
686 return 0;
bugfixaf2be4f2020-03-22 19:13:07 +0100687 if (!living(vic) || ({int})vic->QueryProp(P_GHOST)) {
Bugfixb5b60a42020-04-18 14:39:03 +0200688 ({int})pl->ReceiveMsg(
Arathornbe9d8982021-02-28 14:49:40 +0100689 ({string})vic->Name()+" lebt doch nicht!",
Bugfixb5b60a42020-04-18 14:39:03 +0200690 MT_NOTIFICATION,
691 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200692 return 0;
693 }
694 return vic;
695}
696
697private varargs object
698DoFindEnemyVictim(string wen, object pl, string msg,
699 mixed func, int min, int max) {
700 object vic;
701 mixed no_attack;
702 int row;
703
704 if (!(vic=FindLivingVictim(wen,pl,msg))) {
705 if ((stringp(wen) && wen!="") || !objectp(pl))
706 return 0;
707 if (pointerp(func)) { // Soll einer DIESER Gegner genommen werden?
bugfixaf2be4f2020-03-22 19:13:07 +0100708 if (!(vic=({object})pl->SelectEnemy(func))) // Dann daraus auswaehlen
MG Mud User88f12472016-06-24 23:31:02 +0200709 return 0;
710 } else {
711 if (!stringp(func))
712 func="SelectEnemy";
bugfixaf2be4f2020-03-22 19:13:07 +0100713 if (!(vic=({object})call_other(pl,func,0,min,max)))
MG Mud User88f12472016-06-24 23:31:02 +0200714 return 0;
715 }
716 func=0; // kein zweites Mal pruefen.
717 }
bugfixaf2be4f2020-03-22 19:13:07 +0100718 if (no_attack = ({int|string})vic->QueryProp(P_NO_ATTACK)) {
MG Mud User88f12472016-06-24 23:31:02 +0200719 if (stringp(no_attack))
bugfixaf2be4f2020-03-22 19:13:07 +0100720 ({int})pl->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100721 no_attack,
722 MT_NOTIFICATION,
723 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200724 else
bugfixaf2be4f2020-03-22 19:13:07 +0100725 ({int})pl->ReceiveMsg(
726 ({string})vic->Name(WER,1)+" laesst sich nicht angreifen.",
heull0018720c5a2018-02-08 12:29:49 +0100727 MT_NOTIFICATION,
728 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200729 return 0;
730 }
731 if (vic==pl) {
bugfixaf2be4f2020-03-22 19:13:07 +0100732 ({int})pl->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100733 "Du koenntest Dir dabei wehtun.",
734 MT_NOTIFICATION,
735 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200736 return 0;
737 }
738 if (stringp(func)) {
739 switch(func) {
740 case "SelectNearEnemy":
bugfixaf2be4f2020-03-22 19:13:07 +0100741 if (({int})pl->PresentPosition()>1) {
742 ({int})pl->ReceiveMsg(
heull0018720c5a2018-02-08 12:29:49 +0100743 "Du stehst nicht in der ersten Kampfreihe.",
744 MT_NOTIFICATION,
745 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200746 return 0;
747 }
bugfixaf2be4f2020-03-22 19:13:07 +0100748 if (({int})vic->PresentPosition()>1) {
749 ({int})pl->ReceiveMsg(
750 ({string})vic->Name(WER,1)+" ist in einer hinteren Kampfreihe.",
heull0018720c5a2018-02-08 12:29:49 +0100751 MT_NOTIFICATION,
752 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200753 return 0;
754 }
755 break;
756 case "SelectFarEnemy":
bugfixaf2be4f2020-03-22 19:13:07 +0100757 if (row=({int})vic->PresentPosition())
MG Mud User88f12472016-06-24 23:31:02 +0200758 row--;
759 if (row>=min && row<=max)
760 break;
761 if (row<min)
bugfixaf2be4f2020-03-22 19:13:07 +0100762 ({int})pl->ReceiveMsg(
763 ({string})vic->Name(WER,1)+" ist zu nahe.",
heull0018720c5a2018-02-08 12:29:49 +0100764 MT_NOTIFICATION,
765 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200766 else if (row>max)
bugfixaf2be4f2020-03-22 19:13:07 +0100767 ({int})pl->ReceiveMsg(
768 ({string})vic->Name(WER,1)+" ist zu weit weg.",
heull0018720c5a2018-02-08 12:29:49 +0100769 MT_NOTIFICATION,
770 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200771 else
bugfixaf2be4f2020-03-22 19:13:07 +0100772 ({int})pl->ReceiveMsg(
773 ({string})vic->Name(WER,1)+" ist unerreichbar.",
heull0018720c5a2018-02-08 12:29:49 +0100774 MT_NOTIFICATION,
775 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200776 return 0;
777 default:;
778 }
779 } else if (pointerp(func)) {
780 if (member(func,vic)<0) {
bugfixaf2be4f2020-03-22 19:13:07 +0100781 ({int})pl->ReceiveMsg(
782 ({string})vic->Name(WER,1)+" ist unerreichbar.",
heull0018720c5a2018-02-08 12:29:49 +0100783 MT_NOTIFICATION,
784 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200785 return 0;
786 }
787 }
788
bugfixaf2be4f2020-03-22 19:13:07 +0100789 if (!({int})pl->IsEnemy(vic)) // War es bisher kein Feind?
790 ({int})pl->Kill(vic); // Dann ist es jetzt einer.
MG Mud User88f12472016-06-24 23:31:02 +0200791 return vic;
792}
793
794varargs object
795FindEnemyVictim(string wen, object pl, string msg) {
796 return DoFindEnemyVictim(wen,pl,msg,"SelectEnemy");
797}
798
799// Wie FindEnemyVictim, aber nur im Nahkampf erreichbare Feinde werden
800// gefunden.
801varargs object
802FindNearEnemyVictim(string wen, object pl, string msg) {
803 return DoFindEnemyVictim(wen,pl,msg,"SelectNearEnemy");
804}
805
806// Wie FindEnemyVictim, aber nur Feinde im Bereich der angegebenen Reihen
807// (min,max) werden gefunden.
808varargs object
809FindFarEnemyVictim(string wen, object pl, string msg,
810 int min, int max) {
811 return DoFindEnemyVictim(wen,pl,msg,"SelectFarEnemy",min,max);
812}
813
814// Wie FindEnemyVictim, findet aber nur Feinde in
815// FindDistantGroup(GEGNER,entfernung,abweichung)
816varargs object
817FindDistantEnemyVictim(string wen, object pl, string msg,
818 int dist, int dy) {
819 return DoFindEnemyVictim(wen,pl,msg,
820 FindDistantGroup(pl,-1,dist,dy,10000));
821}
822
823varargs int
824TryGlobalAttackSpell(object caster, mapping sinfo, int suc,
825 int damage, mixed dt, mixed is_spell,
826 int dist, int depth, int width) {
827 int i;
828 mixed x,coldam;
829 object *obs,ob;
830
831 if (!suc) suc=random(sinfo[SI_SKILLABILITY]);
832 if (!dt) dt=GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster);
833 if (!is_spell) is_spell=GetData(SI_SPELL,sinfo,caster);
834 if (!dist) dist=GetRandFValueO(SI_DISTANCE,sinfo,caster);
835 if (!depth) depth=GetRandFValueO(SI_DEPTH,sinfo,caster);
836 if (!width) width=GetRandFValueO(SI_WIDTH,sinfo,caster);
837
838 if (!depth && width) depth=width;
839 if (!width && depth) width=depth;
840 if (!mappingp(is_spell)) is_spell=([]);
841 is_spell[SP_GLOBAL_ATTACK]=1;
842
843 x=FindDistantGroups(caster,dist,depth,width);
844 sinfo[SI_NUMBER_ENEMIES]=sizeof(x[0]);
845 sinfo[SI_NUMBER_FRIENDS]=sizeof(x[1]);
846
847 obs=x[0];
848 for (i=sizeof(obs)-1;i>=0;i--)
bugfixaf2be4f2020-03-22 19:13:07 +0100849 if (objectp(ob=obs[i]) && suc>=({int})ob->SpellDefend(caster,sinfo))
MG Mud User88f12472016-06-24 23:31:02 +0200850 TryAttackSpell(ob,(damage?random(damage):
851 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)),
852 dt,is_spell,caster,sinfo);
853
854 if (!intp(coldam=sinfo[SI_COLLATERAL_DAMAGE]) || !coldam)
855 return 1;
856 obs=x[1];
857 for (i=sizeof(obs)-1;i>=0;i--)
bugfixaf2be4f2020-03-22 19:13:07 +0100858 if (objectp(ob=obs[i]) && suc>=({int})ob->SpellDefend(caster,sinfo))
859 ({int})ob->reduce_hit_points(((damage?random(damage):
MG Mud User88f12472016-06-24 23:31:02 +0200860 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster))
861 *coldam)/10);
862 // 10 statt 100 ist Absicht, weil reduce_hit_points schon um Faktor
863 // 10 staerker wirkt als Defend.
864
865 return 1;
866}