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