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