blob: 6f65709ab4d656e7989a09e964581aab0a08afe7 [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))
heull0018720c5a2018-02-08 12:29:49 +0100112 caster->ReceiveMsg(
113 no_attack,
114 MT_NOTIFICATION,
115 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200116 else
heull0018720c5a2018-02-08 12:29:49 +0100117 caster->ReceiveMsg(
118 victim->Name(WER,1)+" laesst sich nicht angreifen!",
119 MT_NOTIFICATION,
120 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200121 return 0;
122 }
123 if (victim->QueryProp(P_GHOST)) {
heull0018720c5a2018-02-08 12:29:49 +0100124 caster->ReceiveMsg(
125 victim->Name(WER,1)+" ist ein Geist!",
126 MT_NOTIFICATION,
127 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200128 return 0;
129 }
130
131 damage=(damage*(int)caster->QuerySkillAttribute(SA_DAMAGE))/100;
132
133 // ggf. Loggen von PK-Versuchen und ggf. ausserdem Angriff abbrechen.
134 // BTW: Aufruf von InsertEnemy() gibt 0 zurueck, wenn der Gegner nicht
135 // eingetragen wurde. Allerdings tut es das auch, wenn der Gegner schon
136 // Feind war. Daher muss noch geprueft werden, ob nach dem InsertEnemy() die
137 // beiden Spieler Feinde sind. Wenn nicht, wird der Spell auch abgebrochen
138 // und eine Meldung ausgegeben.
139 if (query_once_interactive(caster) && query_once_interactive(victim)
140 && CheckPlayerAttack(caster, victim,
141 sprintf("Spellbook: %O, Spell: %O\n",
142 object_name(this_object()),
143 ((mappingp(sinfo) && is_spell) ? sinfo[SP_NAME] : "")))
144 && !caster->IsEnemy(victim)
145 && !caster->InsertEnemy(victim)
146 )
147 {
148 tell_object(ME, "Ein goettlicher Einfluss schuetzt Deinen Gegner.\n");
149 return 0;
150 }
151
152 nomag=(int)victim->QueryProp(P_NOMAGIC);
153 if ((sinfo[SI_NOMAGIC] < nomag) &&
154 nomag*100 > random(100)*(int)caster->QuerySkillAttribute(SA_ENEMY_SAVE)) {
155 printf("%s wehrt Deinen Zauber ab.\n", capitalize((string)victim->name(WER, 1)));
156 return 0;
157 }
158 else {
159 return (int)victim->Defend(damage, dtypes, is_spell, caster);
160 }
161}
162
163varargs int
164TryDefaultAttackSpell(object victim, object caster, mapping sinfo,
165 mixed si_spell) {
166 object team;
167 int row;
168
169 if (!si_spell) si_spell=sinfo[SI_SPELL];
170 // Wenn der Spieler in einem Team ist, die Teamreihen-Boni
171 // beruecksichtigen. Wenn nicht, eben nicht.
172 if (!team=((object)caster->QueryProp(P_TEAM)))
173 return TryAttackSpell(victim,
174 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster),
175 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
176 si_spell,
177 caster,
178 sinfo);
179 else
180 {
181 row=(int)caster->PresentPosition();
182 return TryAttackSpell(victim,
183 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)+
184 // Nur wenn SI_SKILLDAMAGE_BY_ROW ein mapping ist,
185 // randomisiert addieren
186 (mappingp(sinfo[SI_SKILLDAMAGE_BY_ROW])?
187 random(sinfo[SI_SKILLDAMAGE_BY_ROW][row]):0)+
188 // Nur wenn das OFFSET davon ein mapping ist,
189 // nicht randomisiert addieren.
190 (mappingp(sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)])?
191 sinfo[OFFSET(SI_SKILLDAMAGE_BY_ROW)][row] : 0),
192 GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster),
193 si_spell,caster,sinfo);
194 }
195}
196
197#define SMUL(x,y) ((x<0 && y<0)?(-1*x*y):(x*y))
198int
199SpellSuccess(object caster, mapping sinfo) {
200 int cval,abil,res;
201
202 abil=sinfo[SI_SKILLABILITY];
203 cval=(int)caster->UseSkill(SK_CASTING);
204 res=abil + (SMUL(abil,cval)) / MAX_ABILITY - random(MAX_ABILITY);
205 if (cval && res>MAX_ABILITY) // besonders gut gelungen?
206 caster->LearnSkill(SK_CASTING,1+(res-MAX_ABILITY)/2000);
207 return res;
208}
209
210int
211CanTrySpell(object caster, mapping sinfo) {
MG Mud User88f12472016-06-24 23:31:02 +0200212 if (caster->QueryProp(P_GHOST)) {
heull0018720c5a2018-02-08 12:29:49 +0100213 caster->ReceiveMsg(
214 "Als Geist kannst Du nicht zaubern.",
215 MT_NOTIFICATION,
216 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200217 return 0;
218 }
Zesstra6a8465b2018-02-13 20:44:51 +0100219 string res;
220 mapping rmap=sinfo[SI_SKILLRESTR_USE];
221 if (mappingp(rmap)
Arathorne7dbb072018-02-10 20:07:23 +0100222 && (res=check_restrictions(caster,rmap)))
223 {
224 caster->ReceiveMsg(res, MT_NOTIFICATION|MSG_BS_DONT_WRAP, MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200225 return 0;
226 }
227 return 1;
228}
229
230void
231Learn(object caster, string spell, mapping sinfo) {
232 int val,diff,attval;
233 mapping attr;
234
235 // gar nicht lernen?
236 if (sinfo[SI_NO_LEARN])
237 return;
238
239 // Attribut sollte int sein, wenn nicht anders angegeben
240 if (mappingp(sinfo[SI_LEARN_ATTRIBUTE]))
241 {
242 attr=sinfo[SI_LEARN_ATTRIBUTE];
243 // Direkt in einem durch berechnen.
244 // Ich gehe davon aus, dass wir nie nie nie
245 // ein weiteres Attribut ins MG einfuegen.
246 // Wir berechnen den Mittelwert
247 attval=((int)caster->QueryAttribute(A_INT)*attr[A_INT]+
248 (int)caster->QueryAttribute(A_DEX)*attr[A_DEX]+
249 (int)caster->QueryAttribute(A_STR)*attr[A_STR]+
250 (int)caster->QueryAttribute(A_CON)*attr[A_CON])
251 /(attr[A_CON]+attr[A_INT]+attr[A_STR]+attr[A_DEX]);
252
253
254 } else {
255 attval=(int)caster->QueryAttribute(A_INT);
256 }
257
258
259 val=((attval/2)*GetFValue(SI_SKILLLEARN,sinfo,caster)+
260 GetOffset(SI_SKILLLEARN,sinfo,caster));
261 if (!(diff=GetFValueO(SI_DIFFICULTY,sinfo,caster)))
262 diff=GetFValueO(SI_SPELLCOST,sinfo,caster);
263 caster->LearnSkill(spell,val,diff);
264}
265
266void
267Erfolg(object caster, string spell, mapping sinfo) {
268 object env;
269 if(env=environment(caster))
270 env->SpellInform(caster,spell,sinfo);
271}
272
273void
274Misserfolg(object caster, string spell, mapping sinfo) {
heull0018720c5a2018-02-08 12:29:49 +0100275 caster->ReceiveMsg(
276 "Der Zauberspruch ist missglueckt.\n"
277 "Du lernst aus Deinem Fehler.\n",
278 MT_NOTIFICATION|MSG_DONT_WRAP,
279 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200280 Learn(caster,spell,sinfo);
281}
282
283string
284SelectSpell(string spell, mapping sinfo) {
285 if (sinfo && sinfo[SI_SKILLFUNC])
286 return sinfo[SI_SKILLFUNC];
287 return spell;
288}
289
290varargs void
291prepare_spell(object caster, string spell, mapping sinfo) {
292 string txt;
293
294 if (!caster || !spell) return ;
295 if (!mappingp(sinfo) || !stringp(txt=sinfo[SI_PREPARE_MSG]))
296 txt="Du bereitest Dich auf den Spruch ``%s'' vor.\n";
297 tell_object(caster,sprintf(txt,spell));
298}
299
300varargs int
301UseSpell(object caster, string spell, mapping sinfo) {
302 mapping ski,tmp;
303 string spellbook,sname,fname,txt;
304 int res,fat,cost;
305 mixed ps;
306
307 if (!caster || !spell)
308 return 0;
309 // Spell kann in der Gilde anderen Namen haben
310 sname=SelectSpell(spell,sinfo);
311 if (!(ski=QuerySpell(sname))) // Existiert dieser Spell?
312 return 0;
313 // Gildeneigenschaften sollen Spelleigenschaften ueberschreiben koennen
314 ski=AddSkillMappings(ski,sinfo);
315 // Fuer verschiedene Rassen unterschiedliche Eigenschaften
316 ski=race_modifier(caster,ski);
317
318 // printf("Spellinfo: %O\n",ski);
319
320 if (!CanTrySpell(caster, ski))
321 return 1;
322
323 if (caster->QueryProp(P_SP) < (cost=GetFValueO(SI_SPELLCOST,ski,caster))) {
heull0018720c5a2018-02-08 12:29:49 +0100324 if(txt=ski[SI_SP_LOW_MSG])
325 caster->ReceiveMsg(
326 txt,
327 MT_NOTIFICATION,
328 MA_SPELL);
329 else
330 caster->ReceiveMsg(
331 "Du hast zu wenig Zauberpunkte fuer diesen Spruch.",
332 MT_NOTIFICATION,
333 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200334 return 1;
335 }
336 // printf("cost: %d\n",cost);
337
338 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
339 // fuer jeden Key die Spellfatigue pruefen, wenn das Mapping hinterher
340 // nicht leer ist, ist man zu erschoepft.
341 tmp = filter(ski[SI_X_SPELLFATIGUE],
342 function int (string key, int val)
343 { return (int)caster->CheckSpellFatigue(key); } );
344 if (sizeof(tmp)) {
heull0018720c5a2018-02-08 12:29:49 +0100345 caster->ReceiveMsg(
346 ski[SI_TIME_MSG] ||
347 "Du bist noch zu erschoepft von Deinem letzten Spruch.",
348 MT_NOTIFICATION,
349 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200350 return 1;
351 }
352 }
353 else {
354 if (caster->CheckSpellFatigue()) {
heull0018720c5a2018-02-08 12:29:49 +0100355 caster->ReceiveMsg(
356 ski[SI_TIME_MSG] ||
357 "Du bist noch zu erschoepft von Deinem letzten Spruch.",
358 MT_NOTIFICATION,
359 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200360 return 1;
361 }
362 }
363
364 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY) &&
365 caster->QueryProp(P_ATTACK_BUSY)) {
heull0018720c5a2018-02-08 12:29:49 +0100366 if (txt=ski[SI_ATTACK_BUSY_MSG])
367 caster->ReceiveMsg(
368 txt,
369 MT_NOTIFICATION,
370 MA_SPELL);
371 else
372 caster->ReceiveMsg(
373 "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
380 if (pointerp(ps=(mixed)caster->QueryProp(P_PREPARED_SPELL)) // Ausstehender Spruch
381 && 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";
heull0018720c5a2018-02-08 12:29:49 +0100386 caster->ReceiveMsg(
387 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
400 caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
401 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
409 caster->SetProp(P_PREPARED_SPELL,({time()+fat,spell,ski[SI_SKILLARG]}));
410 prepare_spell(caster,spell,ski);
411 return 1;
412 }
413 }
414 if (ps)
415 caster->SetProp(P_PREPARED_SPELL,0);
416
417 // Funktion kann anderen Namen haben als Spell
418 if (!(fname=sinfo[SI_SKILLFUNC]))
419 fname=sname;
420
421 if((ski[SI_NOMAGIC] < environment(caster)->QueryProp(P_NOMAGIC)) &&
422 random(100) < environment(caster)->QueryProp(P_NOMAGIC)) {
423 if (txt=ski[SI_NOMAGIC_MSG])
heull0018720c5a2018-02-08 12:29:49 +0100424 caster->ReceiveMsg(
425 txt,
426 MT_NOTIFICATION,
427 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200428 else
heull0018720c5a2018-02-08 12:29:49 +0100429 caster->ReceiveMsg(
430 "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.
437 res=(int)call_other(this_object(),fname,caster,ski);
438 }
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
MG Mud User88f12472016-06-24 23:31:02 +0200445 if (!(ski[SI_NO_ATTACK_BUSY]&NO_ATTACK_BUSY_QUERY))
446 {
447 if (!ski[SI_ATTACK_BUSY_AMOUNT])
448 caster->SetProp(P_ATTACK_BUSY,1);
449 else
450 caster->SetProp(P_ATTACK_BUSY,ski[SI_ATTACK_BUSY_AMOUNT]);
451 }
452
453 caster->restore_spell_points(-1*cost);
454
455 if (mappingp(ski[SI_X_SPELLFATIGUE])) {
456 // fuer jeden Key die Spellfatigue setzen. Keys mit Dauer 0 loesen keine
457 // Spellfatigue aus.
458 filter(ski[SI_X_SPELLFATIGUE],
459 function int (string key, int val)
460 { return (int)caster->SetSpellFatigue(val, key); } );
461 }
462 else {
463 if ((fat=GetFValueO(SI_SPELLFATIGUE,ski,caster))<0)
464 fat=1;
465 caster->SetSpellFatigue(fat);
466 }
467
468
469 if (res==ERFOLG)
470 Erfolg(caster,spell,ski);
471 else
472 if (res==MISSERFOLG)
473 Misserfolg(caster,spell,ski);
474 return 1;
475}
476
477object *
478FindGroup(object pl, int who) {
479 object *res,ob,env,team;
480 int p1,p2;
481 closure qp;
482
483 res=({});
484 if (!pl || !(env=environment(pl))) return res;
485 p1 = query_once_interactive(pl) ? 1 : -1;
486 team=(object)pl->QueryProp(P_TEAM);
487 for (ob=first_inventory(env);ob;ob=next_inventory(ob)) {
488 if (!living(ob)) continue;
489 qp=symbol_function("QueryProp",ob);
490 if (pl->IsEnemy(ob)) // Feinde sind immer Gegner
491 p2=-1*p1;
492 else if (objectp(team) && funcall(qp,P_TEAM)==team)
493 p2=p1; // Teammitglieder sind immer auf Seite des Spielers
494 else
495 p2 = (query_once_interactive(ob)||funcall(qp,P_FRIEND)) ? 1 : -1;
496 if (p2>0 && !interactive(ob) && query_once_interactive(ob))
497 continue; // keine Netztoten.
498 if (funcall(qp,P_GHOST))
499 continue;
500 if ( who<0 && (funcall(qp,P_NO_ATTACK) || funcall(qp,P_NO_GLOBAL_ATTACK)) )
501 continue;
502 if (IS_LEARNING(ob) &&
503 (funcall(qp,P_INVIS) || (who<0 && !pl->IsEnemy(ob))))
504 continue;
505 if (p1*p2*who >=0)
506 res+=({ob});
507 }
508 return res;
509}
510
511object *
512FindGroupN(object pl, int who, int n) {
513 if (!pl) return ({});
514 n=(n*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100;
515 if (n<1) n=1;
516 return FindGroup(pl,who)[0..(n-1)];
517}
518
519object *
520FindGroupP(object pl, int who, int pr) {
521 object *res,*nres;
522 int i;
523
524 nres=({});
525 if (!pl) return nres;
526 pr=(pr*(int)pl->QuerySkillAttribute(SA_EXTENSION))/100;
527 if (pr<0) return nres;
528 res=FindGroup(pl,who);
529 for (i=sizeof(res)-1;i>=0;i--)
530 if (pr>=random(100))
531 nres+=({res[i]});
532 return nres;
533}
534
535// Findet feindliche und freundliche GRUPPEN im angegebenen Bereich.
536varargs mixed
537FindDistantGroups(object pl, int dist, int dy, int dx) {
538 mapping pos;
539 object ob,enteam,myteam;
540 int p1,min,max,i;
541 mixed *b_rows,x;
542 closure is_enemy, qp;
543
544 if (!objectp(pl) || !environment(pl))
545 return ({({}),({})});
546 if (!dy) dy=100;
547 if (!dx) dx=MAX_TEAM_ROWLEN*100;
548 x=(int)pl->QuerySkillAttribute(SA_EXTENSION);
549 dx=(dx*x)/100;dy=(dy*x)/100;
550 dist=(dist*(int)pl->QuerySkillAttribute(SA_RANGE))/100;
551 min=dist-dy/2;
552 max=dist+dy/2;
553
554 pos=([]);
555 p1=query_once_interactive(pl) ? 1 : -1;
556 is_enemy=symbol_function("IsEnemy",pl); // zur Beschleunigung
557 myteam=(object)pl->QueryProp(P_TEAM);
558 for (ob=first_inventory(environment(pl));ob;ob=next_inventory(ob)) {
559 if (!living(ob)) continue;
560 qp=symbol_function("QueryProp",ob); // zur Beschleunigung
561
562 // Zuerst mal die Position feststellen:
563 if (!objectp(enteam=funcall(qp,P_TEAM)))
564 pos[ob]=1;
565 else if (!pos[ob] && mappingp(x=(mapping)ob->PresentTeamPositions()))
566 pos+=x;
567 // PresentTeamPositions wird nur einmal pro Team ausgerechnet, weil
568 // anschliessend jedes anwesende Teammitglied pos[ob]!=0 hat.
569
570 pos[ob]=(2*pos[ob])-1;
571 // Reihen sollen Abstand 2 haben, auch Reihen 1 und -1 (nach Umrechnung)
572
573 // Feindliche Reihen an Y-Achse spiegeln, also gegenueber hinstellen:
574 if (funcall(is_enemy,ob))
575 pos[ob]*=-1; // Ist auf jeden Fall Feind
576 else if (objectp(myteam) && myteam==enteam)
577 ; // Teammitglieder sind immer auf eigener Seite
578 else
579 pos[ob]*=(p1*((int)(query_once_interactive(ob)||
580 funcall(qp,P_FRIEND))?1:-1));
581
582 // Den Spieler auf keinen Fall entfernen
583 if (ob==pl)
584 continue;
585 // Netztote, Geister und unsichtbare Magier nicht beruecksichtigen,
586 // nicht (global) angreifbare Monster und nicht-feindliche Magier
587 // von feindlicher Seite entfernen.
588 if ((!interactive(ob) && query_once_interactive(ob)) // Netztote raus
589 || funcall(qp,P_GHOST)
590 || (IS_LEARNING(ob) && funcall(qp,P_INVIS)) // Bin nicht da :-)
591 || (pos[ob]<0 && (funcall(qp,P_NO_GLOBAL_ATTACK) // Nicht angreifen
592 || funcall(qp,P_NO_ATTACK)
593 || (IS_LEARNING(ob) && !funcall(is_enemy,ob)))))
594 m_delete(pos,ob);
595 }
596
597 // Reihen aufstellen
598 // Kampfreihe: | 5 4 3 2 1 || 1 2 3 4 5|
599 // Arrays: |0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19|
600 // |<----------- Freunde ------->||<--------- Feinde -------->|
601 b_rows=EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY+EMPTY_TEAMARRAY;
602 x=m_indices(pos);
603 for (i=sizeof(x)-1;i>=0;i--) {
604 p1=2*MAX_TEAMROWS-pos[ob=x[i]];
605 pos[ob]=p1;
606 if (p1>=0 && p1<4*MAX_TEAMROWS) {
607 if (random(100)<50) // Damit gut gemischt wird...
608 b_rows[p1]=b_rows[p1]+({ob}); // zufaellig hinten anfuegen
609 else
610 b_rows[p1]=({ob})+b_rows[p1]; // oder vorne anfuegen.
611 }
612 }
613
614 // Umrechnung von Min, Max auf Reihen
615 // ... -2: -124..-75, -1:-74..-25, 0:-24..24, 1:25..74 2:75..124 ...
616 // Das muss man machen, wenn man keine vernuenftige Rundungsfunktion hat:
617 if (min<0) min=-1*((25-min)/50); else min=(25+min)/50;
618 if (max<0) max=-1*((25-max)/50); else max=(25+max)/50;
619 // Relativ zur Position des Spielers verschieben:
620 p1=pos[pl];min+=p1;max+=p1;
621 // Umrechnung von Breite auf Anzahl:
622 dx=((dx+50)/100)-1;if (dx<0) dx=0;
623
624 x=({({}),({})});
625 for (i=0;i<2*MAX_TEAMROWS;i++)
626 if (i>=min && i<=max)
627 x[1]+=b_rows[i][0..dx]; // Freunde
628 for (i=2*MAX_TEAMROWS+1;i<4*MAX_TEAMROWS;i++)
629 if (i>=min && i<=max)
630 x[0]+=b_rows[i][0..dx]; // Feinde
631
632 return x;
633}
634
635// Findet feindliche oder freundliche Gruppe (oder Summe beider) im
636// angegebenen Bereich.
637varargs object *
638FindDistantGroup(object pl, int who, int dist, int dy, int dx) {
639 mixed *x;
640
641 x=FindDistantGroups(pl,dist,dy,dx);
642 if (who<0)
643 return x[0];
644 else if (who>0)
645 return x[1];
646 return x[0]+x[1];
647}
648
649
650static varargs object
651find_victim(string wen, object pl) {
652 object victim;
653
654 if (!pl) return 0;
655 if (!sizeof(wen)) {
656 if (victim = (object)pl->SelectEnemy())
657 return victim;
658 else
659 return 0;
660 }
661 if (victim = present(wen, environment(pl)))
662 return victim;
663 else if (victim = present(wen, pl))
664 return victim;
665 else
666 return 0;
667}
668varargs object
669FindVictim(string wen, object pl, string msg) {
670 object vic;
671
672 if (!(vic=find_victim(wen,pl)) && msg)
heull0018720c5a2018-02-08 12:29:49 +0100673 pl->ReceiveMsg(
674 msg,
675 MT_NOTIFICATION,
676 MA_SPELL);
MG Mud User88f12472016-06-24 23:31:02 +0200677 return vic;
678}
679varargs object
680FindLivingVictim(string wen, object pl, string msg) {
681 object vic;
682
683 if (!(vic=FindVictim(wen,pl,msg)))
684 return 0;
685 if (!living(vic) || vic->QueryProp(P_GHOST)) {
686 printf("%s lebt doch nicht!\n", capitalize((string)vic->name()));
687 return 0;
688 }
689 return vic;
690}
691
692private varargs object
693DoFindEnemyVictim(string wen, object pl, string msg,
694 mixed func, int min, int max) {
695 object vic;
696 mixed no_attack;
697 int row;
698
699 if (!(vic=FindLivingVictim(wen,pl,msg))) {
700 if ((stringp(wen) && wen!="") || !objectp(pl))
701 return 0;
702 if (pointerp(func)) { // Soll einer DIESER Gegner genommen werden?
703 if (!(vic=(object)pl->SelectEnemy(func))) // Dann daraus auswaehlen
704 return 0;
705 } else {
706 if (!stringp(func))
707 func="SelectEnemy";
708 if (!(vic=(object)call_other(pl,func,0,min,max)))
709 return 0;
710 }
711 func=0; // kein zweites Mal pruefen.
712 }
713 if (no_attack = (mixed)vic->QueryProp(P_NO_ATTACK)) {
714 if (stringp(no_attack))
heull0018720c5a2018-02-08 12:29:49 +0100715 pl->ReceiveMsg(
716 no_attack,
717 MT_NOTIFICATION,
718 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200719 else
heull0018720c5a2018-02-08 12:29:49 +0100720 pl->ReceiveMsg(
721 vic->Name(WER,1)+" laesst sich nicht angreifen.",
722 MT_NOTIFICATION,
723 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200724 return 0;
725 }
726 if (vic==pl) {
heull0018720c5a2018-02-08 12:29:49 +0100727 pl->ReceiveMsg(
728 "Du koenntest Dir dabei wehtun.",
729 MT_NOTIFICATION,
730 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200731 return 0;
732 }
733 if (stringp(func)) {
734 switch(func) {
735 case "SelectNearEnemy":
736 if (pl->PresentPosition()>1) {
heull0018720c5a2018-02-08 12:29:49 +0100737 pl->ReceiveMsg(
738 "Du stehst nicht in der ersten Kampfreihe.",
739 MT_NOTIFICATION,
740 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200741 return 0;
742 }
743 if (vic->PresentPosition()>1) {
heull0018720c5a2018-02-08 12:29:49 +0100744 pl->ReceiveMsg(
745 vic->Name(WER,1)+" ist in einer hinteren Kampfreihe.",
746 MT_NOTIFICATION,
747 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200748 return 0;
749 }
750 break;
751 case "SelectFarEnemy":
752 if (row=(int)vic->PresentPosition())
753 row--;
754 if (row>=min && row<=max)
755 break;
756 if (row<min)
heull0018720c5a2018-02-08 12:29:49 +0100757 pl->ReceiveMsg(
758 vic->Name(WER,1)+" ist zu nahe.",
759 MT_NOTIFICATION,
760 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200761 else if (row>max)
heull0018720c5a2018-02-08 12:29:49 +0100762 pl->ReceiveMsg(
763 vic->Name(WER,1)+" ist zu weit weg.",
764 MT_NOTIFICATION,
765 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200766 else
heull0018720c5a2018-02-08 12:29:49 +0100767 pl->ReceiveMsg(
768 vic->Name(WER,1)+" ist unerreichbar.",
769 MT_NOTIFICATION,
770 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200771 return 0;
772 default:;
773 }
774 } else if (pointerp(func)) {
775 if (member(func,vic)<0) {
heull0018720c5a2018-02-08 12:29:49 +0100776 pl->ReceiveMsg(
777 vic->Name(WER,1)+" ist unerreichbar.",
778 MT_NOTIFICATION,
779 MA_FIGHT);
MG Mud User88f12472016-06-24 23:31:02 +0200780 return 0;
781 }
782 }
783
784 if (!pl->IsEnemy(vic)) // War es bisher kein Feind?
785 pl->Kill(vic); // Dann ist es jetzt einer.
786 return vic;
787}
788
789varargs object
790FindEnemyVictim(string wen, object pl, string msg) {
791 return DoFindEnemyVictim(wen,pl,msg,"SelectEnemy");
792}
793
794// Wie FindEnemyVictim, aber nur im Nahkampf erreichbare Feinde werden
795// gefunden.
796varargs object
797FindNearEnemyVictim(string wen, object pl, string msg) {
798 return DoFindEnemyVictim(wen,pl,msg,"SelectNearEnemy");
799}
800
801// Wie FindEnemyVictim, aber nur Feinde im Bereich der angegebenen Reihen
802// (min,max) werden gefunden.
803varargs object
804FindFarEnemyVictim(string wen, object pl, string msg,
805 int min, int max) {
806 return DoFindEnemyVictim(wen,pl,msg,"SelectFarEnemy",min,max);
807}
808
809// Wie FindEnemyVictim, findet aber nur Feinde in
810// FindDistantGroup(GEGNER,entfernung,abweichung)
811varargs object
812FindDistantEnemyVictim(string wen, object pl, string msg,
813 int dist, int dy) {
814 return DoFindEnemyVictim(wen,pl,msg,
815 FindDistantGroup(pl,-1,dist,dy,10000));
816}
817
818varargs int
819TryGlobalAttackSpell(object caster, mapping sinfo, int suc,
820 int damage, mixed dt, mixed is_spell,
821 int dist, int depth, int width) {
822 int i;
823 mixed x,coldam;
824 object *obs,ob;
825
826 if (!suc) suc=random(sinfo[SI_SKILLABILITY]);
827 if (!dt) dt=GetData(SI_SKILLDAMAGE_TYPE,sinfo,caster);
828 if (!is_spell) is_spell=GetData(SI_SPELL,sinfo,caster);
829 if (!dist) dist=GetRandFValueO(SI_DISTANCE,sinfo,caster);
830 if (!depth) depth=GetRandFValueO(SI_DEPTH,sinfo,caster);
831 if (!width) width=GetRandFValueO(SI_WIDTH,sinfo,caster);
832
833 if (!depth && width) depth=width;
834 if (!width && depth) width=depth;
835 if (!mappingp(is_spell)) is_spell=([]);
836 is_spell[SP_GLOBAL_ATTACK]=1;
837
838 x=FindDistantGroups(caster,dist,depth,width);
839 sinfo[SI_NUMBER_ENEMIES]=sizeof(x[0]);
840 sinfo[SI_NUMBER_FRIENDS]=sizeof(x[1]);
841
842 obs=x[0];
843 for (i=sizeof(obs)-1;i>=0;i--)
844 if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo))
845 TryAttackSpell(ob,(damage?random(damage):
846 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster)),
847 dt,is_spell,caster,sinfo);
848
849 if (!intp(coldam=sinfo[SI_COLLATERAL_DAMAGE]) || !coldam)
850 return 1;
851 obs=x[1];
852 for (i=sizeof(obs)-1;i>=0;i--)
853 if (objectp(ob=obs[i]) && suc>=ob->SpellDefend(caster,sinfo))
854 ob->reduce_hit_points(((damage?random(damage):
855 GetRandFValueO(SI_SKILLDAMAGE,sinfo,caster))
856 *coldam)/10);
857 // 10 statt 100 ist Absicht, weil reduce_hit_points schon um Faktor
858 // 10 staerker wirkt als Defend.
859
860 return 1;
861}