blob: 24f824fc41d65dcb09ded38bdae44ec7aec0e5bb [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
10#pragma pedantic
11#pragma range_check
12
13//#define NEED_PROTOTYPES
14inherit "/std/thing";
15inherit "/std/restriction_checker";
16inherit "/std/player/pklog";
17
18#include <thing/properties.h>
19#include <properties.h>
20#include <wizlevels.h>
21#include <new_skills.h>
22#include <spellbook.h>
23#define ME this_object()
24
25void create() {
26 seteuid(getuid());
27 // diese Prop sollte von aussen nicht direkt geaendert werden koennen.
28 Set(P_SB_SPELLS,([]),F_VALUE);
29 Set(P_SB_SPELLS,PROTECTED,F_MODE_AS);
30
31 ::create();
32}
33
34mapping _query_sb_spells() {
35 // Kopie liefern, da sonst versehentlich schnell eine Aenderung des
36 // originalen Mappings hier passieren kann.
37 return(deep_copy(Query(P_SB_SPELLS, F_VALUE)));
38}
39
40mapping QuerySpell(string spell) {
41 mapping spi;
42
43 //Query-Methode umgehen, damit nur ein Spellmapping kopiert werden muss und
44 //nicht das ganzen Mapping mit allen Spells.
45 if (!spell
46 || !(spi=Query(P_SB_SPELLS))
47 || !(spi=spi[spell]))
48 return 0;
49 return deep_copy(spi);
50}
51
52varargs int
53AddSpell(string verb, int kosten, mixed ski) {
54 int level;
55 mapping spells;
56
57 if (!verb || kosten<0)
58 return 0;
59
60 verb=lower_case(verb);
61 level=(intp(ski))?ski:0;
62
63 if (!mappingp(ski)) ski=([]);
64 // Zur Sicherheit eine Kopie machen, wer weiss, was da uebergeben wird, bzw.
65 // was der Setzende mit ski hinterher noch macht.
66 else ski=deep_copy(ski);
67
68 ski=AddSkillMappings(QueryProp(P_GLOBAL_SKILLPROPS),ski);
69 ski+=([SI_SPELLCOST:kosten]);
70 // SI_SPELL-Submapping hinzufuegen, wenn nicht da.
71 if (!member(ski,SI_SPELL))
72 ski+=([SI_SPELL:([]) ]);
73 // ski[SI_SPELL][SP_NAME] ergaenzen, ggf. aus ski verschieben
74 if (!stringp(ski[SI_SPELL][SP_NAME]) && stringp(ski[SP_NAME])) {
75 ski[SI_SPELL][SP_NAME] = ski[SP_NAME];
76 m_delete(ski, SP_NAME);
77 }
78 else if (!stringp(ski[SI_SPELL][SP_NAME])) {
79 ski[SI_SPELL][SP_NAME] = verb;
80 }
81 if (stringp(ski[SP_NAME])) {
82 m_delete(ski, SP_NAME);
83 }
84
85 if (level)
86 ski=AddSkillMappings(ski,([SI_SKILLRESTR_LEARN:([P_LEVEL:level])]));
87
88 // Abfrage per Query(), um das Mapping als Referenz, nicht als Kopie zu
89 // bekommen, das spart etwas Zeit.
90 if (!(spells=Query(P_SB_SPELLS,F_VALUE))) {
91 spells=([]);
92 SetProp(P_SB_SPELLS,spells);
93 }
94 // Spell setzen...
95 spells[verb]=ski;
96 // Set() nicht noetig, da Query() an spell das Originalmapping geliefert hat
97 // und dieses bereits jetzt geaendert wurde. ;-)
98 //Set(P_SB_SPELLS,spells+([verb:ski]),F_VALUE);
99
100 // hat wohl geklappt...
101 return(1);
102}
103
104int
105TryAttackSpell(object victim, int damage, mixed dtypes,
106 mixed is_spell, object caster, mapping sinfo) {
107 mixed no_attack;
108 int nomag;
109
110 if (no_attack = (mixed)victim->QueryProp(P_NO_ATTACK)) {
111 if (stringp(no_attack))
112 write(no_attack);
113 else
114 write(victim->Name(WER,1)+" laesst sich nicht angreifen!\n");
115 return 0;
116 }
117 if (victim->QueryProp(P_GHOST)) {
118 write(victim->Name(WER,1)+" ist ein Geist!\n");
119 return 0;
120 }
121
122 damage=(damage*(int)caster->QuerySkillAttribute(SA_DAMAGE))/100;
123
124 // ggf. Loggen von PK-Versuchen und ggf. ausserdem Angriff abbrechen.
125 // BTW: Aufruf von InsertEnemy() gibt 0 zurueck, wenn der Gegner nicht
126 // eingetragen wurde. Allerdings tut es das auch, wenn der Gegner schon
127 // Feind war. Daher muss noch geprueft werden, ob nach dem InsertEnemy() die
128 // beiden Spieler Feinde sind. Wenn nicht, wird der Spell auch abgebrochen
129 // und eine Meldung ausgegeben.
130 if (query_once_interactive(caster) && query_once_interactive(victim)
131 && CheckPlayerAttack(caster, victim,
132 sprintf("Spellbook: %O, Spell: %O\n",
133 object_name(this_object()),
134 ((mappingp(sinfo) && is_spell) ? sinfo[SP_NAME] : "")))
135 && !caster->IsEnemy(victim)
136 && !caster->InsertEnemy(victim)
137 )
138 {
139 tell_object(ME, "Ein goettlicher Einfluss schuetzt Deinen Gegner.\n");
140 return 0;
141 }
142
143 nomag=(int)victim->QueryProp(P_NOMAGIC);
144 if ((sinfo[SI_NOMAGIC] < nomag) &&
145 nomag*100 > random(100)*(int)caster->QuerySkillAttribute(SA_ENEMY_SAVE)) {
146 printf("%s wehrt Deinen Zauber ab.\n", capitalize((string)victim->name(WER, 1)));
147 return 0;
148 }
149 else {
150 return (int)victim->Defend(damage, dtypes, is_spell, caster);
151 }
152}
153
154varargs int
155TryDefaultAttackSpell(object victim, object caster, mapping sinfo,
156 mixed si_spell) {
157 object team;
158 int row;
159
160 if (!si_spell) si_spell=sinfo[SI_SPELL];
161 // Wenn der Spieler in einem Team ist, die Teamreihen-Boni
162 // beruecksichtigen. Wenn nicht, eben nicht.
163 if (!team=((object)caster->QueryProp(P_TEAM)))
164 return TryAttackSpell(victim,
165 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster),
166 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
167 si_spell,
168 caster,
169 sinfo);
170 else
171 {
172 row=(int)caster->PresentPosition();
173 return TryAttackSpell(victim,
174 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)+
175 // Nur wenn SI_SKILLDAMAGE_BY_ROW ein mapping ist,
176 // randomisiert addieren
177 (mappingp(sinfo[SI_SKILLDAMAGE_BY_ROW])?
178 random(sinfo[SI_SKILLDAMAGE_BY_ROW][row]):0)+
179 // Nur wenn das OFFSET davon ein mapping ist,
180 // nicht randomisiert addieren.
181 (mappingp(sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)])?
182 sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)][row] : 0),
183 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
184 si_spell,caster,sinfo);
185 }
186}
187
188#define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y))
189int
190SpellSuccess(object caster, mapping sinfo) {
191 int cval,abil,res;
192
193 abil=sinfo[SI_SKILLABILITY];
194 cval=(int)caster->UseSkill(SK_CASTING);
195 res=abil + (SMUL(abil,cval)) / MAX_ABILITY - random(MAX_ABILITY);
196 if (cval && res>MAX_ABILITY) // besonders gut gelungen?
197 caster->LearnSkill(SK_CASTING,1+(res-MAX_ABILITY)/2000);
198 return res;
199}
200
201int
202CanTrySpell(object caster, mapping sinfo) {
203 mapping rmap;
204 string res;
205
206 if (caster->QueryProp(P_GHOST)) {
207 write("Als Geist kannst Du nicht zaubern.\n");
208 return 0;
209 }
210 if ((rmap=sinfo[SI_SKILLRESTR_USE])
211 && (res=check_restrictions(caster,rmap))) {
212 write(res);
213 return 0;
214 }
215 return 1;
216}
217
218void
219Learn(object caster, string spell, mapping sinfo) {
220 int val,diff,attval;
221 mapping attr;
222
223 // gar nicht lernen?
224 if (sinfo[SI_NO_LEARN])
225 return;
226
227 // Attribut sollte int sein, wenn nicht anders angegeben
228 if (mappingp(sinfo[SI_LEARN_ATTRIBUTE]))
229 {
230 attr=sinfo[SI_LEARN_ATTRIBUTE];
231 // Direkt in einem durch berechnen.
232 // Ich gehe davon aus, dass wir nie nie nie
233 // ein weiteres Attribut ins MG einfuegen.
234 // Wir berechnen den Mittelwert
235 attval=((int)caster->QueryAttribute(A_INT)*attr[A_INT]+
236 (int)caster->QueryAttribute(A_DEX)*attr[A_DEX]+
237 (int)caster->QueryAttribute(A_STR)*attr[A_STR]+
238 (int)caster->QueryAttribute(A_CON)*attr[A_CON])
239 /(attr[A_CON]+attr[A_INT]+attr[A_STR]+attr[A_DEX]);
240
241
242 } else {
243 attval=(int)caster->QueryAttribute(A_INT);
244 }
245
246
247 val=((attval/2)*GetFValue(SI_SKILLLEARN,sinfo,caster)+
248 GetOffset(SI_SKILLLEARN,sinfo,caster));
249 if (!(diff=GetFValueO(SI_DIFFICULTY,sinfo,caster)))
250 diff=GetFValueO(SI_SPELLCOST,sinfo,caster);
251 caster->LearnSkill(spell,val,diff);
252}
253
254void
255Erfolg(object caster, string spell, mapping sinfo) {
256 object env;
257 if(env=environment(caster))
258 env->SpellInform(caster,spell,sinfo);
259}
260
261void
262Misserfolg(object caster, string spell, mapping sinfo) {
263 write("Der Zauberspruch ist missglueckt.\n");
264 write("Du lernst aus Deinem Fehler.\n");
265 Learn(caster,spell,sinfo);
266}
267
268string
269SelectSpell(string spell, mapping sinfo) {
270 if (sinfo && sinfo[SI_SKILLFUNC])
271 return sinfo[SI_SKILLFUNC];
272 return spell;
273}
274
275varargs void
276prepare_spell(object caster, string spell, mapping sinfo) {
277 string txt;
278
279 if (!caster || !spell) return ;
280 if (!mappingp(sinfo) || !stringp(txt=sinfo[SI_PREPARE_MSG]))
281 txt="Du bereitest Dich auf den Spruch ``%s'' vor.\n";
282 tell_object(caster,sprintf(txt,spell));
283}
284
285varargs int
286UseSpell(object caster, string spell, mapping sinfo) {
287 mapping ski,tmp;
288 string spellbook,sname,fname,txt;
289 int res,fat,cost;
290 mixed ps;
291
292 if (!caster || !spell)
293 return 0;
294 // Spell kann in der Gilde anderen Namen haben
295 sname=SelectSpell(spell,sinfo);
296 if (!(ski=QuerySpell(sname))) // Existiert dieser Spell?
297 return 0;
298 // Gildeneigenschaften sollen Spelleigenschaften ueberschreiben koennen
299 ski=AddSkillMappings(ski,sinfo);
300 // Fuer verschiedene Rassen unterschiedliche Eigenschaften
301 ski=race_modifier(caster,ski);
302
303 // printf("Spellinfo: %O\n",ski);
304
305 if (!CanTrySpell(caster, ski))
306 return 1;
307
308 if (caster->QueryProp(P_SP) < (cost=GetFValueO(SI_SPELLCOST,ski,caster))) {
309 if(txt=ski[SI_SP_LOW_MSG])write(txt);
310 else write("Du hast zu wenig Zauberpunkte fuer diesen Spruch.\n");
311 return 1;
312 }
313 // printf("cost: %d\n",cost);
314
315 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
316 // fuer jeden Key die Spellfatigue pruefen, wenn das Mapping hinterher
317 // nicht leer ist, ist man zu erschoepft.
318 tmp = filter(ski[SI_X_SPELLFATIGUE],
319 function int (string key, int val)
320 { return (int)caster->CheckSpellFatigue(key); } );
321 if (sizeof(tmp)) {
322 write(ski[SI_TIME_MSG] ||
323 "Du bist noch zu erschoepft von Deinem letzten Spruch.\n");
324 return 1;
325 }
326 }
327 else {
328 if (caster->CheckSpellFatigue()) {
329 write(ski[SI_TIME_MSG] ||
330 "Du bist noch zu erschoepft von Deinem letzten Spruch.\n");
331 return 1;
332 }
333 }
334
335 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY) &&
336 caster->QueryProp(P_ATTACK_BUSY)) {
337 if (txt=ski[SI_ATTACK_BUSY_MSG]) write(txt);
338 else write("Du bist schon zu sehr beschaeftigt.\n");
339 return 1;
340 }
341
342 // Spruchvorbereitung
343 if (pointerp(ps=(mixed)caster->QueryProp(P_PREPARED_SPELL)) // Ausstehender Spruch
344 && sizeof(ps)>=3 && intp(ps[0] && stringp(ps[1]))) {
345 if (ps[1]==spell) { // Dieser Spruch wird noch vorbereitet
346 if (time()<ps[0]) {
347 if (!stringp(txt=ski[SI_PREPARE_BUSY_MSG]))
348 txt="Du bist noch mit der Spruchvorbereitung beschaeftigt.\n";
349 write(txt);
350 return 1;
351 }
352 }
353 else { // Andere Sprueche brechen die Vorbereitung ab
354 if (!mappingp(tmp=QuerySpell(ps[1])) || // richtige Meldung holen...
355 !stringp(txt=tmp[SI_PREPARE_ABORT_MSG]))
356 txt="Du brichst die Spruchvorbereitung fuer `%s' ab.\n";
357 printf(txt,ps[1]);
358 if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) {
359 // Spruch braucht vorbereitungszeit
360 caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
361 prepare_spell(caster,spell,ski);
362 return 1;
363 }
364 }
365 }
366 else {
367 if (fat=GetValue(SI_PREPARE_TIME,ski,caster)) {
368 // Spruch braucht vorbereitungszeit
369 caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
370 prepare_spell(caster,spell,ski);
371 return 1;
372 }
373 }
374 if (ps)
375 caster->SetProp(P_PREPARED_SPELL,0);
376
377 // Funktion kann anderen Namen haben als Spell
378 if (!(fname=sinfo[SI_SKILLFUNC]))
379 fname=sname;
380
381 if((ski[SI_NOMAGIC] < environment(caster)->QueryProp(P_NOMAGIC)) &&
382 random(100) < environment(caster)->QueryProp(P_NOMAGIC)) {
383 if (txt=ski[SI_NOMAGIC_MSG])
384 write(txt);
385 else
386 write("Dein Zauberspruch verpufft im Nichts.\n");
387 res=ABGEWEHRT;
388 }
389 else {
390 // Spruch ausfuehren.
391 res=(int)call_other(this_object(),fname,caster,ski);
392 }
393 // TODO: Wenn die ausgefuehrte Spellfunktion eine 0 zurueckgibt, sollen jetzt
394 // noch notify_fails zum Zuge kommen koennen. Daher in diesem Fall auch 0
395 // zurueckgeben.
396 if (!res || !caster)
397 return 1;
398
399 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY))
400 {
401 if (!ski[SI_ATTACK_BUSY_AMOUNT])
402 caster->SetProp(P_ATTACK_BUSY,1);
403 else
404 caster->SetProp(P_ATTACK_BUSY,ski[SI_ATTACK_BUSY_AMOUNT]);
405 }
406
407 caster->restore_spell_points(-1*cost);
408
409 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
410 // fuer jeden Key die Spellfatigue setzen. Keys mit Dauer 0 loesen keine
411 // Spellfatigue aus.
412 filter(ski[SI_X_SPELLFATIGUE],
413 function int (string key, int val)
414 { return (int)caster->SetSpellFatigue(val, key); } );
415 }
416 else {
417 if ((fat=GetFValueO(SI_SPELLFATIGUE,ski,caster))<0)
418 fat=1;
419 caster->SetSpellFatigue(fat);
420 }
421
422
423 if (res==ERFOLG)
424 Erfolg(caster,spell,ski);
425 else
426 if (res==MISSERFOLG)
427 Misserfolg(caster,spell,ski);
428 return 1;
429}
430
431object *
432FindGroup(object pl, int who) {
433 object *res,ob,env,team;
434 int p1,p2;
435 closure qp;
436
437 res=({});
438 if (!pl || !(env=environment(pl))) return res;
439 p1 = query_once_interactive(pl) ? 1 : -1;
440 team=(object)pl->QueryProp(P_TEAM);
441 for (ob=first_inventory(env);ob;ob=next_inventory(ob)) {
442 if (!living(ob)) continue;
443 qp=symbol_function("QueryProp",ob);
444 if (pl->IsEnemy(ob)) // Feinde sind immer Gegner
445 p2=-1*p1;
446 else if (objectp(team) && funcall(qp,P_TEAM)==team)
447 p2=p1; // Teammitglieder sind immer auf Seite des Spielers
448 else
449 p2 = (query_once_interactive(ob)||funcall(qp,P_FRIEND)) ? 1 : -1;
450 if (p2>0 && !interactive(ob) && query_once_interactive(ob))
451 continue; // keine Netztoten.
452 if (funcall(qp,P_GHOST))
453 continue;
454 if ( who<0 && (funcall(qp,P_NO_ATTACK) || funcall(qp,P_NO_GLOBAL_ATTACK)) )
455 continue;
456 if (IS_LEARNING(ob) &&
457 (funcall(qp,P_INVIS) || (who<0 && !pl->IsEnemy(ob))))
458 continue;
459 if (p1*p2*who >=0)
460 res+=({ob});
461 }
462 return res;
463}
464
465object *
466FindGroupN(object pl, int who, int n) {
467 if (!pl) return ({});
468 n=(n*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100;
469 if (n<1) n=1;
470 return FindGroup(pl,who)[0..(n-1)];
471}
472
473object *
474FindGroupP(object pl, int who, int pr) {
475 object *res,*nres;
476 int i;
477
478 nres=({});
479 if (!pl) return nres;
480 pr=(pr*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100;
481 if (pr<0) return nres;
482 res=FindGroup(pl,who);
483 for (i=sizeof(res)-1;i>=0;i--)
484 if (pr>=random(100))
485 nres+=({res[i]});
486 return nres;
487}
488
489// Findet feindliche und freundliche GRUPPEN im angegebenen Bereich.
490varargs mixed
491FindDistantGroups(object pl, int dist, int dy, int dx) {
492 mapping pos;
493 object ob,enteam,myteam;
494 int p1,min,max,i;
495 mixed *b_rows,x;
496 closure is_enemy, qp;
497
498 if (!objectp(pl) || !environment(pl))
499 return ({({}),({})});
500 if (!dy) dy=100;
501 if (!dx) dx=MAX_TEAM_ROWLEN*100;
502 x=(int)pl->QuerySkillAttribute(SA_EXTENSION);
503 dx=(dx*x)/100;dy=(dy*x)/100;
504 dist=(dist*(int)pl->QuerySkillAttribute(SA_RANGE))/100;
505 min=dist-dy/2;
506 max=dist+dy/2;
507
508 pos=([]);
509 p1=query_once_interactive(pl) ? 1 : -1;
510 is_enemy=symbol_function("IsEnemy",pl); // zur Beschleunigung
511 myteam=(object)pl->QueryProp(P_TEAM);
512 for (ob=first_inventory(environment(pl));ob;ob=next_inventory(ob)) {
513 if (!living(ob)) continue;
514 qp=symbol_function("QueryProp",ob); // zur Beschleunigung
515
516 // Zuerst mal die Position feststellen:
517 if (!objectp(enteam=funcall(qp,P_TEAM)))
518 pos[ob]=1;
519 else if (!pos[ob] && mappingp(x=(mapping)ob->PresentTeamPositions()))
520 pos+=x;
521 // PresentTeamPositions wird nur einmal pro Team ausgerechnet, weil
522 // anschliessend jedes anwesende Teammitglied pos[ob]!=0 hat.
523
524 pos[ob]=(2*pos[ob])-1;
525 // Reihen sollen Abstand 2 haben, auch Reihen 1 und -1 (nach Umrechnung)
526
527 // Feindliche Reihen an Y-Achse spiegeln, also gegenueber hinstellen:
528 if (funcall(is_enemy,ob))
529 pos[ob]*=-1; // Ist auf jeden Fall Feind
530 else if (objectp(myteam) && myteam==enteam)
531 ; // Teammitglieder sind immer auf eigener Seite
532 else
533 pos[ob]*=(p1*((int)(query_once_interactive(ob)||
534 funcall(qp,P_FRIEND))?1:-1));
535
536 // Den Spieler auf keinen Fall entfernen
537 if (ob==pl)
538 continue;
539 // Netztote, Geister und unsichtbare Magier nicht beruecksichtigen,
540 // nicht (global) angreifbare Monster und nicht-feindliche Magier
541 // von feindlicher Seite entfernen.
542 if ((!interactive(ob) && query_once_interactive(ob)) // Netztote raus
543 || funcall(qp,P_GHOST)
544 || (IS_LEARNING(ob) && funcall(qp,P_INVIS)) // Bin nicht da :-)
545 || (pos[ob]<0 && (funcall(qp,P_NO_GLOBAL_ATTACK) // Nicht angreifen
546 || funcall(qp,P_NO_ATTACK)
547 || (IS_LEARNING(ob) && !funcall(is_enemy,ob)))))
548 m_delete(pos,ob);
549 }
550
551 // Reihen aufstellen
552 // Kampfreihe: | 5 4 3 2 1 || 1 2 3 4 5|
553 // Arrays: |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19|
554 // |<----------- Freunde ------->||<--------- Feinde -------->|
555 b_rows=EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY;
556 x=m_indices(pos);
557 for (i=sizeof(x)-1;i>=0;i--) {
558 p1=2*MAX_TEAMROWS-pos[ob=x[i]];
559 pos[ob]=p1;
560 if (p1>=0 && p1<4*MAX_TEAMROWS) {
561 if (random(100)<50) // Damit gut gemischt wird...
562 b_rows[p1]=b_rows[p1]+({ob}); // zufaellig hinten anfuegen
563 else
564 b_rows[p1]=({ob})+b_rows[p1]; // oder vorne anfuegen.
565 }
566 }
567
568 // Umrechnung von Min, Max auf Reihen
569 // ... -2: -124..-75, -1:-74..-25, 0:-24..24, 1:25..74 2:75..124 ...
570 // Das muss man machen, wenn man keine vernuenftige Rundungsfunktion hat:
571 if (min<0) min=-1*((25-min)/50); else min=(25+min)/50;
572 if (max<0) max=-1*((25-max)/50); else max=(25+max)/50;
573 // Relativ zur Position des Spielers verschieben:
574 p1=pos[pl];min+=p1;max+=p1;
575 // Umrechnung von Breite auf Anzahl:
576 dx=((dx+50)/100)-1;if (dx<0) dx=0;
577
578 x=({({}),({})});
579 for (i=0;i<2*MAX_TEAMROWS;i++)
580 if (i>=min && i<=max)
581 x[1]+=b_rows[i][0..dx]; // Freunde
582 for (i=2*MAX_TEAMROWS+1;i<4*MAX_TEAMROWS;i++)
583 if (i>=min && i<=max)
584 x[0]+=b_rows[i][0..dx]; // Feinde
585
586 return x;
587}
588
589// Findet feindliche oder freundliche Gruppe (oder Summe beider) im
590// angegebenen Bereich.
591varargs object *
592FindDistantGroup(object pl, int who, int dist, int dy, int dx) {
593 mixed *x;
594
595 x=FindDistantGroups(pl,dist,dy,dx);
596 if (who<0)
597 return x[0];
598 else if (who>0)
599 return x[1];
600 return x[0]+x[1];
601}
602
603
604static varargs object
605find_victim(string wen, object pl) {
606 object victim;
607
608 if (!pl) return 0;
609 if (!sizeof(wen)) {
610 if (victim = (object)pl->SelectEnemy())
611 return victim;
612 else
613 return 0;
614 }
615 if (victim = present(wen, environment(pl)))
616 return victim;
617 else if (victim = present(wen, pl))
618 return victim;
619 else
620 return 0;
621}
622varargs object
623FindVictim(string wen, object pl, string msg) {
624 object vic;
625
626 if (!(vic=find_victim(wen,pl)) && msg)
627 write(msg);
628 return vic;
629}
630varargs object
631FindLivingVictim(string wen, object pl, string msg) {
632 object vic;
633
634 if (!(vic=FindVictim(wen,pl,msg)))
635 return 0;
636 if (!living(vic) || vic->QueryProp(P_GHOST)) {
637 printf("%s lebt doch nicht!\n", capitalize((string)vic->name()));
638 return 0;
639 }
640 return vic;
641}
642
643private varargs object
644DoFindEnemyVictim(string wen, object pl, string msg,
645 mixed func, int min, int max) {
646 object vic;
647 mixed no_attack;
648 int row;
649
650 if (!(vic=FindLivingVictim(wen,pl,msg))) {
651 if ((stringp(wen) && wen!="") || !objectp(pl))
652 return 0;
653 if (pointerp(func)) { // Soll einer DIESER Gegner genommen werden?
654 if (!(vic=(object)pl->SelectEnemy(func))) // Dann daraus auswaehlen
655 return 0;
656 } else {
657 if (!stringp(func))
658 func="SelectEnemy";
659 if (!(vic=(object)call_other(pl,func,0,min,max)))
660 return 0;
661 }
662 func=0; // kein zweites Mal pruefen.
663 }
664 if (no_attack = (mixed)vic->QueryProp(P_NO_ATTACK)) {
665 if (stringp(no_attack))
666 write(no_attack);
667 else
668 write(vic->Name(WER,1)+" laesst sich nicht angreifen.\n");
669 return 0;
670 }
671 if (vic==pl) {
672 write("Du koenntest Dir dabei wehtun.\n");
673 return 0;
674 }
675 if (stringp(func)) {
676 switch(func) {
677 case "SelectNearEnemy":
678 if (pl->PresentPosition()>1) {
679 write("Du stehst nicht in der ersten Kampfreihe.\n");
680 return 0;
681 }
682 if (vic->PresentPosition()>1) {
683 write(vic->Name(WER,1)+" ist in einer hinteren Kampfreihe.\n");
684 return 0;
685 }
686 break;
687 case "SelectFarEnemy":
688 if (row=(int)vic->PresentPosition())
689 row--;
690 if (row>=min && row<=max)
691 break;
692 if (row<min)
693 write(vic->Name(WER,1)+" ist zu nahe.\n");
694 else if (row>max)
695 write(vic->Name(WER,1)+" ist zu weit weg.\n");
696 else
697 write(vic->Name(WER,1)+" ist unerreichbar.\n");
698 return 0;
699 default:;
700 }
701 } else if (pointerp(func)) {
702 if (member(func,vic)<0) {
703 write(vic->Name(WER,1)+" ist unerreichbar.\n");
704 return 0;
705 }
706 }
707
708 if (!pl->IsEnemy(vic)) // War es bisher kein Feind?
709 pl->Kill(vic); // Dann ist es jetzt einer.
710 return vic;
711}
712
713varargs object
714FindEnemyVictim(string wen, object pl, string msg) {
715 return DoFindEnemyVictim(wen,pl,msg,"SelectEnemy");
716}
717
718// Wie FindEnemyVictim, aber nur im Nahkampf erreichbare Feinde werden
719// gefunden.
720varargs object
721FindNearEnemyVictim(string wen, object pl, string msg) {
722 return DoFindEnemyVictim(wen,pl,msg,"SelectNearEnemy");
723}
724
725// Wie FindEnemyVictim, aber nur Feinde im Bereich der angegebenen Reihen
726// (min,max) werden gefunden.
727varargs object
728FindFarEnemyVictim(string wen, object pl, string msg,
729 int min, int max) {
730 return DoFindEnemyVictim(wen,pl,msg,"SelectFarEnemy",min,max);
731}
732
733// Wie FindEnemyVictim, findet aber nur Feinde in
734// FindDistantGroup(GEGNER,entfernung,abweichung)
735varargs object
736FindDistantEnemyVictim(string wen, object pl, string msg,
737 int dist, int dy) {
738 return DoFindEnemyVictim(wen,pl,msg,
739 FindDistantGroup(pl,-1,dist,dy,10000));
740}
741
742varargs int
743TryGlobalAttackSpell(object caster, mapping sinfo, int suc,
744 int damage, mixed dt, mixed is_spell,
745 int dist, int depth, int width) {
746 int i;
747 mixed x,coldam;
748 object *obs,ob;
749
750 if (!suc) suc=random(sinfo[SI_SKILLABILITY]);
751 if (!dt) dt=GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster);
752 if (!is_spell) is_spell=GetData(SI_SPELL,sinfo,caster);
753 if (!dist) dist=GetRandFValueO(SI_DISTANCE,sinfo,caster);
754 if (!depth) depth=GetRandFValueO(SI_DEPTH,sinfo,caster);
755 if (!width) width=GetRandFValueO(SI_WIDTH,sinfo,caster);
756
757 if (!depth && width) depth=width;
758 if (!width && depth) width=depth;
759 if (!mappingp(is_spell)) is_spell=([]);
760 is_spell[SP_GLOBAL_ATTACK]=1;
761
762 x=FindDistantGroups(caster,dist,depth,width);
763 sinfo[SI_NUMBER_ENEMIES]=sizeof(x[0]);
764 sinfo[SI_NUMBER_FRIENDS]=sizeof(x[1]);
765
766 obs=x[0];
767 for (i=sizeof(obs)-1;i>=0;i--)
768 if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo))
769 TryAttackSpell(ob,(damage?random(damage):
770 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)),
771 dt,is_spell,caster,sinfo);
772
773 if (!intp(coldam=sinfo[SI_COLLATERAL_DAMAGE]) || !coldam)
774 return 1;
775 obs=x[1];
776 for (i=sizeof(obs)-1;i>=0;i--)
777 if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo))
778 ob->reduce_hit_points(((damage?random(damage):
779 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster))
780 *coldam)/10);
781 // 10 statt 100 ist Absicht, weil reduce_hit_points schon um Faktor
782 // 10 staerker wirkt als Defend.
783
784 return 1;
785}