blob: 6c968f936b0d961661844e9dc36499b7ab681221 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// living/combat.c -- Basis-Kampfmodul
4//
5// $Id: combat.c 9568 2016-06-05 18:53:10Z Zesstra $
6#pragma strong_types
7#pragma save_types
8#pragma range_check
9#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020010
11inherit "/std/living/skill_utils";
12inherit "/std/living/inventory";
13inherit "/std/living/team";
14
15#include <sys_debug.h>
16#include <debug_message.h>
17
18#define NEED_PROTOTYPES
19#include <hook.h>
20#include <living/skills.h>
21#include <thing/properties.h>
22#include <player/comm.h>
23#include <living/skill_attributes.h>
24#include <combat.h>
25#include <living.h>
26#undef NEED_PROTOTYPES
27
28#include <config.h>
29#include <properties.h>
30#include <language.h>
31#include <wizlevels.h>
32#include <attributes.h>
33#include <new_skills.h>
34
35#include <defines.h>
36
37#include <sensitive.h>
38
39#define HUNTTIME 300 //300 HBs sind 10 Minuten
40#define RNAME(x) capitalize(getuid(x))
41
42// 'private'-Prototypes
43private string _kill_alias( string str );
44
45// globale Variablen
46nosave mapping enemies;
MG Mud User88f12472016-06-24 23:31:02 +020047private nosave int attack_busy;
48nosave int no_more_attacks;
49private nosave int remaining_heart_beats;
50private nosave int att2_time;
51private nosave string last_attack_msg;
52private nosave object *missing_attacks;
MG Mud User88f12472016-06-24 23:31:02 +020053// Cache fuer QueryArmourByType()
54private nosave mapping QABTCache;
55
56protected void create()
57{
58 Set(P_WIMPY, SAVE, F_MODE_AS);
59 Set(P_TOTAL_AC, PROTECTED, F_MODE_AS);
60 Set(P_HANDS, SAVE, F_MODE_AS);
61 Set(P_SHOW_ATTACK_MSG,SAVE,F_MODE_AS);
62 Set(P_RESISTANCE, ({}));
63 Set(P_VULNERABILITY, ({}));
64 Set(P_GUILD_PREPAREBLOCK, SAVE, F_MODE_AS);
65 // Kein Setzen von P_ARMOURS von aussen per Set(). (Per SetProp() geht es
66 // durch die Setmethode).
67 Set(P_ARMOURS,PROTECTED,F_MODE_AS);
68 SetProp(P_ARMOURS, ({}));
69 attack_busy=100;
70 att2_time=0;
71 enemies=([]);
MG Mud User88f12472016-06-24 23:31:02 +020072 team::create();
73 offerHook(H_HOOK_DEFEND,1);
74 offerHook(H_HOOK_ATTACK,1);
75 offerHook(H_HOOK_ATTACK_MOD,1);
76}
77
78#undef DEBUG
79#define DEBUG(x) if (find_object("zesstra")) \
80 tell_object(find_player("zesstra"),x)
81
82public void UpdateResistanceStrengths() {
83 mapping resmods, strmap;
84
85 if ( !mappingp(resmods=Query(P_RESISTANCE_MODIFIER)) )
86 return;
87
88 //erstmal die alte Aufsummation loeschen
89 m_delete(resmods,"me");
90
91 //wenn jetzt leer: Abbruch, keine Res-Modifier da.
92 if (!sizeof(resmods))
93 return(Set(P_RESISTANCE_MODIFIER,0));
94
95 strmap = ([]);
96
97 // ueber alle gesetzten ResModifier gehen
98 foreach(string mod, mapping resmap, object ob: resmods) {
99 if ( !mappingp(resmap) || !sizeof(resmap)
100 || !objectp(ob) ) {
101 m_delete(resmods, mod);
102 continue; // Resi ungueltig, weg damit.
103 }
104 // jetzt noch ueber die Submappings laufen, die die Resis der Objekte
105 // beinhalten.
106 foreach(string reskey, float resi: resmap) {
107 strmap[reskey] = ((strmap[reskey]+1.0)*(resi+1.0))-1.0;
108 }
109 }
110
111 if ( !sizeof(strmap) )
112 Set(P_RESISTANCE_MODIFIER, 0);
113 else
114 Set(P_RESISTANCE_MODIFIER, resmods+([ "me" : strmap; 0 ]) );
115}
116
117public varargs int AddResistanceModifier(mapping mod, string add)
118{ string key;
119 mapping res;
120
121 if ( !mappingp(mod) || !sizeof(mod) || !previous_object() )
122 return 0;
123
124 key = explode(object_name(previous_object()),"#")[0];
125
126 if ( add )
127 key += ("#"+add);
128
129 res = Query(P_RESISTANCE_MODIFIER);
130 mod = deep_copy(mod);
131
132 if ( !mappingp(res) )
133 res = ([ key : mod; previous_object() ]);
134 else
135 res += ([ key : mod; previous_object() ]);
136
137 Set(P_RESISTANCE_MODIFIER, res);
138 UpdateResistanceStrengths();
139
140 return 1;
141}
142
143public varargs void RemoveResistanceModifier(string add)
144{ string key;
145 mapping res;
146
147 if ( !previous_object() )
148 return;
149
150 key = explode(object_name(previous_object()),"#")[0];
151
152 if ( add )
153 key += ("#"+add);
154
155 if ( !mappingp(res = Query(P_RESISTANCE_MODIFIER)) )
156 return;
157
158 m_delete(res, key);
159 Set(P_RESISTANCE_MODIFIER, res);
160 UpdateResistanceStrengths();
161}
162
163// veraltete Prop, aus Kompatibilitaetsgruenden noch vorhanden.
164static mixed _set_resistance(mixed arg)
165{ int i;
166 mapping resimap;
167 mixed old;
168
169 if ( !pointerp(arg) )
170 arg=({arg});
171
172 if ( !mappingp(resimap=Query(P_RESISTANCE_STRENGTHS)) )
173 resimap=([]);
174
175 if ( pointerp(old=QueryProp(P_RESISTANCE)) )
176 for ( i=sizeof(old)-1 ; i>=0 ; i-- )
Zesstrac4052902019-12-29 16:45:21 +0100177 resimap[old[i]]=(1.0+resimap[old[i]])*2.0-1.0;
MG Mud User88f12472016-06-24 23:31:02 +0200178
179 for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
Zesstrac4052902019-12-29 16:45:21 +0100180 resimap[arg[i]]=(1.0+resimap[arg[i]])*0.5-1.0;
MG Mud User88f12472016-06-24 23:31:02 +0200181
182 SetProp(P_RESISTANCE_STRENGTHS,resimap);
183
184 return Set(P_RESISTANCE,arg);
185}
186
187// veraltete Prop, aus Kompatibilitaetsgruenden noch vorhanden.
188static mixed _set_vulnerability(mixed arg)
189{ int i;
190 mapping resimap;
191 mixed old;
192
193 if ( !pointerp(arg) )
194 arg=({arg});
195
196 if ( !mappingp(resimap=Query(P_RESISTANCE_STRENGTHS)) )
197 resimap=([]);
198
199 if ( pointerp(old=QueryProp(P_VULNERABILITY)) )
200 for ( i=sizeof(old)-1 ; i>=0 ; i-- )
Zesstrac4052902019-12-29 16:45:21 +0100201 resimap[old[i]]=(1.0+resimap[old[i]])*0.5-1.0;
MG Mud User88f12472016-06-24 23:31:02 +0200202
203 for ( i=sizeof(arg)-1 ; i>=0 ; i-- )
Zesstrac4052902019-12-29 16:45:21 +0100204 resimap[arg[i]]=(1.0+resimap[arg[i]])*2.0-1.0;
MG Mud User88f12472016-06-24 23:31:02 +0200205
206 SetProp(P_RESISTANCE_STRENGTHS,resimap);
207
208 return Set(P_VULNERABILITY,arg);
209}
210
211
212/** kill - Kampf starten.
213 * Fuegt ob der Feindesliste hinzu.
214 */
215public int Kill(object ob)
Zesstra31e09ab2021-05-10 10:32:05 +0200216{ int res;
MG Mud User88f12472016-06-24 23:31:02 +0200217 int|string no_attack;
218
219 if ( !objectp(ob) )
220 return 0;
221
222 if ( ob->QueryProp(P_GHOST) )
223 {
224 tell_object(ME,ob->Name(WER)+" ist doch schon tot!\n");
225 return -1;
226 }
227
228 if ( no_attack = ob->QueryProp(P_NO_ATTACK) )
229 {
230 if ( stringp(no_attack) )
231 tell_object(ME, no_attack);
232 else
233 tell_object(ME, ob->Name(WER,1)+" laesst sich nicht angreifen!\n");
234
235 return -2;
236 }
237
238 if ( QueryProp(P_NO_ATTACK) )
239 return -3;
240
241 res=InsertEnemy(ob);
242 if (res)
243 tell_object(ME, "Ok.\n");
244 else if (IsEnemy(ob))
245 tell_object(ME, "Jajaja, machst Du doch schon!\n");
246 //else //kein gueltiger Feind, ob wurde nicht eingetragen.
247
248 if ( !res )
249 return -4; // nicht aendern ohne Kill in player/combat.c
250
251 return 1;
252}
253
254public int InsertSingleEnemy(object ob)
255{
256 if ( !living(ob) )
257 return 0;
258
259 // Wie lange verfolgt dieses Living? Wenn Prop nicht gesetzt oder
260 // ungueltig, d.h. kein int oder <= 0, dann wird die Defaultzeit genommen.
Zesstrac4052902019-12-29 16:45:21 +0100261 int hunttime = QueryProp(P_HUNTTIME);
MG Mud User88f12472016-06-24 23:31:02 +0200262 hunttime = (intp(hunttime) && hunttime > 0) ?
263 hunttime / __HEART_BEAT_INTERVAL__ : HUNTTIME;
264
265 // Auch wenn ein Objekt schon als Gegner eingetragen ist, trotzdem die
266 // HUNTTIME erneuern. Das "return 0;" muss aber bleiben, da der Gegner
267 // ja nicht neu ist.
268 //
269 // 09.12.2000, Tiamak
270 if ( member( enemies, ob ) ) {
271 enemies[ob,ENEMY_HUNTTIME] = hunttime;
272 return 0;
273 }
274
275 if ( (ob==ME) || QueryProp(P_NO_ATTACK)
276 || !objectp(ob) || (ob->QueryProp(P_NO_ATTACK))
277 || (ob->QueryProp(P_GHOST)) )
278 return 0;
279
280 object team;
281 if ( ( query_once_interactive(ME) || query_once_interactive(ob) )
282 && objectp(team=Query(P_TEAM)) && (team==(ob->Query(P_TEAM))) )
283 return 0;
284
285 set_heart_beat(1);
286
287 last_attack_msg=0;
288
289 enemies[ob,ENEMY_HUNTTIME] = hunttime;
290 Set(P_LAST_COMBAT_TIME,time());
291
292 return 1;
293}
294
295public int InsertEnemy(object ob)
296{ int res;
297
298 if ( res=InsertSingleEnemy(ob) )
299 InsertEnemyTeam(ob);
300
301 return res;
302}
303
304public object QueryPreferedEnemy()
305{ int sz,r;
306 <int|object>* pref;
307 object enemy;
308
309 enemy=0;
310 if ( pointerp(pref=QueryProp(P_PREFERED_ENEMY)) && ((sz=sizeof(pref))>1)
311 && intp(r=pref[0]) && (random(100)<r) )
312 {
313 enemy=pref[1+random(sz-1)];
314
315 if ( !objectp(enemy) )
316 {
317 pref-=({enemy});
318
319 if ( sizeof(pref)<2 )
320 pref=0;
321
322 SetProp(P_PREFERED_ENEMY,pref);
323
324 return 0;
325 }
326
327 if ( !IsEnemy(enemy) )
328 return 0;
329 }
330
331 return enemy;
332}
333
334public object *PresentEnemies() {
335
336 object *here=({});
337 object *netdead=({});
338 mixed no_attack;
339 foreach(object pl: enemies) {
Zesstrac4052902019-12-29 16:45:21 +0100340 if (no_attack = pl->QueryProp(P_NO_ATTACK)) {
MG Mud User88f12472016-06-24 23:31:02 +0200341 m_delete(enemies,pl);
342 // Und auch im Gegner austragen, sonst haut der weiter zu und dieses
343 // Living wehrt sich nicht. (Zesstra, 26.11.2006)
344 if (stringp(no_attack)) {
345 tell_object(ME, no_attack);
346 pl->StopHuntFor(ME, 1);
347 }
348 else pl->StopHuntFor(ME, 0);
349 }
350 else if ( environment()==environment(pl) )
351 {
352 if ( interactive(pl) || !query_once_interactive(pl) )
353 here+=({pl});
354 else
355 netdead+=({pl});
356 }
357 }
358
359 if ( !sizeof(here) ) // Netztote sind nur Feinde, falls keine anderen da sind
360 return netdead;
361
362 return here;
363}
364
365public varargs object SelectEnemy(object *here)
366{ object enemy;
367
368 if ( !pointerp(here) )
369 here=PresentEnemies();
370
371 if ( !sizeof(here) )
372 return 0;
373
374 if ( !objectp(enemy=QueryPreferedEnemy())
375 || (environment(enemy)!=environment()) )
376 enemy=here[random(sizeof(here))];
377
378 return enemy;
379}
380
381protected void heart_beat() {
382 int hbs;
383 // leider rufen viele Leute einfach das geerbte heart_beat() obwohl sie
384 // schon tot sind und damit das Objekt zerstoert ist.
385 if (!living(this_object()))
386 return;
387
388 // Paralyse pruefen, ggf. reduzieren
389 int dis_attack = QueryProp(P_DISABLE_ATTACK);
390 if ( intp(dis_attack) && (dis_attack > 0) )
391 {
392 SetProp(P_DISABLE_ATTACK, --dis_attack);
393 }
394
395 // Attacken ermitteln: SA_SPEED + Rest aus dem letzten HB
396 hbs = remaining_heart_beats + QuerySkillAttribute(SA_SPEED);
397 if ( hbs <= 0 )
398 hbs = 100;
399
400 // P_ATTACK_BUSY bestimmen
401 if ( attack_busy > 99)
402 attack_busy = 100 + hbs;
403 else
404 attack_busy += 100 + hbs;
405 // mehr fuer Seher...
406 if ( IS_SEER(ME) )
407 attack_busy+=(100+QueryProp(P_LEVEL));
408 // max. 500, d.h. 5 Attacken
409 if ( attack_busy>500 )
410 attack_busy=500;
411
412 // unganzzahligen Rest fuer naechsten HB speichern
413 remaining_heart_beats = hbs % 100;
414 hbs /= 100; // ganze Attacken. ;-)
415
416 if ( hbs > 10 ) // nicht mehr als 10 Attacken.
417 hbs = 10;
418
419 // Falls jemand seit dem letzten HB in diesem Living von aussen Attack()
420 // gerufen hat (nicht ExtraAttack()), wird jetzt was abgezogen.
421 hbs -= no_more_attacks;
422 no_more_attacks = 0;
423
424 // Wie lange verfolgt dieser NPC? Wenn Prop nicht gesetzt oder ungueltig,
425 // d.h. kein int oder <= 0, dann wird die Defaultzeit genommen.
Zesstrac4052902019-12-29 16:45:21 +0100426 int hunttime = QueryProp(P_HUNTTIME);
MG Mud User88f12472016-06-24 23:31:02 +0200427 hunttime = (intp(hunttime) && hunttime > 0) ?
428 hunttime / __HEART_BEAT_INTERVAL__ : HUNTTIME;
429 // erstmal die Hunttimes der Gegner aktualisieren und nebenbei die
430 // anwesenden Feinde merken. Ausserdem ggf. Kampf abbrechen, wenn
431 // P_NO_ATTACK gesetzt ist.
432 // eigentlich ist diese ganze Schleife fast das gleiche wie
433 // PresentEnemies(), aber leider muessen ja die Hunttimes aktulisiert werde,
434 // daher kann man nicht direkt PresentEnemies() nehmen.
435 // TODO: Diese Schleife und PresentEnmies() irgendwie vereinigen.
436 object *netdead=({});
437 object *here=({});
438 object enemy;
439 mixed no_attack;
440 foreach(enemy, int htime: &enemies) { // Keys in enemy
441 // ggf. Meldungen ausgeben und Gegner austragen und dieses Living beim
442 // Gegner austragen.
443 if (no_attack=enemy->QueryProp(P_NO_ATTACK)) {
444 m_delete(enemies,enemy);
445 if ( stringp(no_attack) ) {
446 write( no_attack );
447 enemy->StopHuntFor( ME, 1 );
448 }
449 else
450 enemy->StopHuntFor( ME, 0 );
451 }
452 else if ( environment()==environment(enemy) ) {
453 // Gegner anwesend, als Feind merken...
454 if ( interactive(enemy) || !query_once_interactive(enemy) )
455 here+=({enemy});
456 else
457 netdead+=({enemy});
458 // ... und Hunttime erneuern.
459 htime = hunttime;
460 }
461 // Feind nicht anwesend. Timer dekrementieren und Feind austragen, wenn
462 // Jagdzeit abgelaufen.
463 else if ( (--htime) <= 0 )
464 StopHuntFor(enemy);
465 }
466 // Netztote sind nur Feinde, wenn sonst keine da sind.
467 if ( !sizeof(here) )
468 here=netdead;
469
470 // schonmal abfragen, ob ein Spell vorbereitet wird.
471 mixed pspell=QueryProp(P_PREPARED_SPELL);
472
473 //Da hbs 0 und no_more_attacks durch einen manuellen Aufruf von Attack() im
474 //Spieler >0 sein kann, kann jetzt hbs <0 sein. (s.o.)
475 if (hbs > 0 && sizeof(here)) {
476 foreach(int i: hbs) {
477 // Feind in Nahkampfreichweite finden
478 if ( objectp(enemy=SelectNearEnemy(here)) ) {
479 // Flucht erwuenscht?
480 if ( QueryProp(P_WIMPY) > QueryProp(P_HP) ) {
481 Flee();
482 // Flucht gelungen?
483 if ( enemy && (environment(enemy)!=environment()) ) {
484 here = 0; // naechste Runde neue Feinde suchen
485 enemy = 0;
486 continue; // kein Kampf, Runde ueberspringen
487 }
488 }
489 }
490 else {
491 // keine Feinde gefunden... Abbrechen.
492 break;
493 }
494 // Paralyse gesetzt? -> Abbrechen. Dieses Pruefung muss hier passieren,
495 // damit ggf. die automatische Flucht des Lebewesens durchgefuehrt wird,
496 // auch wenn es paralysiert ist.
497 if (dis_attack > 0) break;
498
499 // Kampf durch Spell-Prepare blockiert?
500 // Keine genaue Abfrage, wird spaeter noch genau geprueft.
501 if ( pspell && QueryProp(P_GUILD_PREPAREBLOCK) )
502 break; // keine Angriffe in diesem HB.
503 // wenn Feind da: hit'em hard.
504 if ( objectp(enemy) )
505 Attack(enemy);
506 // ggf. hat Attack() no_more_attacks gesetzt. Zuruecksetzen
507 no_more_attacks = 0;
508 // naechste Kampfrunde neue Feinde suchen
509 here = 0;
510 } // foreach
511 }
512
513 no_more_attacks=0;
514
515 // Ist ein Spell in Vorbereitung und evtl. jetzt fertig vorbereitet?
516 if ( pointerp(pspell)
517 && (sizeof(pspell)>=3) && intp(pspell[0]) && stringp(pspell[1]) ) {
518 if ( time()>=pspell[0] ) // Kann der Spruch jetzt ausgefuehrt werden?
519 {
520 UseSpell(pspell[2],pspell[1]); // Dann los
521 SetProp(P_PREPARED_SPELL,0);
522 }
523 }
524 else if ( pspell ) // Unbrauchbarer Wert, loeschen
525 SetProp(P_PREPARED_SPELL,0);
526
527} // Ende heart_beat
528
529// wird von NPC gerufen, die Heartbeats nachholen und dabei die Hunttimes um
530// die Anzahl verpasster Heartbeats reduzieren muessen.
531// Wird von Spielerobjekten gerufen, wenn sie nach 'schlafe ein' aufwachen, um
532// die notwendige Anzahl an HB hier abzuziehen und ggf. die Feinde zu expiren,
533// da Spieler ja netztot keinen Heartbeat haben.
534protected void update_hunt_times(int beats) {
535 if (!mappingp(enemies)) return;
536 foreach(object en, int htime: &enemies) { // Mapping-Keys in en
537 htime -= beats;
538 if ( htime <= 0 )
539 StopHuntFor(en);
540 }
541}
542
543public int IsEnemy(object wer)
544{
545 return (member(enemies,wer));
546}
547
548public void StopHuntText(object arg)
549{
550 tell_object(arg,
551 Name(WER,1)+" "+(QueryProp(P_PLURAL)?"jagen ":"jagt ")+
552 (arg->QueryProp(P_PLURAL)?"Euch":"Dich")+" nicht mehr.\n");
553 tell_object(ME,(QueryProp(P_PLURAL)?"Ihr jagt ":"Du jagst ")+
554 arg->name(WEN,1)+" nicht mehr.\n");
555}
556
557public varargs int StopHuntFor(object arg, int silent)
558{
559 if ( !objectp(arg) || !IsEnemy(arg) )
560 return 0;
561
562 if (!silent)
563 StopHuntText(arg);
564
565 m_delete(enemies,arg);
566 last_attack_msg=0;
567
568 return 1;
569}
570
571// Begruessungsschlag nur einmal pro HB
572public void Attack2(object enemy)
573{
574 if ( att2_time > time() )
575 return;
576
577 att2_time=time() + __HEART_BEAT_INTERVAL__;
578 Attack(enemy);
579}
580
581// Fuer evtl. Attack-Aenderungen, ohne dass man gleich das ganze
582// Attack ueberlagern muss:
583protected void InternalModifyAttack(mapping ainfo)
584{
585 return; // Vorerst!
586/*
587 int fac;
588 mixed res;
589
590 fac=100;
591
592 if (CannotSee(1))
593 fac -= 50;
594
595 // Weitere Mali?
596
597 if (fac<100)
598 ainfo[SI_SKILLDAMAGE] = ainfo[SI_SKILLDAMAGE]*fac/100;
599 // Fertig
600*/
601}
602
603// Ausfuehren Eines Angriffs auch wenn es bereits einen Angriff
604// in dieser Runde gegeben hat. Dieser Angriff wird auch nicht
605// gezaehlt. Die Nutzung dieser Funktion ist nur fuer Spezialfaelle
606// gedacht und immer BALANCEPFLICHTIG!
607varargs public void ExtraAttack(object enemy, int ignore_previous)
608{
609 int saved_no_more_attacks;
610
611 // Erstschlagsinfo speichern.
612 saved_no_more_attacks = no_more_attacks;
613
614 // Bei Bedarf bisher schon durchgefuehrte Erstschlaege ignorieren
615 if (ignore_previous) no_more_attacks=0;
616
617 // Normalen Angriff durchfuehren
618 Attack (enemy);
619
620 // Gespeicherten Wert zuruecksetzen
621 no_more_attacks = saved_no_more_attacks;
622
623 return;
624}
625
626public void Attack(object enemy)
627{
628 closure cl;
629 mixed res;
630 mapping ainfo;
631 mapping edefendinfo; // erweiterte Defend-Infos
632 mixed hookData;
633 mixed hookRes;
634
635 if ( no_more_attacks || QueryProp(P_GHOST) ||
636 !objectp(enemy) || !objectp(this_object()) ||
637 (QueryProp(P_DISABLE_ATTACK) > 0) || enemy->QueryProp(P_NO_ATTACK) ||
638 (query_once_interactive(this_object()) && !interactive(this_object())) )
639 return;
640
641 edefendinfo=([]);
642
643 Set(P_LAST_COMBAT_TIME,time());
644
645 // inkrementieren. Diese Variable wird im HB nach den Angriffen genullt.
646 // Diese Variable wird im naechsten Heartbeat von der Anzahl an verfuegbaren
647 // Angriffen des Livings abgezogen. Dies beruecksichtigt also zwischen den
648 // HBs (z.B. extern) gerufene Attacks().
649 no_more_attacks++;
650
651 // Wird das Attack durch einen temporaeren Attack-Hook ersetzt?
652 if ( res=QueryProp(P_TMP_ATTACK_HOOK) )
653 {
654 if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0]) && (time()<res[0])
655 && objectp(res[1]) && stringp(res[2]) )
656 {
657 if ( !(res=call_other(res[1],res[2],enemy)) )
658 return;
659 }
660 else
661 SetProp(P_TMP_ATTACK_HOOK,0);
662 }
663
664 // trigger attack hook
665 hookData=({enemy});
666 hookRes=HookFlow(H_HOOK_ATTACK,hookData);
667 if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
668 if(hookRes[H_RETCODE]==H_CANCELLED){
669 return;
670 }
671 }
672
673 ainfo = ([ SI_ENEMY : enemy,
674 SI_SPELL : 0,
675 ]);
676
677 if ( objectp(ainfo[P_WEAPON]=QueryProp(P_WEAPON)) )
678 {
679 ainfo[P_WEAPON]->TakeFlaw(enemy);
680
681 // Abfrage fuer den Fall, dass Waffe durch das TakeFlaw() zerstoert
682 // wurde. Dann wird das Attack abgebrochen.
683 if ( !objectp(ainfo[P_WEAPON]) )
684 return;
685
686 cl=symbol_function("name",ainfo[P_WEAPON]);
Zesstrac4052902019-12-29 16:45:21 +0100687 ainfo[SI_SKILLDAMAGE_MSG] = (" mit "+funcall(cl,WEM,0));
688 ainfo[SI_SKILLDAMAGE_MSG2] = (" mit "+funcall(cl,WEM,1));
MG Mud User88f12472016-06-24 23:31:02 +0200689
Zesstrac4052902019-12-29 16:45:21 +0100690 ainfo[SI_SKILLDAMAGE] = ainfo[P_WEAPON]->QueryDamage(enemy);
MG Mud User88f12472016-06-24 23:31:02 +0200691
692 cl=symbol_function("QueryProp",ainfo[P_WEAPON]);
693 ainfo[SI_SKILLDAMAGE_TYPE] = funcall(cl,P_DAM_TYPE);
Zesstrac4052902019-12-29 16:45:21 +0100694 ainfo[P_WEAPON_TYPE] = funcall(cl,P_WEAPON_TYPE);
695 ainfo[P_NR_HANDS] = funcall(cl,P_NR_HANDS);
696 ainfo[P_WC] = funcall(cl,P_WC);
MG Mud User88f12472016-06-24 23:31:02 +0200697
698 // Zweihaendige Waffe?
699 if ( ainfo[P_NR_HANDS]==2
700 && mappingp(res=UseSkill(SK_TWOHANDED,deep_copy(ainfo)))
701 && member(res,SI_SKILLDAMAGE) )
702 {
703 // Nur den neuen Schadenswert uebernehmen.
Zesstrac4052902019-12-29 16:45:21 +0100704 ainfo[SI_SKILLDAMAGE]=res[SI_SKILLDAMAGE];
MG Mud User88f12472016-06-24 23:31:02 +0200705 }
706 }
707 else // Keine Waffe gezueckt
708 {
709 /* Check if there is a magical attack */
710 if ( mappingp(res=UseSkill(SK_MAGIC_ATTACK,([SI_ENEMY:enemy]))) )
711 {
Zesstrac4052902019-12-29 16:45:21 +0100712 ainfo[SI_SKILLDAMAGE]=res[SI_SKILLDAMAGE];
MG Mud User88f12472016-06-24 23:31:02 +0200713 ainfo[SI_SKILLDAMAGE_TYPE]=res[SI_SKILLDAMAGE_TYPE];
714
715 if ( stringp(res[SI_SKILLDAMAGE_MSG]) )
Zesstrac4052902019-12-29 16:45:21 +0100716 ainfo[SI_SKILLDAMAGE_MSG] = " mit "+res[SI_SKILLDAMAGE_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200717 else
718 ainfo[SI_SKILLDAMAGE_MSG] = " mit magischen Faehigkeiten";
719
720 if ( stringp(res[SI_SKILLDAMAGE_MSG2]) )
Zesstrac4052902019-12-29 16:45:21 +0100721 ainfo[SI_SKILLDAMAGE_MSG2] = " mit "+res[SI_SKILLDAMAGE_MSG2];
MG Mud User88f12472016-06-24 23:31:02 +0200722 else
Zesstrac4052902019-12-29 16:45:21 +0100723 ainfo[SI_SKILLDAMAGE_MSG2] = ainfo[SI_SKILLDAMAGE_MSG];
MG Mud User88f12472016-06-24 23:31:02 +0200724
725 if ( !(ainfo[P_WEAPON_TYPE]=res[P_WEAPON_TYPE]) )
726 ainfo[P_WEAPON_TYPE]=WT_MAGIC;
727
728 if ( member(res,SI_SPELL) )
729 ainfo[SI_SPELL]=res[SI_SPELL];
730 }
731 else
732 {
733 /* Ohne (freie) Haende wird auch nicht angegriffen */
734 if ( interactive(this_object())
735 && (QueryProp(P_USED_HANDS) >= QueryProp(P_MAX_HANDS)) )
736 return ;
737
738 if ( !pointerp(res=QueryProp(P_HANDS)) || (sizeof(res)<2) )
739 return;
740
Zesstrac4052902019-12-29 16:45:21 +0100741 ainfo[SI_SKILLDAMAGE] = (( 2*random(res[1]+1)
MG Mud User88f12472016-06-24 23:31:02 +0200742 + 10*(QueryAttribute(A_STR)) )/3);
743 ainfo[SI_SKILLDAMAGE_TYPE] = res[2];
744 ainfo[SI_SKILLDAMAGE_MSG] = ainfo[SI_SKILLDAMAGE_MSG2]
Zesstrac4052902019-12-29 16:45:21 +0100745 = res[0];
MG Mud User88f12472016-06-24 23:31:02 +0200746 ainfo[P_WEAPON_TYPE] = WT_HANDS;
Zesstrac4052902019-12-29 16:45:21 +0100747 ainfo[P_WC] = res[1];
MG Mud User88f12472016-06-24 23:31:02 +0200748 }
749 } // besondere Faehigkeiten mit diesem Waffentyp?
750 if ( mappingp(res=UseSkill(FIGHT(ainfo[P_WEAPON_TYPE]),
751 deep_copy(ainfo))) )
752 SkillResTransfer(res,ainfo);
753
754 // besondere allgemeine Kampffaehigkeiten?
755 if ( mappingp(res=UseSkill(SK_FIGHT,deep_copy(ainfo))) )
756 SkillResTransfer(res,ainfo);
757
758 // Veraenderungen durch einen Attack-Modifier?
759 if ( (res=QueryProp(P_TMP_ATTACK_MOD)) )
760 {
761 if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0])
762 && (time()<res[0]) && objectp(res[1]) && stringp(res[2]) )
763 {
764 if ( !(res=call_other(res[1],res[2],deep_copy(ainfo)))
765 || !mappingp(res) )
766 return;
767 else
768 SkillResTransfer(res,ainfo);
769 }
770 else
771 SetProp(P_TMP_ATTACK_MOD,0);
772 }
773
774 // trigger attack mod hook
775 hookData=deep_copy(ainfo);
776 hookRes=HookFlow(H_HOOK_ATTACK_MOD,hookData);
777 if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
778 if(hookRes[H_RETCODE]==H_CANCELLED){
779 return;
780 }
781 else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA] &&
782 mappingp(hookRes[H_RETDATA])){
783 SkillResTransfer(hookRes[H_RETDATA],ainfo);
784 }
785 }
786
787 // Interne Modifikationen der Angriffswerte
788 InternalModifyAttack(ainfo);
789
790 if ( !objectp(enemy) )
791 return;
792
793 // hier mal bewusst nicht auf P_PLURAL zuerst testen. in 90% der Faelle
794 // wird eh keine Angriffsmeldung mehr ausgegeben, also pruefen wir das
795 // lieber zuerst. Plural testen zieht schon genug Rechenzeit :/
796 // Nicht meine Idee! Nicht meine Idee! Nachtwind 7.7.2001
797 if ( ainfo[SI_SKILLDAMAGE_MSG2]!=last_attack_msg )
798 {
799 last_attack_msg = ainfo[SI_SKILLDAMAGE_MSG2];
800 if (QueryProp(P_PLURAL))
801 {
802 tell_object( ME, " Ihr greift " + enemy->name(WEN,1)
803 + ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
804 say(" "+(Name(WER,1))+" greifen "+(enemy->name(WEN,1))+
805 ainfo[SI_SKILLDAMAGE_MSG]+" an.\n", enemy );
806 tell_object( enemy, " "+(Name(WER,1))+" greifen "+
807 (enemy->QueryProp(P_PLURAL)?"Euch":"Dich")+
808 ainfo[SI_SKILLDAMAGE_MSG2]+" an.\n" );
809 }
810 else
811 {
812 tell_object( ME, " Du greifst " + enemy->name(WEN,1)
813 + ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
814 say(" "+(Name(WER,2))+" greift "+(enemy->name(WEN,1))+
815 ainfo[SI_SKILLDAMAGE_MSG]+" an.\n", enemy );
816 tell_object( enemy, " "+(Name(WER,1))+" greift "+
817 (enemy->QueryProp(P_PLURAL)?"Euch":"Dich")+
818 ainfo[SI_SKILLDAMAGE_MSG2]+" an.\n" ); }
819 }
820 else if ( Query(P_SHOW_ATTACK_MSG) )
821 if (QueryProp(P_PLURAL))
822 tell_object( ME, " Ihr greift " + enemy->name(WEN,1)
823 + ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
824 else
825 tell_object( ME, " Du greifst " + enemy->name(WEN,1)
826 + ainfo[SI_SKILLDAMAGE_MSG2] + " an.\n" );
827
828 // ainfo in defendinfo merken
829 edefendinfo[ ORIGINAL_AINFO]= deep_copy(ainfo);
830 edefendinfo[ ORIGINAL_DAM]= ainfo[SI_SKILLDAMAGE];
831 edefendinfo[ ORIGINAL_DAMTYPE]= ainfo[SI_SKILLDAMAGE_TYPE];
832 edefendinfo[ CURRENT_DAM]= ainfo[SI_SKILLDAMAGE];
833 edefendinfo[ CURRENT_DAMTYPE]= ainfo[SI_SKILLDAMAGE_TYPE];
834
835 // ainfo[SI_SPELL] auf ein mapping normieren
836 if ( intp(ainfo[SI_SPELL]) )
837 {
838 ainfo[SI_SPELL] = ([ SP_PHYSICAL_ATTACK : !ainfo[SI_SPELL],
839 SP_SHOW_DAMAGE : !ainfo[SI_SPELL],
840 SP_REDUCE_ARMOUR : ([ ]) ]);
841 }
842
843 // defendinfo anhaengen, falls ainfo[SI_SPELL] ein mapping ist
844 if( mappingp(ainfo[SI_SPELL]))
845 {
846 ainfo[SI_SPELL][EINFO_DEFEND]=edefendinfo;
847 }
848
849
850 enemy->Defend(ainfo[SI_SKILLDAMAGE], ainfo[SI_SKILLDAMAGE_TYPE],
851 ainfo[SI_SPELL], this_object());
852
853
854 //edefendinfo=([]);
855
856 /* Done attacking */
857}
858
859public void AddDefender(object friend)
Bugfix7dfd0e02024-01-05 17:23:32 +0100860{
861 if(!objectp(friend))
MG Mud User88f12472016-06-24 23:31:02 +0200862 return;
863
Bugfix7dfd0e02024-01-05 17:23:32 +0100864 object* defs = QueryProp(P_DEFENDERS) || ({});
Bugfix84549232024-01-05 18:18:21 +0100865 defs -= ({0});
MG Mud User88f12472016-06-24 23:31:02 +0200866
Bugfix7dfd0e02024-01-05 17:23:32 +0100867 if(friend in defs)
MG Mud User88f12472016-06-24 23:31:02 +0200868 return;
869
Bugfix7dfd0e02024-01-05 17:23:32 +0100870 defs += ({friend});
871 SetProp(P_DEFENDERS, defs);
MG Mud User88f12472016-06-24 23:31:02 +0200872}
873
874public void RemoveDefender(object friend)
Bugfix7dfd0e02024-01-05 17:23:32 +0100875{
MG Mud User88f12472016-06-24 23:31:02 +0200876 if ( !objectp(friend) )
877 return;
878
Bugfix7dfd0e02024-01-05 17:23:32 +0100879 object* defs = QueryProp(P_DEFENDERS) || ({});
Bugfix84549232024-01-05 18:18:21 +0100880 defs -= ({0});
MG Mud User88f12472016-06-24 23:31:02 +0200881
Bugfix7dfd0e02024-01-05 17:23:32 +0100882 if(!(friend in defs))
MG Mud User88f12472016-06-24 23:31:02 +0200883 return;
884
885 defs -= ({friend});
Bugfix7dfd0e02024-01-05 17:23:32 +0100886 SetProp(P_DEFENDERS, defs);
MG Mud User88f12472016-06-24 23:31:02 +0200887}
888
Bugfixa2d8e632024-01-01 21:36:12 +0100889public object* QueryDefenders()
890{
891 return filter(QueryProp(P_DEFENDERS) || ({}), #'objectp);
892}
893
894public object* QueryPresentDefenders(object* defenders =
895 QueryProp(P_DEFENDERS))
896{
897 // Alles selbst pruefen, um Durchlaeufe zu sparen.
898 return filter(defenders,
899 function int(object o)
900 {
901 return (objectp(o) &&
902 (environment(o) == environment(ME) || environment(o) == ME));
903 });
904}
905
906public object* QueryNearDefenders(object* defenders =
907 QueryProp(P_DEFENDERS))
908{
909 // alles selbst pruefen, um Durchlaeufe zu sparen.
910 return filter(defenders,
911 function int(object o)
912 {
Zesstra8b8604c2024-04-04 00:38:39 +0200913 // Nahe sind: Objekte im eigenen Inventar oder Objekt im Environment,
914 // welche in Teamreihe 1 oder 0 (haben einfach keine Teamreihe) sind.
Bugfixa2d8e632024-01-01 21:36:12 +0100915 return (objectp(o) &&
Zesstra8b8604c2024-04-04 00:38:39 +0200916 ( environment(o) == ME ||
917 (environment(o) == environment(ME) && o->PresentPosition() <= 1) )
918 );
919 }
920 );
Bugfixa2d8e632024-01-01 21:36:12 +0100921}
922
MG Mud User88f12472016-06-24 23:31:02 +0200923public void InformDefend(object enemy)
924{
925 UseSkill(SK_INFORM_DEFEND,([ SI_ENEMY : enemy,
926 SI_FRIEND : previous_object() ]));
927 // Oh, oh - ich hoffe mal, dass InformDefend wirklich NUR aus Defend
928 // eines befreundeten livings aufgerufen wird... (Silvana)
929 // This is only experimental... ;)
930}
931
932public <int|string*|mapping>* DefendOther(int dam, string|string* dam_type,
933 int|mapping spell, object enemy)
934{
935 <int|string*|mapping>* res;
936
937 if ( (res=UseSkill(SK_DEFEND_OTHER,([ SI_SKILLDAMAGE : dam,
938 SI_SKILLDAMAGE_TYPE : dam_type,
939 SI_SPELL : spell,
940 SI_FRIEND : previous_object(),
941 SI_ENEMY : enemy ])))
942 && pointerp(res) )
943 return res;
944
945 return 0;
946}
947
948public void CheckWimpyAndFlee()
949{
950 if ( (QueryProp(P_WIMPY)>QueryProp(P_HP)) && !TeamFlee()
951 && find_call_out("Flee")<0 )
952 call_out(#'Flee,0,environment());
953}
954
955protected string mess(string msg,object me,object enemy)
956{ closure mname, ename;
Zesstra31e09ab2021-05-10 10:32:05 +0200957 string *parts;
MG Mud User88f12472016-06-24 23:31:02 +0200958 int i;
959
960 mname = symbol_function("name", me);
961 ename = symbol_function("name", enemy);
962
963 parts=regexplode(msg,"@WE[A-Z]*[12]");
964 for ( i=sizeof(parts)-2 ; i>=1 ; i-=2 )
965 {
966 switch(parts[i])
967 {
968 case "@WER1": parts[i]=funcall(mname,WER,1); break;
969 case "@WESSEN1": parts[i]=funcall(mname,WESSEN,1); break;
970 case "@WEM1": parts[i]=funcall(mname,WEM,1); break;
971 case "@WEN1": parts[i]=funcall(mname,WEN,1); break;
972 case "@WER2": parts[i]=funcall(ename,WER,1); break;
973 case "@WESSEN2": parts[i]=funcall(ename,WESSEN,1); break;
974 case "@WEM2": parts[i]=funcall(ename,WEM,1); break;
975 case "@WEN2": parts[i]=funcall(ename,WEN,1); break;
976 default: ;
977 }
978 }
979
980 return break_string(capitalize(implode(parts,"")),78," ",1);
981}
982
983// Fuer evtl. Defend-Aenderungen, ohne dass man gleich das ganze
984// Attack ueberlagern muss:
985protected void InternalModifyDefend(int dam, string* dt, mapping spell, object enemy)
986{
987 return;
988}
989
Zesstrada3ad452019-01-16 22:04:05 +0100990protected nomask void normalize_defend_args(int dam, string|string* dam_type,
991 int|mapping spell, object enemy)
992{
993 // this_player(), wenn kein enemy bekannt...
994 enemy ||= this_player();
995
996 // Schadenstyp ueberpruefen
997 if ( !pointerp(dam_type) )
998 dam_type = ({ dam_type });
999
1000 // Und das Spellmapping pruefen, erzeugen, ergaenzen etc.
1001 if ( intp(spell) )
1002 spell = ([ SP_PHYSICAL_ATTACK : !spell,
1003 SP_SHOW_DAMAGE : !spell,
1004 SP_REDUCE_ARMOUR : ([ ]),
1005 EINFO_DEFEND : ([ORIGINAL_DAM:dam,
1006 ORIGINAL_DAMTYPE:dam_type ])
1007 ]);
1008 else if ( mappingp(spell) )
1009 {
1010 // testen ob eine erweiterte defendinfo vorhanden ist
1011 if(!member(spell,EINFO_DEFEND))
1012 {
1013 // wenn nicht, koennen wir an den fehlenden Infos wenig machen, aber
1014 // zumindest ergaenzen wir es und schreiben die (hier) initialen dam und
1015 // dam_type rein.
1016 spell[EINFO_DEFEND] = ([ORIGINAL_DAM:dam,
1017 ORIGINAL_DAMTYPE:dam_type]);
1018 }
1019 if ( !mappingp(spell[SP_REDUCE_ARMOUR]) )
1020 spell[SP_REDUCE_ARMOUR] = ([]);
1021 }
1022 else // Illegaler spell-Parameter
1023 raise_error(sprintf("Ungueltiger Typ des spell-Arguments: %d\n",
1024 get_type_info(spell,0)));
1025}
1026
MG Mud User88f12472016-06-24 23:31:02 +02001027public int Defend(int dam, string|string* dam_type, int|mapping spell, object enemy)
1028{
MG Mud User88f12472016-06-24 23:31:02 +02001029 mixed res,res2;
1030 object *armours,tmp;
1031 mixed hookData;
1032 mixed hookRes;
Zesstra31e09ab2021-05-10 10:32:05 +02001033 int i;
Zesstrada3ad452019-01-16 22:04:05 +01001034
MG Mud User88f12472016-06-24 23:31:02 +02001035 // string what, how;
1036 string enname, myname;
1037
Zesstrada3ad452019-01-16 22:04:05 +01001038 normalize_defend_args(&dam, &dam_type, &spell, &enemy);
1039
MG Mud User88f12472016-06-24 23:31:02 +02001040 // Testen, ob dieses Lebewesen ueberhaupt angegriffen werden darf
1041 if ( !this_object() || !enemy || QueryProp(P_NO_ATTACK)
1042 || ( query_once_interactive(enemy) && ! interactive(enemy) ) )
1043 return 0;
1044
MG Mud User88f12472016-06-24 23:31:02 +02001045 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1046 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1047
1048 // Testen, ob der Angreifer schon als Feind registriert worden ist.
1049 // Wenn nein, registrieren.
1050 if ( !IsEnemy(enemy) && !spell[SP_NO_ENEMY] )
1051 {
1052 spell[EINFO_DEFEND][ENEMY_INSERTED]=1;
1053 InsertEnemy(enemy);
1054 }
1055
1056 // RFR-Taktik abfangen
1057 if ( !QueryProp(P_ENABLE_IN_ATTACK_OUT) )
1058 {
1059 i=time()-(enemy->QueryProp(P_LAST_MOVE));
1060 // Gegner hat sich bewegt, man selbst nicht
1061 if ( (i<3) && (time()-QueryProp(P_LAST_MOVE)>=5) )
1062 {
1063 // Bei Erster Kampfrunde wenige Schaden
1064 dam/=(4-i);
1065 spell[EINFO_DEFEND][RFR_REDUCE]=dam;
1066 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1067 }
1068 }
1069
Bugfix84549232024-01-05 18:18:21 +01001070 // Man kann Verteidiger haben. Diese kommen als erste zum Zuge, falls sie
1071 // anwesend sind.
1072 object* defs_here = QueryPresentDefenders();
1073 if (sizeof(defs_here))
1074 {
1075 // Die werden erstmal alle informiert.
1076 defs_here->InformDefend(enemy);
1077 // Leider koennten dabei Objekte zerstoert worden sein. *seufz*
1078 defs_here -= ({0});
MG Mud User88f12472016-06-24 23:31:02 +02001079
Bugfix84549232024-01-05 18:18:21 +01001080 // Anwesende Verteidiger eintragen.
1081 spell[EINFO_DEFEND][PRESENT_DEFENDERS] = defs_here;
MG Mud User88f12472016-06-24 23:31:02 +02001082
Bugfix84549232024-01-05 18:18:21 +01001083 // Bei physischen Angriffen koennen nur Verteidiger aus Reihe 1 helfen
1084 // (z.B. fuer Rueckendeckung der Kaempfer)
1085 if(spell[SP_PHYSICAL_ATTACK])
1086 {
1087 defs_here = QueryNearDefenders(defs_here);
MG Mud User88f12472016-06-24 23:31:02 +02001088 }
1089
Bugfix84549232024-01-05 18:18:21 +01001090 if(sizeof(defs_here))
MG Mud User88f12472016-06-24 23:31:02 +02001091 {
Bugfix84549232024-01-05 18:18:21 +01001092 // Wenn in Frage kommende Verteidiger anwesend sind, einen davon
1093 // auswaehlen...
1094 <object|int|string*|mapping>* edefendtmp = ({defs_here[random(sizeof(defs_here))], 0, 0, 0});
1095 // ... und DefendOther() darin rufen. Das wirkt wie ein Defend-Hook-Light
1096 res = call_other(edefendtmp[DEF_DEFENDER], "DefendOther",
1097 dam, dam_type, spell, enemy);
1098 // Wenn das Defender valide Daten geliefert hat und den Schaden oder
1099 // Schadenstyp geaendert hat (z.B. Umwandlung von Feuer nach Eis),
1100 // werden die neuen Daten uebernommen.
1101 if(pointerp(res) && (sizeof(res)>=3) && intp(res[0])
MG Mud User88f12472016-06-24 23:31:02 +02001102 && pointerp(res[1]))
1103 {
Bugfix84549232024-01-05 18:18:21 +01001104 dam = res[0];
1105 edefendtmp[DEF_DAM] = dam;
1106 dam_type = res[1];
1107 edefendtmp[DEF_DAMTYPE] = dam_type;
1108 if(mappingp(res[2]))
MG Mud User88f12472016-06-24 23:31:02 +02001109 {
Bugfix84549232024-01-05 18:18:21 +01001110 spell = res[2];
MG Mud User88f12472016-06-24 23:31:02 +02001111 // teuer, aber geht nicht anders (Rekursion vermeiden)
Bugfix84549232024-01-05 18:18:21 +01001112 edefendtmp[DEF_SPELL] = deep_copy(res[2]);
MG Mud User88f12472016-06-24 23:31:02 +02001113 }
Bugfix84549232024-01-05 18:18:21 +01001114 // Die Werte sind jetzt auch die aktuell wirksamen Werte.
1115 spell[EINFO_DEFEND][CURRENT_DAMTYPE] = dam_type;
1116 spell[EINFO_DEFEND][CURRENT_DAM] = dam;
MG Mud User88f12472016-06-24 23:31:02 +02001117 }
Bugfix84549232024-01-05 18:18:21 +01001118 spell[EINFO_DEFEND][DEFENDING_DEFENDER] = edefendtmp;
MG Mud User88f12472016-06-24 23:31:02 +02001119 }
1120 } // Ende Defender-Verarbeitung
1121
1122
1123 // Ueber einen P_TMP_DEFEND_HOOK werden z.B. Schutzzauber gehandhabt
1124 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_NOHOOK;
1125 if ( res=QueryProp(P_TMP_DEFEND_HOOK) )
1126 {
1127 if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0]) && (time()<res[0])
1128 && objectp(res[1]) && stringp(res[2]) )
1129 {
1130 if ( !(res=call_other(res[1],res[2],dam,dam_type,spell,enemy)) )
1131 {
1132 // Ein P_TMP_DEFEND_HOOK kann den Schaden vollstaendig abfangen,
1133 // das Defend wird dann hier abgebrochen *SICK* und es wird nur
1134 // noch getestet, ob man in die Flucht geschlagen wurde
1135 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
1136 CheckWimpyAndFlee();
1137 return 0;
1138 }
1139 else
1140 {
1141 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
1142 if ( pointerp(res) && (sizeof(res)>=3)
1143 && intp(res[0] && pointerp(res[1])) )
1144 {
1145 mixed edefendtmp=({0,0,0});
1146 // Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
1147 // -art sowie die Spell-Infos veraendern
1148 dam=res[0];
1149 edefendtmp[HOOK_DAM]=dam;
1150 dam_type=res[1];
1151 edefendtmp[HOOK_DAMTYPE]=dam_type;
1152
1153 if ( mappingp(res[2]) )
1154 {
1155 spell=res[2];
1156 // Waeh. Teuer. Aber geht nicht anders.
1157 edefendtmp[HOOK_SPELL]=deep_copy(spell);
1158 }
1159 spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
1160 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1161 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1162 }
1163 }
1164 }
1165 else
1166 SetProp(P_TMP_DEFEND_HOOK,0);
1167 } // P_TMP_DEFEND_HOOK
1168
1169 // trigger defend hook
1170 hookData=({dam,dam_type,spell,enemy});
1171 hookRes=HookFlow(H_HOOK_DEFEND,hookData);
1172 if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
1173 if(hookRes[H_RETCODE]==H_CANCELLED){
1174 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
1175 CheckWimpyAndFlee();
1176 return 0;
1177 }
1178 else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA]){
1179 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
1180 if ( pointerp(hookRes[H_RETDATA]) && (sizeof(hookRes[H_RETDATA])>=3)
1181 && intp(hookRes[H_RETDATA][0] && pointerp(hookRes[H_RETDATA][1])) )
1182 {
1183 mixed edefendtmp=({0,0,0});
1184 // Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
1185 // -art sowie die Spell-Infos veraendern
1186 dam=hookRes[H_RETDATA][0];
1187 edefendtmp[HOOK_DAM]=dam;
1188 dam_type=hookRes[H_RETDATA][1];
1189 edefendtmp[HOOK_DAMTYPE]=dam_type;
1190
1191 if ( mappingp(hookRes[H_RETDATA][2]) )
1192 {
1193 spell=hookRes[H_RETDATA][2];
1194 // Teuer, teuer... :-(
1195 edefendtmp[HOOK_SPELL]=deep_copy(spell);
1196 }
1197 spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
1198 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1199 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1200 }
1201 }
1202 } // Ende Hook-Behandlung
1203
MG Mud User88f12472016-06-24 23:31:02 +02001204 // Es gibt auch Parierwaffen,
1205 if ( objectp(tmp=QueryProp(P_PARRY_WEAPON)) )
1206 {
1207 res2=tmp->QueryDefend(dam_type, spell, enemy);
1208
1209 // Reduzierbare Wirksamkeit der Parierwaffe?
1210 if ( member(spell[SP_REDUCE_ARMOUR],P_PARRY_WEAPON)
1211 && intp(res=spell[SP_REDUCE_ARMOUR][P_PARRY_WEAPON]) && (res>=0) )
1212 {
1213 res2=(res2*res)/100;
1214 }
1215
1216 dam-=res2;
1217 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1218 }
1219
1220 // Jetzt kommen die Ruestungen des Lebewesens ins Spiel (wenn es denn
1221 // welche traegt)
1222
1223 armours=QueryProp(P_ARMOURS)-({0});
1224 if ( (i=sizeof(armours))>0 ) {
1225 string aty;
1226
1227 tmp=armours[random(i)];
1228
1229 if ( objectp(tmp) )
1230 //Uebergabe des Mappings eh als Pointer/Referenz, daher billig
1231 tmp->TakeFlaw(dam_type,spell[EINFO_DEFEND]);
1232
1233 // pro Ruestung ein Key an Platz reservieren
1234 spell[EINFO_DEFEND][DEFEND_ARMOURS]=m_allocate(i,2);
1235 foreach(object armour : armours) {
1236 if ( objectp(armour) ) {
Zesstrac4052902019-12-29 16:45:21 +01001237 aty=armour->QueryProp(P_ARMOUR_TYPE);
MG Mud User88f12472016-06-24 23:31:02 +02001238
1239 if ( member(spell[SP_REDUCE_ARMOUR],aty)
1240 && intp(res2=spell[SP_REDUCE_ARMOUR][aty]) && (res2>=0) )
1241 dam -= (res2*armour->QueryDefend(dam_type, spell, enemy))/100;
1242 else
Zesstrac4052902019-12-29 16:45:21 +01001243 dam -= armour->QueryDefend(dam_type, spell, enemy);
MG Mud User88f12472016-06-24 23:31:02 +02001244 // Schaden NACH DefendFunc vermerken.
1245 // Schutzwirkung VOR DefendFunc (DEF_ARMOUR_PROT) vermerkt
1246 // das QueryDefend() selber.
1247 spell[EINFO_DEFEND][DEFEND_ARMOURS][armour,DEF_ARMOUR_DAM]=dam;
1248 // akt. Schaden vermerken und Schutz der aktuellen Ruestung (vor
1249 // DefendFunc) wieder auf 0 setzen fuer den naechsten Durchlauf.
1250 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1251 spell[EINFO_DEFEND][DEFEND_CUR_ARMOUR_PROT]=0;
1252 }
1253 }
1254 }
1255
1256 // Manche Gilden haben einen Verteidigungsskill. Der kommt jetzt zum
1257 // tragen. Der Skill kann die Schadenshoehe und -art beeinflussen.
1258 spell[EINFO_DEFEND]+=([DEFEND_GUILD:({})]);
1259 if ( mappingp(res=UseSkill(SK_MAGIC_DEFENSE,
1260 ([ SI_ENEMY : enemy,
1261 SI_SKILLDAMAGE : dam,
1262 SI_SKILLDAMAGE_TYPE : dam_type,
1263 SI_SPELL : spell ]))) )
1264 {
Zesstrac4052902019-12-29 16:45:21 +01001265 dam=res[SI_SKILLDAMAGE];
MG Mud User88f12472016-06-24 23:31:02 +02001266
1267 if ( pointerp(res[SI_SKILLDAMAGE_TYPE]) )
1268 dam_type=res[SI_SKILLDAMAGE_TYPE];
1269
1270 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1271 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1272 spell[EINFO_DEFEND][DEFEND_GUILD]=({dam,dam_type});
1273 }
1274
1275 // Evtl. interne Modifikationen
1276 InternalModifyDefend(&dam, &dam_type, &spell, &enemy);
1277
1278 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1279 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1280
1281 // Testen, ob irgendwas im Inventory des Lebewesen auf den Angriff
1282 // "reagiert"
1283 CheckSensitiveAttack(dam,dam_type,spell,enemy);
1284
1285 if ( !objectp(enemy) )
1286 return 0;
1287
1288 // Angriffszeit im Gegner setzen
1289 enemy->SetProp(P_LAST_COMBAT_TIME,time());
1290
1291 // Die Resistenzen des Lebewesens (natuerliche und durch Ruestungen
1292 // gesetzte) beruecksichtigen
1293 dam = to_int(CheckResistance(dam_type)*dam);
1294 spell[EINFO_DEFEND][DEFEND_RESI]=dam;
1295
1296 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1297
1298 // Bei physikalischen Angriffen wird die natuerliche Panzerung und die
1299 // Geschicklichkeit des Lebewesens beruecksichtigt
1300 object stat = find_object("/d/erzmagier/zesstra/pacstat"); // TODO: remove
1301 if ( spell[SP_PHYSICAL_ATTACK] )
1302 {
Zesstra9033eee2021-05-06 19:57:20 +02001303 // Schutz bestimmen, Minimum 1, aber nur wenn P_BODY > 0
1304 // Um Rundungsverluste zu reduzieren, wird P_BODY anfangs mit 10000
1305 // skaliert. Beim runterskalieren wird aufgerundet (Addition von
1306 // 5000 vor Division).
Zesstra5a142be2021-05-09 12:01:12 +02001307 int body = (QueryProp(P_BODY)+QueryAttribute(A_DEX)) * 10000;
Zesstra9033eee2021-05-06 19:57:20 +02001308 res2 = ((body/4 + random(body*3/4 + 1) + 5000)/10000) || 1;
MG Mud User88f12472016-06-24 23:31:02 +02001309 if (stat)
Zesstra5a142be2021-05-09 12:01:12 +02001310 stat->bodystat(body, res2, (random(body)+1)/10000);
MG Mud User88f12472016-06-24 23:31:02 +02001311
1312 // Reduzierbare Wirksamkeit des Bodies?
1313 if ( member(spell[SP_REDUCE_ARMOUR], P_BODY)
1314 && intp(res=spell[SP_REDUCE_ARMOUR][P_BODY]) && (res>=0) )
1315 res2=(res2*res)/100;
1316
1317 dam-=res2;
1318 }
1319 spell[EINFO_DEFEND][DEFEND_BODY]=dam;
1320 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1321
1322 // Ist ueberhaupt noch etwas vom Schaden uebrig geblieben?
1323 if ( dam<0 )
1324 dam = 0;
1325 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1326
1327 // fuer die Statistik
1328 // TODO: entfernen nach Test-Uptime
1329 if (stat)
1330 stat->damagestat(spell[EINFO_DEFEND]);
1331
1332 // Die Anzahl der abzuziehenden Lebenspunkte ist der durch 10 geteilte
1333 // Schadenswert
1334 dam = dam / 10;
1335 spell[EINFO_DEFEND][DEFEND_LOSTLP]=dam;
1336
1337 // evtl. hat entweder der Aufrufer oder das Lebewesen selber eigene
1338 // Schadensmeldungen definiert. Die vom Aufrufer haben Prioritaet.
1339 mixed dam_msg = spell[SP_SHOW_DAMAGE];
1340 // Wenn != 0 (d.h. Treffermeldungen grundsaetzlich erwuenscht), aber kein
1341 // Array, hier im Living fragen.
1342 if (dam_msg && !pointerp(dam_msg))
1343 dam_msg = QueryProp(P_DAMAGE_MSG);
1344
1345 // In den meisten Faellen soll auch eine Schadensmeldung ausgegeben
1346 // werden, die die Hoehe des Schadens (grob) anzeigt
1347 if (spell[SP_SHOW_DAMAGE] && !pointerp(dam_msg)) {
1348 myname=name(WEN);
1349 enname=enemy->Name(WER);
1350 if (enemy->QueryProp(P_PLURAL)) {
1351 switch (dam) {
1352 case 0:
1353 tell_object(enemy,sprintf(" Ihr verfehlt %s.\n",myname));
1354 tell_object(this_object(),sprintf(" %s verfehlen Dich.\n",enname));
1355 tell_room(environment(enemy)||environment(this_object()),
1356 sprintf(" %s verfehlen %s.\n",enname,myname),
1357 ({ enemy, this_object() }));
1358 break;
1359 case 1:
1360 tell_object(enemy,sprintf(" Ihr kitzelt %s am Bauch.\n",myname));
1361 tell_object(this_object(),
1362 sprintf(" %s kitzeln Dich am Bauch.\n",enname));
1363 tell_room(environment(enemy)||environment(this_object()),
1364 sprintf(" %s kitzeln %s am Bauch.\n",enname,myname),
1365 ({ enemy, this_object() }));
1366 break;
1367 case 2..3:
1368 tell_object(enemy,sprintf(" Ihr kratzt %s.\n",myname));
1369 tell_object(this_object(),sprintf(" %s kratzen Dich.\n",enname));
1370 tell_room(environment(enemy)||environment(this_object()),
1371 sprintf(" %s kratzen %s.\n",enname,myname),
1372 ({ enemy, this_object() }));
1373 break;
1374 case 4..5:
1375 tell_object(enemy,sprintf(" Ihr trefft %s.\n",myname));
1376 tell_object(this_object(),sprintf(" %s treffen Dich.\n",enname));
1377 tell_room(environment(enemy)||environment(this_object()),
1378 sprintf(" %s treffen %s.\n",enname,myname),
1379 ({ enemy, this_object() }));
1380 break;
1381 case 6..10:
1382 tell_object(enemy,sprintf(" Ihr trefft %s hart.\n",myname));
1383 tell_object(this_object(),sprintf(" %s treffen Dich hart.\n",enname));
1384 tell_room(environment(enemy)||environment(this_object()),
1385 sprintf(" %s treffen %s hart.\n",enname,myname),
1386 ({ enemy, this_object() }));
1387 break;
1388 case 11..20:
1389 tell_object(enemy,sprintf(" Ihr trefft %s sehr hart.\n",myname));
1390 tell_object(this_object(),
1391 sprintf(" %s treffen Dich sehr hart.\n",enname));
1392 tell_room(environment(enemy)||environment(this_object()),
1393 sprintf(" %s treffen %s sehr hart.\n",enname,myname),
1394 ({ enemy, this_object() }));
1395 break;
1396 case 21..30:
1397 tell_object(enemy,
1398 sprintf(" Ihr schlagt %s mit dem Krachen brechender Knochen.\n",
1399 myname));
1400 tell_object(this_object(),
1401 sprintf(" %s schlagen Dich mit dem Krachen brechender Knochen.\n",
1402 enname));
1403 tell_room(environment(enemy)||environment(this_object()),
1404 sprintf(" %s schlagen %s mit dem Krachen brechender Knochen.\n",
1405 enname,myname), ({ enemy, this_object() }));
1406 break;
1407 case 31..50:
1408 tell_object(enemy,
1409 sprintf(" Ihr zerschmettert %s in kleine Stueckchen.\n",myname));
1410 tell_object(this_object(),
1411 sprintf(" %s zerschmettern Dich in kleine Stueckchen.\n",
1412 enname));
1413 tell_room(environment(enemy)||environment(this_object()),
1414 sprintf(" %s zerschmettern %s in kleine Stueckchen.\n",
1415 enname,myname), ({ enemy, this_object() }));
1416 break;
1417 case 51..75:
1418 tell_object(enemy,sprintf(" Ihr schlagt %s zu Brei.\n",myname));
1419 tell_object(this_object(),
1420 sprintf(" %s schlagen Dich zu Brei.\n",enname));
1421 tell_room(environment(enemy)||environment(this_object()),
1422 sprintf(" %s schlagen %s zu Brei.\n",enname,myname),
1423 ({ enemy, this_object() }));
1424 break;
1425 case 76..100:
1426 tell_object(enemy,sprintf(" Ihr pulverisiert %s.\n",myname));
1427 tell_object(this_object(),sprintf(" %s pulverisieren Dich.\n",enname));
1428 tell_room(environment(enemy)||environment(this_object()),
1429 sprintf(" %s pulverisieren %s.\n",enname,myname),
1430 ({ enemy, this_object() }));
1431 break;
1432 case 101..150:
1433 tell_object(enemy,sprintf(" Ihr zerstaeubt %s.\n",myname));
1434 tell_object(this_object(),sprintf(" %s zerstaeuben Dich.\n",enname));
1435 tell_room(environment(enemy)||environment(this_object()),
1436 sprintf(" %s zerstaeuben %s.\n",enname,myname),
1437 ({ enemy, this_object() }));
1438 break;
1439 case 151..200:
1440 tell_object(enemy,sprintf(" Ihr atomisiert %s.\n",myname));
1441 tell_object(this_object(),sprintf(" %s atomisieren Dich.\n",enname));
1442 tell_room(environment(enemy)||environment(this_object()),
1443 sprintf(" %s atomisieren %s.\n",enname,myname),
1444 ({ enemy, this_object() }));
1445 break;
1446 default:
1447 tell_object(enemy,sprintf(" Ihr vernichtet %s.\n",myname));
1448 tell_object(this_object(),sprintf(" %s vernichten Dich.\n",enname));
1449 tell_room(environment(enemy)||environment(this_object()),
1450 sprintf(" %s vernichten %s.\n",enname,myname),
1451 ({ enemy, this_object() }));
1452 break;
1453 }
1454
1455 }
1456 else {
1457 switch (dam) {
1458 case 0:
1459 tell_object(enemy,sprintf(" Du verfehlst %s.\n",myname));
1460 tell_object(this_object(),sprintf(" %s verfehlt Dich.\n",enname));
1461 tell_room(environment(enemy)||environment(this_object()),
1462 sprintf(" %s verfehlt %s.\n",enname,myname),
1463 ({ enemy, this_object() }));
1464 break;
1465 case 1:
1466 tell_object(enemy,sprintf(" Du kitzelst %s am Bauch.\n",myname));
1467 tell_object(this_object(),
1468 sprintf(" %s kitzelt Dich am Bauch.\n",enname));
1469 tell_room(environment(enemy)||environment(this_object()),
1470 sprintf(" %s kitzelt %s am Bauch.\n",enname,myname),
1471 ({ enemy, this_object() }));
1472 break;
1473 case 2..3:
1474 tell_object(enemy,sprintf(" Du kratzt %s.\n",myname));
1475 tell_object(this_object(),sprintf(" %s kratzt Dich.\n",enname));
1476 tell_room(environment(enemy)||environment(this_object()),
1477 sprintf(" %s kratzt %s.\n",enname,myname),
1478 ({ enemy, this_object() }));
1479 break;
1480 case 4..5:
1481 tell_object(enemy,sprintf(" Du triffst %s.\n",myname));
1482 tell_object(this_object(),sprintf(" %s trifft Dich.\n",enname));
1483 tell_room(environment(enemy)||environment(this_object()),
1484 sprintf(" %s trifft %s.\n",enname,myname),
1485 ({ enemy, this_object() }));
1486 break;
1487 case 6..10:
1488 tell_object(enemy,sprintf(" Du triffst %s hart.\n",myname));
1489 tell_object(this_object(),sprintf(" %s trifft Dich hart.\n",enname));
1490 tell_room(environment(enemy)||environment(this_object()),
1491 sprintf(" %s trifft %s hart.\n",enname,myname),
1492 ({ enemy, this_object() }));
1493 break;
1494 case 11..20:
1495 tell_object(enemy,sprintf(" Du triffst %s sehr hart.\n",myname));
1496 tell_object(this_object(),
1497 sprintf(" %s trifft Dich sehr hart.\n",enname));
1498 tell_room(environment(enemy)||environment(this_object()),
1499 sprintf(" %s trifft %s sehr hart.\n",enname,myname),
1500 ({ enemy, this_object() }));
1501 break;
1502 case 21..30:
1503 tell_object(enemy,
1504 sprintf(" Du schlaegst %s mit dem Krachen brechender Knochen.\n",
1505 myname));
1506 tell_object(this_object(),
1507 sprintf(" %s schlaegt Dich mit dem Krachen brechender Knochen.\n",
1508 enname));
1509 tell_room(environment(enemy)||environment(this_object()),
1510 sprintf(" %s schlaegt %s mit dem Krachen brechender Knochen.\n",
1511 enname,myname), ({ enemy, this_object() }));
1512 break;
1513 case 31..50:
1514 tell_object(enemy,
1515 sprintf(" Du zerschmetterst %s in kleine Stueckchen.\n",myname));
1516 tell_object(this_object(),
1517 sprintf(" %s zerschmettert Dich in kleine Stueckchen.\n",enname));
1518 tell_room(environment(enemy)||environment(this_object()),
1519 sprintf(" %s zerschmettert %s in kleine Stueckchen.\n",
1520 enname,myname), ({ enemy, this_object() }));
1521 break;
1522 case 51..75:
1523 tell_object(enemy,sprintf(" Du schlaegst %s zu Brei.\n",myname));
1524 tell_object(this_object(),
1525 sprintf(" %s schlaegt Dich zu Brei.\n",enname));
1526 tell_room(environment(enemy)||environment(this_object()),
1527 sprintf(" %s schlaegt %s zu Brei.\n",enname,myname),
1528 ({ enemy, this_object() }));
1529 break;
1530 case 76..100:
1531 tell_object(enemy,sprintf(" Du pulverisierst %s.\n",myname));
1532 tell_object(this_object(),sprintf(" %s pulverisiert Dich.\n",enname));
1533 tell_room(environment(enemy)||environment(this_object()),
1534 sprintf(" %s pulverisiert %s.\n",enname,myname),
1535 ({ enemy, this_object() }));
1536 break;
1537 case 101..150:
1538 tell_object(enemy,sprintf(" Du zerstaeubst %s.\n",myname));
1539 tell_object(this_object(),sprintf(" %s zerstaeubt Dich.\n",enname));
1540 tell_room(environment(enemy)||environment(this_object()),
1541 sprintf(" %s zerstaeubt %s.\n",enname,myname),
1542 ({ enemy, this_object() }));
1543 break;
1544 case 151..200:
1545 tell_object(enemy,sprintf(" Du atomisierst %s.\n",myname));
1546 tell_object(this_object(),sprintf(" %s atomisiert Dich.\n",enname));
1547 tell_room(environment(enemy)||environment(this_object()),
1548 sprintf(" %s atomisiert %s.\n",enname,myname),
1549 ({ enemy, this_object() }));
1550 break;
1551 default:
1552 tell_object(enemy,sprintf(" Du vernichtest %s.\n",myname));
1553 tell_object(this_object(),sprintf(" %s vernichtet Dich.\n",enname));
1554 tell_room(environment(enemy)||environment(this_object()),
1555 sprintf(" %s vernichtet %s.\n",enname,myname),
1556 ({ enemy, this_object() }));
1557 break;
1558 }
1559 }
1560 }
1561
1562 // Man kann auch selbst-definierte Schadensmeldungen ausgeben lassen
1563 else if( spell[SP_SHOW_DAMAGE] && pointerp(dam_msg) )
1564 {
1565 for( i=sizeof(dam_msg) ; --i >= 0 ; )
1566 {
1567 if ( dam>dam_msg[i][0] )
1568 {
1569 tell_object(ME,mess(dam_msg[i][1],ME,enemy));
1570 tell_object(enemy,mess(dam_msg[i][2],ME,enemy));
1571 say(mess(dam_msg[i][3],ME,enemy), enemy);
1572 break;
1573 }
1574 }
1575 }
1576 // else (!spell[SP_SHOW_DAMAGE]) keine Schadensmeldung.
1577
1578 // Informationen ueber den letzten Angriff festhalten
1579 Set(P_LAST_DAMTYPES, dam_type);
1580 Set(P_LAST_DAMTIME, time());
1581 Set(P_LAST_DAMAGE, dam);
1582
1583 // Bei Angriffen mit SP_NO_ENEMY-Flag kann man nicht sterben ...
1584 if ( spell[SP_NO_ENEMY] )
1585 reduce_hit_points(dam);
1586 // ... bei allen anderen natuerlich schon
1587 else
1588 do_damage(dam,enemy);
1589
1590 // evtl. ist dies Objekt hier tot...
1591 if (!objectp(ME)) return dam;
1592
1593 // Testen, ob man in die Fucht geschlagen wird
1594 CheckWimpyAndFlee();
1595
1596 // Verursachten Schaden (in LP) zurueckgeben
1597 return dam;
1598}
1599
1600public float CheckResistance(string *dam_type) {
1601 //funktion kriegt die schadensarten uebergeben, schaut sich dieses
1602 //sowie P_RESISTENCE_STRENGTH und P_RESITENCE_MODFIFIER an und berechnet
1603 //einen Faktor, mit dem die urspruengliche Schadensmenge multipliziert
1604 //wird, um den Schaden nach Beruecksichtigung der Resis/Anfaelligkeiten zu
1605 //erhalten. Rueckgabewert normalerweise (s.u.) >=0.
1606 mapping rstren, mod;
1607 float faktor,n;
1608 int i;
1609
1610 mod = Query(P_RESISTANCE_MODIFIER);
1611 if ( mappingp(mod) )
1612 mod = mod["me"];
1613
1614 if ( !mappingp(rstren=Query(P_RESISTANCE_STRENGTHS)) )
1615 {
1616 if (!mappingp(mod))
1617 return 1.0;
1618 else
1619 rstren = ([]);
1620 }
1621
1622 if ( !mappingp(mod) )
1623 mod = ([]);
1624
1625 if ( (i=sizeof(dam_type))<1 )
1626 return 1.0;
1627
1628 n=to_float(i);
1629 faktor=0.0;
1630
1631 //dies hier tut nicht mehr das gewuenschte, wenn in P_RESISTENCE_STRENGTHS
1632 //Faktoren <-1.0 angegeben werden. Rueckgabewerte <0 sind dann moeglich und
1633 //leider werden die Schadensarten ohne Resis nicht mehr richtig verwurstet. :-/
1634 foreach(string dt: dam_type) {
1635 faktor = faktor + (1.0 + to_float(rstren[dt]))
1636 * (1.0 + to_float(mod[dt]))-1.0;
1637 }
1638 return 1.0+(faktor/n);
1639}
1640
1641public varargs mapping StopHuntingMode(int silent)
1642{ mapping save_enemy;
MG Mud User88f12472016-06-24 23:31:02 +02001643
1644 save_enemy=enemies;
1645 if ( !silent )
1646 walk_mapping(enemies, #'StopHuntText); //#');
1647
1648 enemies=m_allocate(0,1);
1649 last_attack_msg=0;
1650
1651 return save_enemy;
1652}
1653
1654public <object*|int*>* QueryEnemies()
1655{
1656 return ({m_indices(enemies),m_values(enemies)});
1657}
1658
1659public mapping GetEnemies()
1660{
1661 return enemies;
1662}
1663
1664public mapping SetEnemies(<object*|int*>* myenemies)
1665{
1666 enemies=mkmapping(myenemies[0],myenemies[1]);
1667 return enemies;
1668}
1669
1670private string _kill_alias( string str )
1671{
1672 return "\\" + str;
1673}
1674
1675public varargs void Flee( object oldenv, int force )
1676{ mixed *exits, exit, dex;
1677 mapping tmp;
1678 int i;
1679 object env;
1680
1681 if ( !environment() )
1682 return;
1683
1684 // mit 'force' kann man die Checks umgehen, damit der Spieler auf jeden
1685 // Fall fluechtet ...
1686 if ( !force && ( oldenv && (oldenv != environment()) ||
1687 query_once_interactive(ME) && (!EnemyPresent() ||
1688 (QueryProp(P_HP) >= QueryProp(P_WIMPY))) ) )
1689 return;
1690
1691 // ... aber Magier fluechten zu lassen ist nicht ganz so einfach ;-)
1692 if ( query_once_interactive(this_object()) && IS_LEARNING(this_object()) )
1693 return;
1694
1695 // Geister brauchen nicht um ihr Leben zu fuerchten
1696 if ( QueryProp(P_GHOST) )
1697 return;
1698
1699 tell_object( ME, "Die Angst ist staerker als Du ... "+
1700 "Du willst nur noch weg hier.\n");
1701
1702 if ( TeamFlee() ) // Erfolgreiche Flucht in hintere Kampfreihe?
1703 return;
1704
1705 env = environment();
1706 tmp = environment()->QueryProp(P_EXITS);
1707 exits = m_indices(tmp);
1708 tmp = environment()->QueryProp(P_SPECIAL_EXITS);
1709 exits += m_indices(tmp);
1710
1711 if ( query_once_interactive(ME) )
1712 exits = map( exits, #'_kill_alias/*'*/ );
1713
1714 // die Fluchtrichtung von Magiern wird aus Sicherheitsgruenden
1715 // nicht ausgewertet
1716 if ( interactive(ME) && IS_SEER(ME) && !IS_LEARNER(ME)
1717 && (dex=QueryProp(P_WIMPY_DIRECTION)) )
1718 {
1719 i = 60 + 4 * (QueryProp(P_LEVEL) - 30) / 3;
1720 exits += ({dex}); // bevorzugte Fluchtrichtung mindestens einmal
1721 }
1722
1723 if ( !sizeof(exits) )
1724 {
1725 tell_object( ME, "Du versuchst zu fliehen, schaffst es aber nicht.\n" );
1726
1727 return;
1728 }
1729
1730 while ( sizeof(exits) && (environment()==env) )
1731 {
1732 if ( dex // Vorzugsweise Fluchtrichtung?
1733 && (member(exits,dex) >= 0) // moeglich?
1734 && (random(100) <= i)) // und Wahrscheinlichkeit gross genug?
1735 exit = dex;
1736 else
1737 exit = exits[random(sizeof(exits))];
1738
1739 catch(command(exit);publish);
1740 exits -= ({exit});
1741 }
1742
1743 if ( environment()==env )
1744 tell_object( ME, "Dein Fluchtversuch ist gescheitert.\n" );
1745}
1746
1747public object EnemyPresent()
1748{
1749 foreach(object en: enemies) {
1750 if (environment()==environment(en))
1751 return en;
1752 }
1753 return 0;
1754}
1755
1756public object InFight()
1757{
1758 return EnemyPresent();
1759}
1760
1761public varargs int StopHuntID(string str, int silent) {
1762
1763 if ( !stringp(str) )
1764 return 0;
1765
1766 int j;
1767 foreach(object en: enemies) {
1768 if (en->id(str)) {
1769 StopHuntFor(en,silent);
1770 j++;
1771 }
1772 }
1773
1774 return j;
1775}
1776
1777public int SpellDefend(object caster, mapping sinfo)
1778{ int re;
1779 mixed res;
1780 string *ind;
1781
1782 re = UseSkill(SK_SPELL_DEFEND,([ SI_SKILLARG : sinfo ,
1783 SI_ENEMY : caster ]) );
1784
1785 if ( (res=QueryProp(P_MAGIC_RESISTANCE_OFFSET)) && mappingp(res)
1786 && pointerp(sinfo[SI_MAGIC_TYPE]))
1787 {
1788 ind = m_indices(res) & sinfo[SI_MAGIC_TYPE];
1789
1790 if (pointerp(ind) && sizeof(ind) ) {
1791 foreach(string index : ind)
1792 re+=res[index];
1793 }
1794 }
1795 else if(res && intp(res))
1796 re+=res;
1797
1798 if ( (re>3333) && query_once_interactive(this_object()) )
1799 re=3333; /* Maximal 33% Abwehrchance bei Spielern */
1800 return re;
1801}
1802
1803// **** this is property-like
1804
1805static int _set_attack_busy(mixed val)
1806{
1807 if ( ((to_int(val))>5) && previous_object(1)
1808 && query_once_interactive(previous_object(1)) )
1809 log_file("ATTACKBUSY",sprintf("%s %d Taeter: %O Opfer: %O\n",
1810 dtime(time()),to_int(val),previous_object(1),this_object()));
1811
1812 attack_busy-=to_int(val*100.0);
1813
1814 if ( attack_busy<-2000 )
1815 attack_busy=-2000;
1816
1817 return attack_busy;
1818}
1819
1820static int _query_attack_busy()
1821{
1822 if (IS_LEARNING(ME))
1823 return 0;
1824
1825 return (attack_busy<100);
1826}
1827
1828// **** local property methods
1829static int _set_wimpy(int i)
1830{
1831 if ( !intp(i) || (i>QueryProp(P_MAX_HP)) || (i<0) )
1832 return 0;
1833
1834 // ggf. Statusreport ausgeben
1835 if (interactive(ME))
1836 status_report(DO_REPORT_WIMPY, i);
1837
1838 return Set(P_WIMPY, i);
1839}
1840
1841static string _set_wimpy_dir(string s) {
1842 // ggf. Statusreport ausgeben
1843 if (interactive(ME))
1844 status_report(DO_REPORT_WIMPY_DIR, s);
1845 return Set(P_WIMPY_DIRECTION, s, F_VALUE);
1846}
1847
1848static mixed _set_hands(mixed h)
1849{
1850 if ( sizeof(h)==2 )
1851 h += ({ ({DT_BLUDGEON}) });
1852 if (!pointerp(h[2]))
1853 h[2] = ({h[2]});
1854 return Set(P_HANDS, h, F_VALUE);
1855}
1856
1857//TODO normalisieren/korrigieren in updates_after_restore().
1858static mixed _query_hands()
1859{
1860 mixed *hands = Query(P_HANDS);
1861 if ( !hands )
1862 return Set(P_HANDS, ({ " mit blossen Haenden", 30, ({DT_BLUDGEON})}));
1863 else if ( sizeof(hands)<3 )
1864 return Set(P_HANDS, ({hands[0], hands[1], ({DT_BLUDGEON})}));
1865 else if ( !pointerp(hands[2]) )
1866 return Set(P_HANDS, ({hands[0], hands[1], ({ hands[2] })}));
1867
1868 return Query(P_HANDS);
1869}
1870
1871static int _query_total_wc()
1872{ mixed res;
1873 int totwc;
1874
1875 if ( objectp(res=QueryProp(P_WEAPON)) )
Zesstrac4052902019-12-29 16:45:21 +01001876 totwc = res->QueryProp(P_WC);
MG Mud User88f12472016-06-24 23:31:02 +02001877 else if ( pointerp(res=QueryProp(P_HANDS)) && sizeof(res)>1
1878 && intp(res[1]) )
Zesstrac4052902019-12-29 16:45:21 +01001879 totwc=res[1];
MG Mud User88f12472016-06-24 23:31:02 +02001880 else
1881 totwc=30;
1882
1883 totwc = ((2*totwc)+(10*QueryAttribute(A_STR)))/3;
1884
1885 return Set(P_TOTAL_WC, totwc, F_VALUE);
1886}
1887
1888static int _query_total_ac() {
1889
1890 int totac = 0;
1891 object *armours = QueryProp(P_ARMOURS);
1892 object parry = QueryProp(P_PARRY_WEAPON);
1893
1894 if ( member(armours,0)>=0 ) {
1895 armours -= ({ 0 });
1896 }
1897
1898 foreach(object armour: armours)
Zesstrac4052902019-12-29 16:45:21 +01001899 totac += armour->QueryProp(P_AC);
MG Mud User88f12472016-06-24 23:31:02 +02001900
1901 if ( objectp(parry) )
1902 totac += parry->QueryProp(P_AC);
1903
1904 totac += (QueryProp(P_BODY)+QueryAttribute(A_DEX));
1905
1906 return Set(P_TOTAL_AC, totac, F_VALUE);
1907}
1908
1909static mapping _query_resistance_strengths() {
1910
1911 UpdateResistanceStrengths();
1912
1913 mapping rstren = copy(Query(P_RESISTANCE_STRENGTHS, F_VALUE));
1914 mapping mod = Query(P_RESISTANCE_MODIFIER, F_VALUE);
1915
1916 if ( !mappingp(rstren) )
1917 rstren = ([]);
1918
1919 if ( !mappingp(mod) || !mappingp(mod=mod["me"]) || !sizeof(mod) )
1920 return rstren;
1921
1922 foreach(string modkey, float modvalue : mod)
1923 rstren[modkey] = ((1.0+rstren[modkey])*(1.0+modvalue))-1.0;
1924
1925 return rstren;
1926}
1927
1928static int _set_disable_attack(int val)
1929{
1930 if (val<-100000)
1931 {
1932 log_file("PARALYSE_TOO_LOW",
1933 sprintf("Wert zu klein: %s, Wert: %d, "
1934 "Aufrufer: %O, Opfer: %O",
1935 ctime(time()),
1936 val,previous_object(1),
1937 this_object()));
1938 }
1939 if ( val>30 )
1940 val=30;
1941 if ( (val>=20) && previous_object(1)!=ME && query_once_interactive(ME) )
1942 log_file("PARALYSE",sprintf("%s %d Taeter: %O Opfer: %O\n",
1943 ctime(time())[4..15],
1944 val,previous_object(1),this_object()));
1945
1946 if (time()<QueryProp(P_NEXT_DISABLE_ATTACK))
1947 {
1948 // gueltige Zeitsperre existiert.
1949 // Erhoehen von P_DISABLE_ATTACK geht dann nicht. (Ja, auch nicht erhoehen
1950 // eines negativen P_DISABLE_ATTACK)
1951 if (val >= QueryProp(P_DISABLE_ATTACK))
1952 return DISABLE_TOO_EARLY;
1953 // val ist kleiner als aktuelles P_DISABLE_ATTACK - das ist erlaubt, ABER es
1954 // darf die bestehende Zeitsperre nicht verringern, daher wird sie nicht
1955 // geaendert.
1956 return Set(P_DISABLE_ATTACK,val,F_VALUE);
1957 }
1958 // es existiert keine gueltige Zeitsperre - dann wird sie nun gesetzt.
1959 // (Sollte val < 0 sein, wird eine Zeitsperre in der Vergangenheit gesetzt,
1960 // die schon abgelaufen ist. Das ist ueberfluessig, schadet aber auch
1961 // nicht.)
1962 SetProp(P_NEXT_DISABLE_ATTACK,time()+val*2*__HEART_BEAT_INTERVAL__);
1963 return Set(P_DISABLE_ATTACK,val);
1964}
1965
1966// Neue Verwaltung der Haende:
1967
1968// P_HANDS_USED_BY enhaelt ein Array mit allen Objekten, die Haende
1969// belegen, jedes kommt so oft vor wie Haende belegt werden.
1970
1971static mixed *_query_hands_used_by()
1972{
1973 return ((Query(P_HANDS_USED_BY, F_VALUE) || ({}) ) - ({0}));
1974}
1975
1976static int _query_used_hands()
1977{
1978 return sizeof(QueryProp(P_HANDS_USED_BY));
1979}
1980
1981static int _query_free_hands()
1982{
1983 return (QueryProp(P_MAX_HANDS)-QueryProp(P_USED_HANDS));
1984}
1985
1986public varargs int UseHands(object ob, int num)
1987{ mixed *h;
1988
1989 if ( !ob && !(ob=previous_object(1)) )
1990 return 0;
1991
1992 if ( (num<=0) && ((num=ob->QueryProp(P_HANDS))<=0) )
1993 return 0;
1994
1995 h=QueryProp(P_HANDS_USED_BY)-({ob});
1996
1997 if ( (sizeof(h)+num)>QueryProp(P_MAX_HANDS) )
1998 return 0;
1999
2000 foreach(int i: num)
2001 h+=({ob});
2002
2003 SetProp(P_HANDS_USED_BY,h);
2004
2005 return 1;
2006}
2007
2008public varargs int FreeHands(object ob)
2009{
2010 if ( !ob && !(ob=previous_object(1)) )
2011 return 0;
2012
2013 SetProp(P_HANDS_USED_BY,QueryProp(P_HANDS_USED_BY)-({ob}));
2014
2015 return 1;
2016}
2017
2018// Kompatiblitaetsfunktionen:
2019
2020static int _set_used_hands(int new_num)
2021{ int old_num, dif;
2022 object ob;
2023
2024 old_num=QueryProp(P_USED_HANDS);
2025
2026 if ( !objectp(ob=previous_object(1)) )
2027 return old_num;
2028
2029 // Meldung ins Debuglog schreiben. Aufrufer sollte gefixt werden.
2030 debug_message(sprintf("P_USED_HANDS in %O wird gesetzt durch %O\n",
2031 this_object(), ob), DMSG_LOGFILE|DMSG_STAMP);
2032
2033 if ( !(dif=new_num-old_num) )
2034 return new_num;
2035
2036 if ( dif>0 )
2037 UseHands(ob,dif);
2038 else
2039 FreeHands(ob);
2040
2041 return QueryProp(P_USED_HANDS);
2042}
2043
2044// Funktionen fuer Begruessungsschlag / Nackenschlag:
2045
2046public int CheckEnemy(object ob)
2047{
2048 return (living(ob) && IsEnemy(ob));
2049}
2050
2051public varargs void ExecuteMissingAttacks(object *remove_attackers)
2052{
2053 if ( !pointerp(missing_attacks) )
2054 missing_attacks=({});
2055
2056 if ( pointerp(remove_attackers) )
2057 missing_attacks-=remove_attackers;
2058
2059 foreach(object ob : missing_attacks) {
2060 if ( objectp(ob) && (environment(ob)==environment()) )
2061 ob->Attack2(ME);
2062 }
2063 missing_attacks=({});
2064}
2065
2066public void InitAttack()
2067{ object ob,next;
2068 closure cb;
2069
2070 if ( !living(ME) )
2071 return;
2072
2073 ExecuteMissingAttacks();
2074 //EMA kann das Living zerstoeren oder toeten...
2075 if (!living(ME) || QueryProp(P_GHOST)) return;
2076
2077 if ( objectp(ob=IsTeamMove()) )
2078 cb=symbol_function("InitAttack_Callback",ob);
2079 else
2080 cb=0;
2081
2082 for ( ob=first_inventory(environment()) ; objectp(ob) ; ob=next)
2083 {
2084 next=next_inventory(ob);
2085
2086 if ( !living(ob) )
2087 continue;
2088
2089 if (ob->IsEnemy(ME))
2090 {
2091 // Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
2092 // aktualisiert und b) werden Teammitglieder von mir bei diesem
2093 // InsertEnemy() ggf. erfasst.
2094 ob->InsertEnemy(ME);
2095
2096 if ( closurep(cb) && funcall(cb,ob) ) // Wird ganzes Team gemoved?
2097 missing_attacks += ({ ob }); // Dann erstmal warten bis alle da sind
2098 else
2099 ob->Attack2(ME);
2100
2101 }
2102 else if ( IsEnemy(ob) )
2103 {
2104 // Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
2105 // aktualisiert und b) werden Teammitglieder von ob bei diesem
2106 // InsertEnemy() ggf. erfasst.
2107 InsertEnemy(ob);
2108 Attack2(ob);
2109 }
2110 //Attack2 kann dieses Objekt zerstoeren oder toeten. Wenn ja: abbruch
2111 if ( !living(ME) || QueryProp(P_GHOST)) break;
2112 }
2113}
2114
2115public void ExitAttack()
2116{
2117 if ( !living(ME) )
2118 return;
2119
2120 // Noch nachzuholende Begruessungsschlaege:
2121 ExecuteMissingAttacks();
2122}
2123
2124public object|object*|mapping QueryArmourByType(string type)
2125{
2126 // Rueckgabewert:
2127 // DIE Ruestung vom Typ <type>, falls <type> nicht AT_MISC,
2128 // Array aller AT_MISC-Ruestungen falls <type> AT_MISC (auch leer),
2129 // Mapping mit allen oben genannten Infos, falls <type> Null
2130
2131 object *armours;
2132 string typ2;
2133
2134 // Wenn Cache vorhanden, dann Cache liefern.
2135 if (mappingp(QABTCache)) {
2136 if (type == AT_MISC)
2137 return QABTCache[AT_MISC] - ({0});
2138 else if (type)
2139 return QABTCache[type];
2140 else
2141 return copy(QABTCache);
2142 }
2143
2144 if ( !pointerp(armours=QueryProp(P_ARMOURS)) )
2145 armours=({});
2146
2147 // Cache erzeugen
2148 QABTCache = ([ AT_MISC: ({}) ]);
2149 foreach(object ob: armours - ({0}) ) {
2150 if ( !stringp(typ2=ob->QueryProp(P_ARMOUR_TYPE)) )
2151 continue;
2152 if ( typ2==AT_MISC )
2153 QABTCache[AT_MISC] += ({ob});
2154 else
2155 QABTCache[typ2] = ob;
2156 }
2157 // Und gewuenschtes Datum liefern.
2158 if (type)
2159 return QABTCache[type];
2160 else
2161 return copy(QABTCache);
2162}
2163
2164//TODO: langfristig waers besser, wenn hier nicht jeder per SetProp() drauf
2165//los schreiben wuerde.
2166static object *_set_armours(object *armours) {
2167 if (pointerp(armours)) {
2168 // Cache wegwerfen
2169 QABTCache = 0;
2170 // armours ggf. unifizieren. Ausserdem Kopie reinschreiben.
2171 return Set(P_ARMOURS, m_indices(mkmapping(armours)), F_VALUE);
2172 }
2173 return QueryProp(P_ARMOURS);
2174}
2175
2176/** Reduziert die Befriedezaehler pro Reset im Durchschnitt um 2.5.
2177 Berechnet ganzzahlige durchschnittliche Resets seit dem letzten Expire und
2178 erhoeht die letzte Expirezeit um Resets*__RESET_TIME__ (Standard Intervall
2179 fuer Reset ist momentan 3600s, im Durchschnitt kommen dann 2700 zwischen 2
2180 Resets bei raus). So wird der unganzzahlige Rest beim naechsten Aufruf
2181 beruecksichtigt.
2182 Diese Variante des Expires wurde gewaehlt, um zu vermeiden, combat.c einen
2183 reset() zu geben und in jedem Reset in jedem Lebewesen ein Expire machen zu
2184 muessen, auch wenn nur in sehr wenigen Lebewesen was gemacht werden muss.
2185 @param[in,out] ph Mapping aus P_PEACE_HISTORY. Wird direkt aktualisiert.
2186 @attention Muss ein gueltiges P_PEACE_HISTORY uebergeben bekommen, anderem
2187 Datentyp inkl. 0 wird die Funktion buggen.
2188 */
2189private void _decay_peace_history(mixed ph) {
2190 // Ganzzahlige resets seit letztem Expire ermitteln. (Durchschnitt)
2191 int resets = (time() - ph[0]) / (__RESET_TIME__*75/100);
2192 // auf Zeitstempel draufrechnen, damit der unganzzahlige Rest nicht
2193 // verlorengeht, der wird beim naechsten Expire dann beruecksichtigt.
2194 ph[0] += resets * (__RESET_TIME__ * 75 / 100);
2195 // pro Reset werden im Durchschnitt 2.5 Versuche abgezogen. (Hier werden
2196 // beim Expire mal 2 und mal 3 Versuche pro Reset gerechnet. Aber falls hier
2197 // viele Resets vergangen sind, ist es vermutlich eh egal, weil die Counter
2198 // auf 0 fallen.)
2199 int expire = resets * (random(2) + 2);
2200 // ueber alle Gilden
2201 mapping tmp=ph[1];
2202 foreach(string key, int count: &tmp ) {
2203 count-=expire;
2204 if (count < 0) count = 0;
2205 }
2206}
2207
2208/** Pacify() dient zur Bestimmung, ob sich ein Lebewesen gerade
2209 * befrieden lassen will.
2210 * Berechnet eine Wahrscheinlichkeit nach unten stehender Formel, welche die
2211 * Intelligenz dieses Lebenwesens, die Intelligenz des Casters und die
2212 * bisherige Anzahl erfolgreicher Befriedungsversuche dieser Gilde eingeht.
2213 * Anschliessend wird aus der Wahrscheinlichkeit und random() bestimmt, ob
2214 * dieser Versuch erfolgreich ist.
2215Formel: w = (INT_CASTER + 10 - ANZ*4) / (INT_ME + 10)
2216INT_CASTER: Caster-Intelligenz, INT_ME: Intelligenz dieses Livings
2217ANZ: Anzahl erfolgreicher Befriedungsversuche
2218
2219Annahme: INT_CASTER === 22, alle Wahrscheinlichkeiten * 100
2220
2221INT_ME Erfolgswahrscheinlichkeiten je nach Anzahl erfolgreicher Versuche
2222 1 2 3 4 5 6 7 8
2223 0 280 240 200 160 120 80 40 0
2224 2 233,33 200 166,67 133,33 100 66,67 33,33 0
2225 4 200 171,43 142,86 114,29 85,71 57,14 28,57 0
2226 6 175 150 125 100 75 50 25 0
2227 8 155,56 133,33 111,11 88,89 66,67 44,44 22,22 0
2228 10 140 120 100 80 60 40 20 0
2229 12 127,27 109,09 90,91 72,73 54,55 36,36 18,18 0
2230 14 116,67 100 83,33 66,67 50 33,33 16,67 0
2231 16 107,69 92,31 76,92 61,54 46,15 30,77 15,38 0
2232 18 100 85,71 71,43 57,14 42,86 28,57 14,29 0
2233 20 93,33 80 66,67 53,33 40 26,67 13,33 0
2234 22 87,5 75 62,5 50 37,5 25 12,5 0
2235 24 82,35 70,59 58,82 47,06 35,29 23,53 11,76 0
2236 26 77,78 66,67 55,56 44,44 33,33 22,22 11,11 0
2237 28 73,68 63,16 52,63 42,11 31,58 21,05 10,53 0
2238 30 70 60 50 40 30 20 10 0
2239 32 66,67 57,14 47,62 38,1 28,57 19,05 9,52 0
2240 34 63,64 54,55 45,45 36,36 27,27 18,18 9,09 0
2241 35 62,22 53,33 44,44 35,56 26,67 17,78 8,89 0
2242 36 60,87 52,17 43,48 34,78 26,09 17,39 8,7 0
2243 38 58,33 50 41,67 33,33 25 16,67 8,33 0
2244 40 56 48 40 32 24 16 8 0
2245 42 53,85 46,15 38,46 30,77 23,08 15,38 7,69 0
2246 44 51,85 44,44 37,04 29,63 22,22 14,81 7,41 0
2247 46 50 42,86 35,71 28,57 21,43 14,29 7,14 0
2248 48 48,28 41,38 34,48 27,59 20,69 13,79 6,9 0
2249 50 46,67 40 33,33 26,67 20 13,33 6,67 0
2250 52 45,16 38,71 32,26 25,81 19,35 12,9 6,45 0
2251 54 43,75 37,5 31,25 25 18,75 12,5 6,25 0
2252 56 42,42 36,36 30,3 24,24 18,18 12,12 6,06 0
2253 58 41,18 35,29 29,41 23,53 17,65 11,76 5,88 0
2254 60 40 34,29 28,57 22,86 17,14 11,43 5,71 0
2255 62 38,89 33,33 27,78 22,22 16,67 11,11 5,56 0
2256 64 37,84 32,43 27,03 21,62 16,22 10,81 5,41 0
2257 66 36,84 31,58 26,32 21,05 15,79 10,53 5,26 0
2258 68 35,9 30,77 25,64 20,51 15,38 10,26 5,13 0
2259 70 35 30 25 20 15 10 5 0
2260 72 34,15 29,27 24,39 19,51 14,63 9,76 4,88 0
2261 74 33,33 28,57 23,81 19,05 14,29 9,52 4,76 0
2262 76 32,56 27,91 23,26 18,6 13,95 9,3 4,65 0
2263 78 31,82 27,27 22,73 18,18 13,64 9,09 4,55 0
2264 80 31,11 26,67 22,22 17,78 13,33 8,89 4,44 0
2265 82 30,43 26,09 21,74 17,39 13,04 8,7 4,35 0
2266 84 29,79 25,53 21,28 17,02 12,77 8,51 4,26 0
2267 86 29,17 25 20,83 16,67 12,5 8,33 4,17 0
2268 88 28,57 24,49 20,41 16,33 12,24 8,16 4,08 0
2269 90 28 24 20 16 12 8 4 0
2270 92 27,45 23,53 19,61 15,69 11,76 7,84 3,92 0
2271 94 26,92 23,08 19,23 15,38 11,54 7,69 3,85 0
2272 96 26,42 22,64 18,87 15,09 11,32 7,55 3,77 0
2273 98 25,93 22,22 18,52 14,81 11,11 7,41 3,7 0
2274 100 25,45 21,82 18,18 14,55 10,91 7,27 3,64 0
2275 * @return 1, falls Befrieden erlaubt ist, 0 sonst.
2276 * @param[in] caster Derjenige, der den Spruch ausfuehrt.
2277 * @attention Wenn acify() 1 zurueckliefert, zaehlt dies als
2278 * erfolgreicher Befriedungsversuch und in diesem Lebewesen wurde
2279 * StopHuntingMode(1) aufgerufen.
2280*/
2281public int Pacify(object caster) {
2282
2283 // wenn das Viech keine Gegner hat, dann witzlos. ;-) Ohne Caster gehts auch
2284 // direkt raus.
2285 if (!mappingp(enemies) || !sizeof(enemies)
2286 || !objectp(caster)) {
2287 return 0;
2288 }
2289
2290 // Wenn P_ACCEPT_PEACE gesetzt ist, altes Verhalten wiederspiegeln
2291 // -> der NPC ist einfach immer befriedbar. Gleiches gilt fuer den Caster
2292 // selber, der wird sich ja nicht gegen das eigene Befriede wehren. Und auch
2293 // im team wehrt man sich nicht gegen das Befriede eines Teamkollegen
2294 if (QueryProp(P_ACCEPT_PEACE)==1 || caster==ME
2295 || member(TeamMembers(), caster) > -1) {
2296 StopHuntingMode(1); // Caster/Gilde sollte eigene Meldung ausgeben
2297 return 1;
2298 }
2299
2300 string gilde = caster->QueryProp(P_GUILD) || "ANY";
2301
2302 // ggf. P_PEACE_HISTORY initialisieren
Zesstrac4052902019-12-29 16:45:21 +01002303 <int|mapping>* ph = QueryProp(P_PEACE_HISTORY);
MG Mud User88f12472016-06-24 23:31:02 +02002304 if (!pointerp(ph))
2305 SetProp(P_PEACE_HISTORY, ph=({time(), ([]) }) );
2306
2307 // ggf. die Zaehler reduzieren.
2308 if ( ph[0] + __RESET_TIME__ * 75/100 < time()) {
2309 _decay_peace_history(&ph);
2310 }
2311
2312 float w = (caster->QueryAttribute(A_INT) + 10 - ph[1][gilde] * 4.0) /
2313 (QueryAttribute(A_INT) + 10);
2314 // auf [0,1] begrenzen.
2315 if (w<0) w=0.0;
2316 else if (w>1) w=1.0;
2317 // w * n ist eine Zahl zwischen 0 und n, wenn w * n > random(n)+1,
2318 // darf befriedet werden. Da das Random fuer grosse Zahlen
2319 // besser verteilt ist, nehm ich n = __INT_MAX__ und vernachlaessige
2320 // ausserdem die +1 beim random().
2321 if (ceil(w * __INT_MAX__) > random(__INT_MAX__) ) {
2322 ph[1][gilde]++;
2323 StopHuntingMode(1);
2324 return 1;
2325 }
2326 // ein SetProp(P_PEACE_HISTORY) kann entfallen, da das Mapping direkt
2327 // geaendert wird. Sollte die Prop allerdings mal ne Querymethode haben,
2328 // welche eine Kopie davon liefert, muss das hier geaendert oder die
2329 // Prop per Query() abgefragt werden.
2330 return 0;
2331}
2332