blob: 2ab6562da2e78759073c1e86c105e7fbb9d8ddb4 [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] = ([]);
1025 }
1026 else // Illegaler spell-Parameter
1027 raise_error(sprintf("Ungueltiger Typ des spell-Arguments: %d\n",
1028 get_type_info(spell,0)));
1029}
1030
MG Mud User88f12472016-06-24 23:31:02 +02001031public int Defend(int dam, string|string* dam_type, int|mapping spell, object enemy)
1032{
MG Mud User88f12472016-06-24 23:31:02 +02001033 mixed res,res2;
1034 object *armours,tmp;
1035 mixed hookData;
1036 mixed hookRes;
Zesstra31e09ab2021-05-10 10:32:05 +02001037 int i;
Zesstrada3ad452019-01-16 22:04:05 +01001038
MG Mud User88f12472016-06-24 23:31:02 +02001039 // string what, how;
1040 string enname, myname;
1041
Zesstrada3ad452019-01-16 22:04:05 +01001042 normalize_defend_args(&dam, &dam_type, &spell, &enemy);
1043
MG Mud User88f12472016-06-24 23:31:02 +02001044 // Testen, ob dieses Lebewesen ueberhaupt angegriffen werden darf
1045 if ( !this_object() || !enemy || QueryProp(P_NO_ATTACK)
1046 || ( query_once_interactive(enemy) && ! interactive(enemy) ) )
1047 return 0;
1048
MG Mud User88f12472016-06-24 23:31:02 +02001049 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1050 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1051
1052 // Testen, ob der Angreifer schon als Feind registriert worden ist.
1053 // Wenn nein, registrieren.
1054 if ( !IsEnemy(enemy) && !spell[SP_NO_ENEMY] )
1055 {
1056 spell[EINFO_DEFEND][ENEMY_INSERTED]=1;
1057 InsertEnemy(enemy);
1058 }
1059
1060 // RFR-Taktik abfangen
1061 if ( !QueryProp(P_ENABLE_IN_ATTACK_OUT) )
1062 {
1063 i=time()-(enemy->QueryProp(P_LAST_MOVE));
1064 // Gegner hat sich bewegt, man selbst nicht
1065 if ( (i<3) && (time()-QueryProp(P_LAST_MOVE)>=5) )
1066 {
1067 // Bei Erster Kampfrunde wenige Schaden
1068 dam/=(4-i);
1069 spell[EINFO_DEFEND][RFR_REDUCE]=dam;
1070 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1071 }
1072 }
1073
Bugfix84549232024-01-05 18:18:21 +01001074 // Man kann Verteidiger haben. Diese kommen als erste zum Zuge, falls sie
1075 // anwesend sind.
1076 object* defs_here = QueryPresentDefenders();
1077 if (sizeof(defs_here))
1078 {
1079 // Die werden erstmal alle informiert.
1080 defs_here->InformDefend(enemy);
1081 // Leider koennten dabei Objekte zerstoert worden sein. *seufz*
1082 defs_here -= ({0});
MG Mud User88f12472016-06-24 23:31:02 +02001083
Bugfix84549232024-01-05 18:18:21 +01001084 // Anwesende Verteidiger eintragen.
1085 spell[EINFO_DEFEND][PRESENT_DEFENDERS] = defs_here;
MG Mud User88f12472016-06-24 23:31:02 +02001086
Bugfix84549232024-01-05 18:18:21 +01001087 // Bei physischen Angriffen koennen nur Verteidiger aus Reihe 1 helfen
1088 // (z.B. fuer Rueckendeckung der Kaempfer)
1089 if(spell[SP_PHYSICAL_ATTACK])
1090 {
1091 defs_here = QueryNearDefenders(defs_here);
MG Mud User88f12472016-06-24 23:31:02 +02001092 }
1093
Bugfix84549232024-01-05 18:18:21 +01001094 if(sizeof(defs_here))
MG Mud User88f12472016-06-24 23:31:02 +02001095 {
Bugfix84549232024-01-05 18:18:21 +01001096 // Wenn in Frage kommende Verteidiger anwesend sind, einen davon
1097 // auswaehlen...
1098 <object|int|string*|mapping>* edefendtmp = ({defs_here[random(sizeof(defs_here))], 0, 0, 0});
1099 // ... und DefendOther() darin rufen. Das wirkt wie ein Defend-Hook-Light
1100 res = call_other(edefendtmp[DEF_DEFENDER], "DefendOther",
1101 dam, dam_type, spell, enemy);
1102 // Wenn das Defender valide Daten geliefert hat und den Schaden oder
1103 // Schadenstyp geaendert hat (z.B. Umwandlung von Feuer nach Eis),
1104 // werden die neuen Daten uebernommen.
1105 if(pointerp(res) && (sizeof(res)>=3) && intp(res[0])
MG Mud User88f12472016-06-24 23:31:02 +02001106 && pointerp(res[1]))
1107 {
Bugfix84549232024-01-05 18:18:21 +01001108 dam = res[0];
1109 edefendtmp[DEF_DAM] = dam;
1110 dam_type = res[1];
1111 edefendtmp[DEF_DAMTYPE] = dam_type;
1112 if(mappingp(res[2]))
MG Mud User88f12472016-06-24 23:31:02 +02001113 {
Bugfix84549232024-01-05 18:18:21 +01001114 spell = res[2];
MG Mud User88f12472016-06-24 23:31:02 +02001115 // teuer, aber geht nicht anders (Rekursion vermeiden)
Bugfix84549232024-01-05 18:18:21 +01001116 edefendtmp[DEF_SPELL] = deep_copy(res[2]);
MG Mud User88f12472016-06-24 23:31:02 +02001117 }
Bugfix84549232024-01-05 18:18:21 +01001118 // Die Werte sind jetzt auch die aktuell wirksamen Werte.
1119 spell[EINFO_DEFEND][CURRENT_DAMTYPE] = dam_type;
1120 spell[EINFO_DEFEND][CURRENT_DAM] = dam;
MG Mud User88f12472016-06-24 23:31:02 +02001121 }
Bugfix84549232024-01-05 18:18:21 +01001122 spell[EINFO_DEFEND][DEFENDING_DEFENDER] = edefendtmp;
MG Mud User88f12472016-06-24 23:31:02 +02001123 }
1124 } // Ende Defender-Verarbeitung
1125
1126
1127 // Ueber einen P_TMP_DEFEND_HOOK werden z.B. Schutzzauber gehandhabt
1128 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_NOHOOK;
1129 if ( res=QueryProp(P_TMP_DEFEND_HOOK) )
1130 {
1131 if ( pointerp(res) && (sizeof(res)>=3) && intp(res[0]) && (time()<res[0])
1132 && objectp(res[1]) && stringp(res[2]) )
1133 {
1134 if ( !(res=call_other(res[1],res[2],dam,dam_type,spell,enemy)) )
1135 {
1136 // Ein P_TMP_DEFEND_HOOK kann den Schaden vollstaendig abfangen,
1137 // das Defend wird dann hier abgebrochen *SICK* und es wird nur
1138 // noch getestet, ob man in die Flucht geschlagen wurde
1139 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
1140 CheckWimpyAndFlee();
1141 return 0;
1142 }
1143 else
1144 {
1145 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
1146 if ( pointerp(res) && (sizeof(res)>=3)
1147 && intp(res[0] && pointerp(res[1])) )
1148 {
1149 mixed edefendtmp=({0,0,0});
1150 // Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
1151 // -art sowie die Spell-Infos veraendern
1152 dam=res[0];
1153 edefendtmp[HOOK_DAM]=dam;
1154 dam_type=res[1];
1155 edefendtmp[HOOK_DAMTYPE]=dam_type;
1156
1157 if ( mappingp(res[2]) )
1158 {
1159 spell=res[2];
1160 // Waeh. Teuer. Aber geht nicht anders.
1161 edefendtmp[HOOK_SPELL]=deep_copy(spell);
1162 }
1163 spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
1164 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1165 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1166 }
1167 }
1168 }
1169 else
1170 SetProp(P_TMP_DEFEND_HOOK,0);
1171 } // P_TMP_DEFEND_HOOK
1172
1173 // trigger defend hook
1174 hookData=({dam,dam_type,spell,enemy});
1175 hookRes=HookFlow(H_HOOK_DEFEND,hookData);
1176 if(hookRes && pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
1177 if(hookRes[H_RETCODE]==H_CANCELLED){
1178 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOKINTERRUPT;
1179 CheckWimpyAndFlee();
1180 return 0;
1181 }
1182 else if(hookRes[H_RETCODE]==H_ALTERED && hookRes[H_RETDATA]){
1183 spell[EINFO_DEFEND][DEFEND_HOOK]=DI_HOOK;
1184 if ( pointerp(hookRes[H_RETDATA]) && (sizeof(hookRes[H_RETDATA])>=3)
1185 && intp(hookRes[H_RETDATA][0] && pointerp(hookRes[H_RETDATA][1])) )
1186 {
1187 mixed edefendtmp=({0,0,0});
1188 // Der P_TMP_DEFEND_HOOK kann ebenfalls Schadenshoehe und
1189 // -art sowie die Spell-Infos veraendern
1190 dam=hookRes[H_RETDATA][0];
1191 edefendtmp[HOOK_DAM]=dam;
1192 dam_type=hookRes[H_RETDATA][1];
1193 edefendtmp[HOOK_DAMTYPE]=dam_type;
1194
1195 if ( mappingp(hookRes[H_RETDATA][2]) )
1196 {
1197 spell=hookRes[H_RETDATA][2];
1198 // Teuer, teuer... :-(
1199 edefendtmp[HOOK_SPELL]=deep_copy(spell);
1200 }
1201 spell[EINFO_DEFEND][DEFEND_HOOK]=edefendtmp;
1202 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1203 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1204 }
1205 }
1206 } // Ende Hook-Behandlung
1207
MG Mud User88f12472016-06-24 23:31:02 +02001208 // Es gibt auch Parierwaffen,
1209 if ( objectp(tmp=QueryProp(P_PARRY_WEAPON)) )
1210 {
1211 res2=tmp->QueryDefend(dam_type, spell, enemy);
1212
1213 // Reduzierbare Wirksamkeit der Parierwaffe?
1214 if ( member(spell[SP_REDUCE_ARMOUR],P_PARRY_WEAPON)
1215 && intp(res=spell[SP_REDUCE_ARMOUR][P_PARRY_WEAPON]) && (res>=0) )
1216 {
1217 res2=(res2*res)/100;
1218 }
1219
1220 dam-=res2;
1221 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1222 }
1223
1224 // Jetzt kommen die Ruestungen des Lebewesens ins Spiel (wenn es denn
1225 // welche traegt)
1226
1227 armours=QueryProp(P_ARMOURS)-({0});
1228 if ( (i=sizeof(armours))>0 ) {
1229 string aty;
1230
1231 tmp=armours[random(i)];
1232
1233 if ( objectp(tmp) )
1234 //Uebergabe des Mappings eh als Pointer/Referenz, daher billig
1235 tmp->TakeFlaw(dam_type,spell[EINFO_DEFEND]);
1236
1237 // pro Ruestung ein Key an Platz reservieren
1238 spell[EINFO_DEFEND][DEFEND_ARMOURS]=m_allocate(i,2);
1239 foreach(object armour : armours) {
1240 if ( objectp(armour) ) {
Zesstrac4052902019-12-29 16:45:21 +01001241 aty=armour->QueryProp(P_ARMOUR_TYPE);
MG Mud User88f12472016-06-24 23:31:02 +02001242
1243 if ( member(spell[SP_REDUCE_ARMOUR],aty)
1244 && intp(res2=spell[SP_REDUCE_ARMOUR][aty]) && (res2>=0) )
1245 dam -= (res2*armour->QueryDefend(dam_type, spell, enemy))/100;
1246 else
Zesstrac4052902019-12-29 16:45:21 +01001247 dam -= armour->QueryDefend(dam_type, spell, enemy);
MG Mud User88f12472016-06-24 23:31:02 +02001248 // Schaden NACH DefendFunc vermerken.
1249 // Schutzwirkung VOR DefendFunc (DEF_ARMOUR_PROT) vermerkt
1250 // das QueryDefend() selber.
1251 spell[EINFO_DEFEND][DEFEND_ARMOURS][armour,DEF_ARMOUR_DAM]=dam;
1252 // akt. Schaden vermerken und Schutz der aktuellen Ruestung (vor
1253 // DefendFunc) wieder auf 0 setzen fuer den naechsten Durchlauf.
1254 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1255 spell[EINFO_DEFEND][DEFEND_CUR_ARMOUR_PROT]=0;
1256 }
1257 }
1258 }
1259
1260 // Manche Gilden haben einen Verteidigungsskill. Der kommt jetzt zum
1261 // tragen. Der Skill kann die Schadenshoehe und -art beeinflussen.
1262 spell[EINFO_DEFEND]+=([DEFEND_GUILD:({})]);
1263 if ( mappingp(res=UseSkill(SK_MAGIC_DEFENSE,
1264 ([ SI_ENEMY : enemy,
1265 SI_SKILLDAMAGE : dam,
1266 SI_SKILLDAMAGE_TYPE : dam_type,
1267 SI_SPELL : spell ]))) )
1268 {
Zesstrac4052902019-12-29 16:45:21 +01001269 dam=res[SI_SKILLDAMAGE];
MG Mud User88f12472016-06-24 23:31:02 +02001270
1271 if ( pointerp(res[SI_SKILLDAMAGE_TYPE]) )
1272 dam_type=res[SI_SKILLDAMAGE_TYPE];
1273
1274 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1275 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1276 spell[EINFO_DEFEND][DEFEND_GUILD]=({dam,dam_type});
1277 }
1278
1279 // Evtl. interne Modifikationen
1280 InternalModifyDefend(&dam, &dam_type, &spell, &enemy);
1281
1282 spell[EINFO_DEFEND][CURRENT_DAMTYPE]=dam_type;
1283 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1284
1285 // Testen, ob irgendwas im Inventory des Lebewesen auf den Angriff
1286 // "reagiert"
1287 CheckSensitiveAttack(dam,dam_type,spell,enemy);
1288
1289 if ( !objectp(enemy) )
1290 return 0;
1291
1292 // Angriffszeit im Gegner setzen
1293 enemy->SetProp(P_LAST_COMBAT_TIME,time());
1294
1295 // Die Resistenzen des Lebewesens (natuerliche und durch Ruestungen
1296 // gesetzte) beruecksichtigen
1297 dam = to_int(CheckResistance(dam_type)*dam);
1298 spell[EINFO_DEFEND][DEFEND_RESI]=dam;
1299
1300 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1301
1302 // Bei physikalischen Angriffen wird die natuerliche Panzerung und die
1303 // Geschicklichkeit des Lebewesens beruecksichtigt
1304 object stat = find_object("/d/erzmagier/zesstra/pacstat"); // TODO: remove
1305 if ( spell[SP_PHYSICAL_ATTACK] )
1306 {
Zesstra9033eee2021-05-06 19:57:20 +02001307 // Schutz bestimmen, Minimum 1, aber nur wenn P_BODY > 0
1308 // Um Rundungsverluste zu reduzieren, wird P_BODY anfangs mit 10000
1309 // skaliert. Beim runterskalieren wird aufgerundet (Addition von
1310 // 5000 vor Division).
Zesstra5a142be2021-05-09 12:01:12 +02001311 int body = (QueryProp(P_BODY)+QueryAttribute(A_DEX)) * 10000;
Zesstra9033eee2021-05-06 19:57:20 +02001312 res2 = ((body/4 + random(body*3/4 + 1) + 5000)/10000) || 1;
MG Mud User88f12472016-06-24 23:31:02 +02001313 if (stat)
Zesstra5a142be2021-05-09 12:01:12 +02001314 stat->bodystat(body, res2, (random(body)+1)/10000);
MG Mud User88f12472016-06-24 23:31:02 +02001315
1316 // Reduzierbare Wirksamkeit des Bodies?
1317 if ( member(spell[SP_REDUCE_ARMOUR], P_BODY)
1318 && intp(res=spell[SP_REDUCE_ARMOUR][P_BODY]) && (res>=0) )
1319 res2=(res2*res)/100;
1320
1321 dam-=res2;
1322 }
1323 spell[EINFO_DEFEND][DEFEND_BODY]=dam;
1324 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1325
1326 // Ist ueberhaupt noch etwas vom Schaden uebrig geblieben?
1327 if ( dam<0 )
1328 dam = 0;
1329 spell[EINFO_DEFEND][CURRENT_DAM]=dam;
1330
1331 // fuer die Statistik
1332 // TODO: entfernen nach Test-Uptime
1333 if (stat)
1334 stat->damagestat(spell[EINFO_DEFEND]);
1335
1336 // Die Anzahl der abzuziehenden Lebenspunkte ist der durch 10 geteilte
1337 // Schadenswert
1338 dam = dam / 10;
1339 spell[EINFO_DEFEND][DEFEND_LOSTLP]=dam;
1340
1341 // evtl. hat entweder der Aufrufer oder das Lebewesen selber eigene
1342 // Schadensmeldungen definiert. Die vom Aufrufer haben Prioritaet.
1343 mixed dam_msg = spell[SP_SHOW_DAMAGE];
1344 // Wenn != 0 (d.h. Treffermeldungen grundsaetzlich erwuenscht), aber kein
1345 // Array, hier im Living fragen.
1346 if (dam_msg && !pointerp(dam_msg))
1347 dam_msg = QueryProp(P_DAMAGE_MSG);
1348
1349 // In den meisten Faellen soll auch eine Schadensmeldung ausgegeben
1350 // werden, die die Hoehe des Schadens (grob) anzeigt
1351 if (spell[SP_SHOW_DAMAGE] && !pointerp(dam_msg)) {
1352 myname=name(WEN);
1353 enname=enemy->Name(WER);
1354 if (enemy->QueryProp(P_PLURAL)) {
1355 switch (dam) {
1356 case 0:
1357 tell_object(enemy,sprintf(" Ihr verfehlt %s.\n",myname));
1358 tell_object(this_object(),sprintf(" %s verfehlen Dich.\n",enname));
1359 tell_room(environment(enemy)||environment(this_object()),
1360 sprintf(" %s verfehlen %s.\n",enname,myname),
1361 ({ enemy, this_object() }));
1362 break;
1363 case 1:
1364 tell_object(enemy,sprintf(" Ihr kitzelt %s am Bauch.\n",myname));
1365 tell_object(this_object(),
1366 sprintf(" %s kitzeln Dich am Bauch.\n",enname));
1367 tell_room(environment(enemy)||environment(this_object()),
1368 sprintf(" %s kitzeln %s am Bauch.\n",enname,myname),
1369 ({ enemy, this_object() }));
1370 break;
1371 case 2..3:
1372 tell_object(enemy,sprintf(" Ihr kratzt %s.\n",myname));
1373 tell_object(this_object(),sprintf(" %s kratzen Dich.\n",enname));
1374 tell_room(environment(enemy)||environment(this_object()),
1375 sprintf(" %s kratzen %s.\n",enname,myname),
1376 ({ enemy, this_object() }));
1377 break;
1378 case 4..5:
1379 tell_object(enemy,sprintf(" Ihr trefft %s.\n",myname));
1380 tell_object(this_object(),sprintf(" %s treffen Dich.\n",enname));
1381 tell_room(environment(enemy)||environment(this_object()),
1382 sprintf(" %s treffen %s.\n",enname,myname),
1383 ({ enemy, this_object() }));
1384 break;
1385 case 6..10:
1386 tell_object(enemy,sprintf(" Ihr trefft %s hart.\n",myname));
1387 tell_object(this_object(),sprintf(" %s treffen Dich hart.\n",enname));
1388 tell_room(environment(enemy)||environment(this_object()),
1389 sprintf(" %s treffen %s hart.\n",enname,myname),
1390 ({ enemy, this_object() }));
1391 break;
1392 case 11..20:
1393 tell_object(enemy,sprintf(" Ihr trefft %s sehr hart.\n",myname));
1394 tell_object(this_object(),
1395 sprintf(" %s treffen Dich sehr hart.\n",enname));
1396 tell_room(environment(enemy)||environment(this_object()),
1397 sprintf(" %s treffen %s sehr hart.\n",enname,myname),
1398 ({ enemy, this_object() }));
1399 break;
1400 case 21..30:
1401 tell_object(enemy,
1402 sprintf(" Ihr schlagt %s mit dem Krachen brechender Knochen.\n",
1403 myname));
1404 tell_object(this_object(),
1405 sprintf(" %s schlagen Dich mit dem Krachen brechender Knochen.\n",
1406 enname));
1407 tell_room(environment(enemy)||environment(this_object()),
1408 sprintf(" %s schlagen %s mit dem Krachen brechender Knochen.\n",
1409 enname,myname), ({ enemy, this_object() }));
1410 break;
1411 case 31..50:
1412 tell_object(enemy,
1413 sprintf(" Ihr zerschmettert %s in kleine Stueckchen.\n",myname));
1414 tell_object(this_object(),
1415 sprintf(" %s zerschmettern Dich in kleine Stueckchen.\n",
1416 enname));
1417 tell_room(environment(enemy)||environment(this_object()),
1418 sprintf(" %s zerschmettern %s in kleine Stueckchen.\n",
1419 enname,myname), ({ enemy, this_object() }));
1420 break;
1421 case 51..75:
1422 tell_object(enemy,sprintf(" Ihr schlagt %s zu Brei.\n",myname));
1423 tell_object(this_object(),
1424 sprintf(" %s schlagen Dich zu Brei.\n",enname));
1425 tell_room(environment(enemy)||environment(this_object()),
1426 sprintf(" %s schlagen %s zu Brei.\n",enname,myname),
1427 ({ enemy, this_object() }));
1428 break;
1429 case 76..100:
1430 tell_object(enemy,sprintf(" Ihr pulverisiert %s.\n",myname));
1431 tell_object(this_object(),sprintf(" %s pulverisieren Dich.\n",enname));
1432 tell_room(environment(enemy)||environment(this_object()),
1433 sprintf(" %s pulverisieren %s.\n",enname,myname),
1434 ({ enemy, this_object() }));
1435 break;
1436 case 101..150:
1437 tell_object(enemy,sprintf(" Ihr zerstaeubt %s.\n",myname));
1438 tell_object(this_object(),sprintf(" %s zerstaeuben Dich.\n",enname));
1439 tell_room(environment(enemy)||environment(this_object()),
1440 sprintf(" %s zerstaeuben %s.\n",enname,myname),
1441 ({ enemy, this_object() }));
1442 break;
1443 case 151..200:
1444 tell_object(enemy,sprintf(" Ihr atomisiert %s.\n",myname));
1445 tell_object(this_object(),sprintf(" %s atomisieren Dich.\n",enname));
1446 tell_room(environment(enemy)||environment(this_object()),
1447 sprintf(" %s atomisieren %s.\n",enname,myname),
1448 ({ enemy, this_object() }));
1449 break;
1450 default:
1451 tell_object(enemy,sprintf(" Ihr vernichtet %s.\n",myname));
1452 tell_object(this_object(),sprintf(" %s vernichten Dich.\n",enname));
1453 tell_room(environment(enemy)||environment(this_object()),
1454 sprintf(" %s vernichten %s.\n",enname,myname),
1455 ({ enemy, this_object() }));
1456 break;
1457 }
1458
1459 }
1460 else {
1461 switch (dam) {
1462 case 0:
1463 tell_object(enemy,sprintf(" Du verfehlst %s.\n",myname));
1464 tell_object(this_object(),sprintf(" %s verfehlt Dich.\n",enname));
1465 tell_room(environment(enemy)||environment(this_object()),
1466 sprintf(" %s verfehlt %s.\n",enname,myname),
1467 ({ enemy, this_object() }));
1468 break;
1469 case 1:
1470 tell_object(enemy,sprintf(" Du kitzelst %s am Bauch.\n",myname));
1471 tell_object(this_object(),
1472 sprintf(" %s kitzelt Dich am Bauch.\n",enname));
1473 tell_room(environment(enemy)||environment(this_object()),
1474 sprintf(" %s kitzelt %s am Bauch.\n",enname,myname),
1475 ({ enemy, this_object() }));
1476 break;
1477 case 2..3:
1478 tell_object(enemy,sprintf(" Du kratzt %s.\n",myname));
1479 tell_object(this_object(),sprintf(" %s kratzt Dich.\n",enname));
1480 tell_room(environment(enemy)||environment(this_object()),
1481 sprintf(" %s kratzt %s.\n",enname,myname),
1482 ({ enemy, this_object() }));
1483 break;
1484 case 4..5:
1485 tell_object(enemy,sprintf(" Du triffst %s.\n",myname));
1486 tell_object(this_object(),sprintf(" %s trifft Dich.\n",enname));
1487 tell_room(environment(enemy)||environment(this_object()),
1488 sprintf(" %s trifft %s.\n",enname,myname),
1489 ({ enemy, this_object() }));
1490 break;
1491 case 6..10:
1492 tell_object(enemy,sprintf(" Du triffst %s hart.\n",myname));
1493 tell_object(this_object(),sprintf(" %s trifft Dich hart.\n",enname));
1494 tell_room(environment(enemy)||environment(this_object()),
1495 sprintf(" %s trifft %s hart.\n",enname,myname),
1496 ({ enemy, this_object() }));
1497 break;
1498 case 11..20:
1499 tell_object(enemy,sprintf(" Du triffst %s sehr hart.\n",myname));
1500 tell_object(this_object(),
1501 sprintf(" %s trifft Dich sehr hart.\n",enname));
1502 tell_room(environment(enemy)||environment(this_object()),
1503 sprintf(" %s trifft %s sehr hart.\n",enname,myname),
1504 ({ enemy, this_object() }));
1505 break;
1506 case 21..30:
1507 tell_object(enemy,
1508 sprintf(" Du schlaegst %s mit dem Krachen brechender Knochen.\n",
1509 myname));
1510 tell_object(this_object(),
1511 sprintf(" %s schlaegt Dich mit dem Krachen brechender Knochen.\n",
1512 enname));
1513 tell_room(environment(enemy)||environment(this_object()),
1514 sprintf(" %s schlaegt %s mit dem Krachen brechender Knochen.\n",
1515 enname,myname), ({ enemy, this_object() }));
1516 break;
1517 case 31..50:
1518 tell_object(enemy,
1519 sprintf(" Du zerschmetterst %s in kleine Stueckchen.\n",myname));
1520 tell_object(this_object(),
1521 sprintf(" %s zerschmettert Dich in kleine Stueckchen.\n",enname));
1522 tell_room(environment(enemy)||environment(this_object()),
1523 sprintf(" %s zerschmettert %s in kleine Stueckchen.\n",
1524 enname,myname), ({ enemy, this_object() }));
1525 break;
1526 case 51..75:
1527 tell_object(enemy,sprintf(" Du schlaegst %s zu Brei.\n",myname));
1528 tell_object(this_object(),
1529 sprintf(" %s schlaegt Dich zu Brei.\n",enname));
1530 tell_room(environment(enemy)||environment(this_object()),
1531 sprintf(" %s schlaegt %s zu Brei.\n",enname,myname),
1532 ({ enemy, this_object() }));
1533 break;
1534 case 76..100:
1535 tell_object(enemy,sprintf(" Du pulverisierst %s.\n",myname));
1536 tell_object(this_object(),sprintf(" %s pulverisiert Dich.\n",enname));
1537 tell_room(environment(enemy)||environment(this_object()),
1538 sprintf(" %s pulverisiert %s.\n",enname,myname),
1539 ({ enemy, this_object() }));
1540 break;
1541 case 101..150:
1542 tell_object(enemy,sprintf(" Du zerstaeubst %s.\n",myname));
1543 tell_object(this_object(),sprintf(" %s zerstaeubt Dich.\n",enname));
1544 tell_room(environment(enemy)||environment(this_object()),
1545 sprintf(" %s zerstaeubt %s.\n",enname,myname),
1546 ({ enemy, this_object() }));
1547 break;
1548 case 151..200:
1549 tell_object(enemy,sprintf(" Du atomisierst %s.\n",myname));
1550 tell_object(this_object(),sprintf(" %s atomisiert Dich.\n",enname));
1551 tell_room(environment(enemy)||environment(this_object()),
1552 sprintf(" %s atomisiert %s.\n",enname,myname),
1553 ({ enemy, this_object() }));
1554 break;
1555 default:
1556 tell_object(enemy,sprintf(" Du vernichtest %s.\n",myname));
1557 tell_object(this_object(),sprintf(" %s vernichtet Dich.\n",enname));
1558 tell_room(environment(enemy)||environment(this_object()),
1559 sprintf(" %s vernichtet %s.\n",enname,myname),
1560 ({ enemy, this_object() }));
1561 break;
1562 }
1563 }
1564 }
1565
1566 // Man kann auch selbst-definierte Schadensmeldungen ausgeben lassen
1567 else if( spell[SP_SHOW_DAMAGE] && pointerp(dam_msg) )
1568 {
1569 for( i=sizeof(dam_msg) ; --i >= 0 ; )
1570 {
1571 if ( dam>dam_msg[i][0] )
1572 {
1573 tell_object(ME,mess(dam_msg[i][1],ME,enemy));
1574 tell_object(enemy,mess(dam_msg[i][2],ME,enemy));
1575 say(mess(dam_msg[i][3],ME,enemy), enemy);
1576 break;
1577 }
1578 }
1579 }
1580 // else (!spell[SP_SHOW_DAMAGE]) keine Schadensmeldung.
1581
1582 // Informationen ueber den letzten Angriff festhalten
1583 Set(P_LAST_DAMTYPES, dam_type);
1584 Set(P_LAST_DAMTIME, time());
1585 Set(P_LAST_DAMAGE, dam);
1586
1587 // Bei Angriffen mit SP_NO_ENEMY-Flag kann man nicht sterben ...
1588 if ( spell[SP_NO_ENEMY] )
1589 reduce_hit_points(dam);
1590 // ... bei allen anderen natuerlich schon
1591 else
1592 do_damage(dam,enemy);
1593
1594 // evtl. ist dies Objekt hier tot...
1595 if (!objectp(ME)) return dam;
1596
1597 // Testen, ob man in die Fucht geschlagen wird
1598 CheckWimpyAndFlee();
1599
1600 // Verursachten Schaden (in LP) zurueckgeben
1601 return dam;
1602}
1603
1604public float CheckResistance(string *dam_type) {
1605 //funktion kriegt die schadensarten uebergeben, schaut sich dieses
1606 //sowie P_RESISTENCE_STRENGTH und P_RESITENCE_MODFIFIER an und berechnet
1607 //einen Faktor, mit dem die urspruengliche Schadensmenge multipliziert
1608 //wird, um den Schaden nach Beruecksichtigung der Resis/Anfaelligkeiten zu
1609 //erhalten. Rueckgabewert normalerweise (s.u.) >=0.
1610 mapping rstren, mod;
1611 float faktor,n;
1612 int i;
1613
1614 mod = Query(P_RESISTANCE_MODIFIER);
1615 if ( mappingp(mod) )
1616 mod = mod["me"];
1617
1618 if ( !mappingp(rstren=Query(P_RESISTANCE_STRENGTHS)) )
1619 {
1620 if (!mappingp(mod))
1621 return 1.0;
1622 else
1623 rstren = ([]);
1624 }
1625
1626 if ( !mappingp(mod) )
1627 mod = ([]);
1628
1629 if ( (i=sizeof(dam_type))<1 )
1630 return 1.0;
1631
1632 n=to_float(i);
1633 faktor=0.0;
1634
1635 //dies hier tut nicht mehr das gewuenschte, wenn in P_RESISTENCE_STRENGTHS
1636 //Faktoren <-1.0 angegeben werden. Rueckgabewerte <0 sind dann moeglich und
1637 //leider werden die Schadensarten ohne Resis nicht mehr richtig verwurstet. :-/
1638 foreach(string dt: dam_type) {
1639 faktor = faktor + (1.0 + to_float(rstren[dt]))
1640 * (1.0 + to_float(mod[dt]))-1.0;
1641 }
1642 return 1.0+(faktor/n);
1643}
1644
1645public varargs mapping StopHuntingMode(int silent)
1646{ mapping save_enemy;
MG Mud User88f12472016-06-24 23:31:02 +02001647
1648 save_enemy=enemies;
1649 if ( !silent )
1650 walk_mapping(enemies, #'StopHuntText); //#');
1651
1652 enemies=m_allocate(0,1);
1653 last_attack_msg=0;
1654
1655 return save_enemy;
1656}
1657
1658public <object*|int*>* QueryEnemies()
1659{
1660 return ({m_indices(enemies),m_values(enemies)});
1661}
1662
1663public mapping GetEnemies()
1664{
1665 return enemies;
1666}
1667
1668public mapping SetEnemies(<object*|int*>* myenemies)
1669{
1670 enemies=mkmapping(myenemies[0],myenemies[1]);
1671 return enemies;
1672}
1673
1674private string _kill_alias( string str )
1675{
1676 return "\\" + str;
1677}
1678
1679public varargs void Flee( object oldenv, int force )
1680{ mixed *exits, exit, dex;
1681 mapping tmp;
1682 int i;
1683 object env;
1684
1685 if ( !environment() )
1686 return;
1687
1688 // mit 'force' kann man die Checks umgehen, damit der Spieler auf jeden
1689 // Fall fluechtet ...
1690 if ( !force && ( oldenv && (oldenv != environment()) ||
1691 query_once_interactive(ME) && (!EnemyPresent() ||
1692 (QueryProp(P_HP) >= QueryProp(P_WIMPY))) ) )
1693 return;
1694
1695 // ... aber Magier fluechten zu lassen ist nicht ganz so einfach ;-)
1696 if ( query_once_interactive(this_object()) && IS_LEARNING(this_object()) )
1697 return;
1698
1699 // Geister brauchen nicht um ihr Leben zu fuerchten
1700 if ( QueryProp(P_GHOST) )
1701 return;
1702
1703 tell_object( ME, "Die Angst ist staerker als Du ... "+
1704 "Du willst nur noch weg hier.\n");
1705
1706 if ( TeamFlee() ) // Erfolgreiche Flucht in hintere Kampfreihe?
1707 return;
1708
1709 env = environment();
1710 tmp = environment()->QueryProp(P_EXITS);
1711 exits = m_indices(tmp);
1712 tmp = environment()->QueryProp(P_SPECIAL_EXITS);
1713 exits += m_indices(tmp);
1714
1715 if ( query_once_interactive(ME) )
1716 exits = map( exits, #'_kill_alias/*'*/ );
1717
1718 // die Fluchtrichtung von Magiern wird aus Sicherheitsgruenden
1719 // nicht ausgewertet
1720 if ( interactive(ME) && IS_SEER(ME) && !IS_LEARNER(ME)
1721 && (dex=QueryProp(P_WIMPY_DIRECTION)) )
1722 {
1723 i = 60 + 4 * (QueryProp(P_LEVEL) - 30) / 3;
1724 exits += ({dex}); // bevorzugte Fluchtrichtung mindestens einmal
1725 }
1726
1727 if ( !sizeof(exits) )
1728 {
1729 tell_object( ME, "Du versuchst zu fliehen, schaffst es aber nicht.\n" );
1730
1731 return;
1732 }
1733
1734 while ( sizeof(exits) && (environment()==env) )
1735 {
1736 if ( dex // Vorzugsweise Fluchtrichtung?
1737 && (member(exits,dex) >= 0) // moeglich?
1738 && (random(100) <= i)) // und Wahrscheinlichkeit gross genug?
1739 exit = dex;
1740 else
1741 exit = exits[random(sizeof(exits))];
1742
1743 catch(command(exit);publish);
1744 exits -= ({exit});
1745 }
1746
1747 if ( environment()==env )
1748 tell_object( ME, "Dein Fluchtversuch ist gescheitert.\n" );
1749}
1750
1751public object EnemyPresent()
1752{
1753 foreach(object en: enemies) {
1754 if (environment()==environment(en))
1755 return en;
1756 }
1757 return 0;
1758}
1759
1760public object InFight()
1761{
1762 return EnemyPresent();
1763}
1764
1765public varargs int StopHuntID(string str, int silent) {
1766
1767 if ( !stringp(str) )
1768 return 0;
1769
1770 int j;
1771 foreach(object en: enemies) {
1772 if (en->id(str)) {
1773 StopHuntFor(en,silent);
1774 j++;
1775 }
1776 }
1777
1778 return j;
1779}
1780
1781public int SpellDefend(object caster, mapping sinfo)
1782{ int re;
1783 mixed res;
1784 string *ind;
1785
1786 re = UseSkill(SK_SPELL_DEFEND,([ SI_SKILLARG : sinfo ,
1787 SI_ENEMY : caster ]) );
1788
1789 if ( (res=QueryProp(P_MAGIC_RESISTANCE_OFFSET)) && mappingp(res)
1790 && pointerp(sinfo[SI_MAGIC_TYPE]))
1791 {
1792 ind = m_indices(res) & sinfo[SI_MAGIC_TYPE];
1793
1794 if (pointerp(ind) && sizeof(ind) ) {
1795 foreach(string index : ind)
1796 re+=res[index];
1797 }
1798 }
1799 else if(res && intp(res))
1800 re+=res;
1801
1802 if ( (re>3333) && query_once_interactive(this_object()) )
1803 re=3333; /* Maximal 33% Abwehrchance bei Spielern */
1804 return re;
1805}
1806
1807// **** this is property-like
1808
1809static int _set_attack_busy(mixed val)
1810{
1811 if ( ((to_int(val))>5) && previous_object(1)
1812 && query_once_interactive(previous_object(1)) )
1813 log_file("ATTACKBUSY",sprintf("%s %d Taeter: %O Opfer: %O\n",
1814 dtime(time()),to_int(val),previous_object(1),this_object()));
1815
1816 attack_busy-=to_int(val*100.0);
1817
1818 if ( attack_busy<-2000 )
1819 attack_busy=-2000;
1820
1821 return attack_busy;
1822}
1823
1824static int _query_attack_busy()
1825{
1826 if (IS_LEARNING(ME))
1827 return 0;
1828
1829 return (attack_busy<100);
1830}
1831
1832// **** local property methods
1833static int _set_wimpy(int i)
1834{
1835 if ( !intp(i) || (i>QueryProp(P_MAX_HP)) || (i<0) )
1836 return 0;
1837
1838 // ggf. Statusreport ausgeben
1839 if (interactive(ME))
1840 status_report(DO_REPORT_WIMPY, i);
1841
1842 return Set(P_WIMPY, i);
1843}
1844
1845static string _set_wimpy_dir(string s) {
1846 // ggf. Statusreport ausgeben
1847 if (interactive(ME))
1848 status_report(DO_REPORT_WIMPY_DIR, s);
1849 return Set(P_WIMPY_DIRECTION, s, F_VALUE);
1850}
1851
1852static mixed _set_hands(mixed h)
1853{
1854 if ( sizeof(h)==2 )
1855 h += ({ ({DT_BLUDGEON}) });
1856 if (!pointerp(h[2]))
1857 h[2] = ({h[2]});
1858 return Set(P_HANDS, h, F_VALUE);
1859}
1860
1861//TODO normalisieren/korrigieren in updates_after_restore().
1862static mixed _query_hands()
1863{
1864 mixed *hands = Query(P_HANDS);
1865 if ( !hands )
1866 return Set(P_HANDS, ({ " mit blossen Haenden", 30, ({DT_BLUDGEON})}));
1867 else if ( sizeof(hands)<3 )
1868 return Set(P_HANDS, ({hands[0], hands[1], ({DT_BLUDGEON})}));
1869 else if ( !pointerp(hands[2]) )
1870 return Set(P_HANDS, ({hands[0], hands[1], ({ hands[2] })}));
1871
1872 return Query(P_HANDS);
1873}
1874
1875static int _query_total_wc()
1876{ mixed res;
1877 int totwc;
1878
1879 if ( objectp(res=QueryProp(P_WEAPON)) )
Zesstrac4052902019-12-29 16:45:21 +01001880 totwc = res->QueryProp(P_WC);
MG Mud User88f12472016-06-24 23:31:02 +02001881 else if ( pointerp(res=QueryProp(P_HANDS)) && sizeof(res)>1
1882 && intp(res[1]) )
Zesstrac4052902019-12-29 16:45:21 +01001883 totwc=res[1];
MG Mud User88f12472016-06-24 23:31:02 +02001884 else
1885 totwc=30;
1886
1887 totwc = ((2*totwc)+(10*QueryAttribute(A_STR)))/3;
1888
1889 return Set(P_TOTAL_WC, totwc, F_VALUE);
1890}
1891
1892static int _query_total_ac() {
1893
1894 int totac = 0;
1895 object *armours = QueryProp(P_ARMOURS);
1896 object parry = QueryProp(P_PARRY_WEAPON);
1897
1898 if ( member(armours,0)>=0 ) {
1899 armours -= ({ 0 });
1900 }
1901
1902 foreach(object armour: armours)
Zesstrac4052902019-12-29 16:45:21 +01001903 totac += armour->QueryProp(P_AC);
MG Mud User88f12472016-06-24 23:31:02 +02001904
1905 if ( objectp(parry) )
1906 totac += parry->QueryProp(P_AC);
1907
1908 totac += (QueryProp(P_BODY)+QueryAttribute(A_DEX));
1909
1910 return Set(P_TOTAL_AC, totac, F_VALUE);
1911}
1912
1913static mapping _query_resistance_strengths() {
1914
1915 UpdateResistanceStrengths();
1916
1917 mapping rstren = copy(Query(P_RESISTANCE_STRENGTHS, F_VALUE));
1918 mapping mod = Query(P_RESISTANCE_MODIFIER, F_VALUE);
1919
1920 if ( !mappingp(rstren) )
1921 rstren = ([]);
1922
1923 if ( !mappingp(mod) || !mappingp(mod=mod["me"]) || !sizeof(mod) )
1924 return rstren;
1925
1926 foreach(string modkey, float modvalue : mod)
1927 rstren[modkey] = ((1.0+rstren[modkey])*(1.0+modvalue))-1.0;
1928
1929 return rstren;
1930}
1931
1932static int _set_disable_attack(int val)
1933{
1934 if (val<-100000)
1935 {
1936 log_file("PARALYSE_TOO_LOW",
1937 sprintf("Wert zu klein: %s, Wert: %d, "
1938 "Aufrufer: %O, Opfer: %O",
1939 ctime(time()),
1940 val,previous_object(1),
1941 this_object()));
1942 }
1943 if ( val>30 )
1944 val=30;
1945 if ( (val>=20) && previous_object(1)!=ME && query_once_interactive(ME) )
1946 log_file("PARALYSE",sprintf("%s %d Taeter: %O Opfer: %O\n",
1947 ctime(time())[4..15],
1948 val,previous_object(1),this_object()));
1949
1950 if (time()<QueryProp(P_NEXT_DISABLE_ATTACK))
1951 {
1952 // gueltige Zeitsperre existiert.
1953 // Erhoehen von P_DISABLE_ATTACK geht dann nicht. (Ja, auch nicht erhoehen
1954 // eines negativen P_DISABLE_ATTACK)
1955 if (val >= QueryProp(P_DISABLE_ATTACK))
1956 return DISABLE_TOO_EARLY;
1957 // val ist kleiner als aktuelles P_DISABLE_ATTACK - das ist erlaubt, ABER es
1958 // darf die bestehende Zeitsperre nicht verringern, daher wird sie nicht
1959 // geaendert.
1960 return Set(P_DISABLE_ATTACK,val,F_VALUE);
1961 }
1962 // es existiert keine gueltige Zeitsperre - dann wird sie nun gesetzt.
1963 // (Sollte val < 0 sein, wird eine Zeitsperre in der Vergangenheit gesetzt,
1964 // die schon abgelaufen ist. Das ist ueberfluessig, schadet aber auch
1965 // nicht.)
1966 SetProp(P_NEXT_DISABLE_ATTACK,time()+val*2*__HEART_BEAT_INTERVAL__);
1967 return Set(P_DISABLE_ATTACK,val);
1968}
1969
1970// Neue Verwaltung der Haende:
1971
1972// P_HANDS_USED_BY enhaelt ein Array mit allen Objekten, die Haende
1973// belegen, jedes kommt so oft vor wie Haende belegt werden.
1974
1975static mixed *_query_hands_used_by()
1976{
1977 return ((Query(P_HANDS_USED_BY, F_VALUE) || ({}) ) - ({0}));
1978}
1979
1980static int _query_used_hands()
1981{
1982 return sizeof(QueryProp(P_HANDS_USED_BY));
1983}
1984
1985static int _query_free_hands()
1986{
1987 return (QueryProp(P_MAX_HANDS)-QueryProp(P_USED_HANDS));
1988}
1989
1990public varargs int UseHands(object ob, int num)
1991{ mixed *h;
1992
1993 if ( !ob && !(ob=previous_object(1)) )
1994 return 0;
1995
1996 if ( (num<=0) && ((num=ob->QueryProp(P_HANDS))<=0) )
1997 return 0;
1998
1999 h=QueryProp(P_HANDS_USED_BY)-({ob});
2000
2001 if ( (sizeof(h)+num)>QueryProp(P_MAX_HANDS) )
2002 return 0;
2003
2004 foreach(int i: num)
2005 h+=({ob});
2006
2007 SetProp(P_HANDS_USED_BY,h);
2008
2009 return 1;
2010}
2011
2012public varargs int FreeHands(object ob)
2013{
2014 if ( !ob && !(ob=previous_object(1)) )
2015 return 0;
2016
2017 SetProp(P_HANDS_USED_BY,QueryProp(P_HANDS_USED_BY)-({ob}));
2018
2019 return 1;
2020}
2021
2022// Kompatiblitaetsfunktionen:
2023
2024static int _set_used_hands(int new_num)
2025{ int old_num, dif;
2026 object ob;
2027
2028 old_num=QueryProp(P_USED_HANDS);
2029
2030 if ( !objectp(ob=previous_object(1)) )
2031 return old_num;
2032
2033 // Meldung ins Debuglog schreiben. Aufrufer sollte gefixt werden.
2034 debug_message(sprintf("P_USED_HANDS in %O wird gesetzt durch %O\n",
2035 this_object(), ob), DMSG_LOGFILE|DMSG_STAMP);
2036
2037 if ( !(dif=new_num-old_num) )
2038 return new_num;
2039
2040 if ( dif>0 )
2041 UseHands(ob,dif);
2042 else
2043 FreeHands(ob);
2044
2045 return QueryProp(P_USED_HANDS);
2046}
2047
2048// Funktionen fuer Begruessungsschlag / Nackenschlag:
2049
2050public int CheckEnemy(object ob)
2051{
2052 return (living(ob) && IsEnemy(ob));
2053}
2054
2055public varargs void ExecuteMissingAttacks(object *remove_attackers)
2056{
2057 if ( !pointerp(missing_attacks) )
2058 missing_attacks=({});
2059
2060 if ( pointerp(remove_attackers) )
2061 missing_attacks-=remove_attackers;
2062
2063 foreach(object ob : missing_attacks) {
2064 if ( objectp(ob) && (environment(ob)==environment()) )
2065 ob->Attack2(ME);
2066 }
2067 missing_attacks=({});
2068}
2069
2070public void InitAttack()
2071{ object ob,next;
2072 closure cb;
2073
2074 if ( !living(ME) )
2075 return;
2076
2077 ExecuteMissingAttacks();
2078 //EMA kann das Living zerstoeren oder toeten...
2079 if (!living(ME) || QueryProp(P_GHOST)) return;
2080
2081 if ( objectp(ob=IsTeamMove()) )
2082 cb=symbol_function("InitAttack_Callback",ob);
2083 else
2084 cb=0;
2085
2086 for ( ob=first_inventory(environment()) ; objectp(ob) ; ob=next)
2087 {
2088 next=next_inventory(ob);
2089
2090 if ( !living(ob) )
2091 continue;
2092
2093 if (ob->IsEnemy(ME))
2094 {
2095 // Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
2096 // aktualisiert und b) werden Teammitglieder von mir bei diesem
2097 // InsertEnemy() ggf. erfasst.
2098 ob->InsertEnemy(ME);
2099
2100 if ( closurep(cb) && funcall(cb,ob) ) // Wird ganzes Team gemoved?
2101 missing_attacks += ({ ob }); // Dann erstmal warten bis alle da sind
2102 else
2103 ob->Attack2(ME);
2104
2105 }
2106 else if ( IsEnemy(ob) )
2107 {
2108 // Das ist nicht so sinnlos wie es aussieht. a) werden die Hunttimes
2109 // aktualisiert und b) werden Teammitglieder von ob bei diesem
2110 // InsertEnemy() ggf. erfasst.
2111 InsertEnemy(ob);
2112 Attack2(ob);
2113 }
2114 //Attack2 kann dieses Objekt zerstoeren oder toeten. Wenn ja: abbruch
2115 if ( !living(ME) || QueryProp(P_GHOST)) break;
2116 }
2117}
2118
2119public void ExitAttack()
2120{
2121 if ( !living(ME) )
2122 return;
2123
2124 // Noch nachzuholende Begruessungsschlaege:
2125 ExecuteMissingAttacks();
2126}
2127
2128public object|object*|mapping QueryArmourByType(string type)
2129{
2130 // Rueckgabewert:
2131 // DIE Ruestung vom Typ <type>, falls <type> nicht AT_MISC,
2132 // Array aller AT_MISC-Ruestungen falls <type> AT_MISC (auch leer),
2133 // Mapping mit allen oben genannten Infos, falls <type> Null
2134
2135 object *armours;
2136 string typ2;
2137
2138 // Wenn Cache vorhanden, dann Cache liefern.
2139 if (mappingp(QABTCache)) {
2140 if (type == AT_MISC)
2141 return QABTCache[AT_MISC] - ({0});
2142 else if (type)
2143 return QABTCache[type];
2144 else
2145 return copy(QABTCache);
2146 }
2147
2148 if ( !pointerp(armours=QueryProp(P_ARMOURS)) )
2149 armours=({});
2150
2151 // Cache erzeugen
2152 QABTCache = ([ AT_MISC: ({}) ]);
2153 foreach(object ob: armours - ({0}) ) {
2154 if ( !stringp(typ2=ob->QueryProp(P_ARMOUR_TYPE)) )
2155 continue;
2156 if ( typ2==AT_MISC )
2157 QABTCache[AT_MISC] += ({ob});
2158 else
2159 QABTCache[typ2] = ob;
2160 }
2161 // Und gewuenschtes Datum liefern.
2162 if (type)
2163 return QABTCache[type];
2164 else
2165 return copy(QABTCache);
2166}
2167
2168//TODO: langfristig waers besser, wenn hier nicht jeder per SetProp() drauf
2169//los schreiben wuerde.
2170static object *_set_armours(object *armours) {
2171 if (pointerp(armours)) {
2172 // Cache wegwerfen
2173 QABTCache = 0;
2174 // armours ggf. unifizieren. Ausserdem Kopie reinschreiben.
2175 return Set(P_ARMOURS, m_indices(mkmapping(armours)), F_VALUE);
2176 }
2177 return QueryProp(P_ARMOURS);
2178}
2179
2180/** Reduziert die Befriedezaehler pro Reset im Durchschnitt um 2.5.
2181 Berechnet ganzzahlige durchschnittliche Resets seit dem letzten Expire und
2182 erhoeht die letzte Expirezeit um Resets*__RESET_TIME__ (Standard Intervall
2183 fuer Reset ist momentan 3600s, im Durchschnitt kommen dann 2700 zwischen 2
2184 Resets bei raus). So wird der unganzzahlige Rest beim naechsten Aufruf
2185 beruecksichtigt.
2186 Diese Variante des Expires wurde gewaehlt, um zu vermeiden, combat.c einen
2187 reset() zu geben und in jedem Reset in jedem Lebewesen ein Expire machen zu
2188 muessen, auch wenn nur in sehr wenigen Lebewesen was gemacht werden muss.
2189 @param[in,out] ph Mapping aus P_PEACE_HISTORY. Wird direkt aktualisiert.
2190 @attention Muss ein gueltiges P_PEACE_HISTORY uebergeben bekommen, anderem
2191 Datentyp inkl. 0 wird die Funktion buggen.
2192 */
2193private void _decay_peace_history(mixed ph) {
2194 // Ganzzahlige resets seit letztem Expire ermitteln. (Durchschnitt)
2195 int resets = (time() - ph[0]) / (__RESET_TIME__*75/100);
2196 // auf Zeitstempel draufrechnen, damit der unganzzahlige Rest nicht
2197 // verlorengeht, der wird beim naechsten Expire dann beruecksichtigt.
2198 ph[0] += resets * (__RESET_TIME__ * 75 / 100);
2199 // pro Reset werden im Durchschnitt 2.5 Versuche abgezogen. (Hier werden
2200 // beim Expire mal 2 und mal 3 Versuche pro Reset gerechnet. Aber falls hier
2201 // viele Resets vergangen sind, ist es vermutlich eh egal, weil die Counter
2202 // auf 0 fallen.)
2203 int expire = resets * (random(2) + 2);
2204 // ueber alle Gilden
2205 mapping tmp=ph[1];
2206 foreach(string key, int count: &tmp ) {
2207 count-=expire;
2208 if (count < 0) count = 0;
2209 }
2210}
2211
2212/** Pacify() dient zur Bestimmung, ob sich ein Lebewesen gerade
2213 * befrieden lassen will.
2214 * Berechnet eine Wahrscheinlichkeit nach unten stehender Formel, welche die
2215 * Intelligenz dieses Lebenwesens, die Intelligenz des Casters und die
2216 * bisherige Anzahl erfolgreicher Befriedungsversuche dieser Gilde eingeht.
2217 * Anschliessend wird aus der Wahrscheinlichkeit und random() bestimmt, ob
2218 * dieser Versuch erfolgreich ist.
2219Formel: w = (INT_CASTER + 10 - ANZ*4) / (INT_ME + 10)
2220INT_CASTER: Caster-Intelligenz, INT_ME: Intelligenz dieses Livings
2221ANZ: Anzahl erfolgreicher Befriedungsversuche
2222
2223Annahme: INT_CASTER === 22, alle Wahrscheinlichkeiten * 100
2224
2225INT_ME Erfolgswahrscheinlichkeiten je nach Anzahl erfolgreicher Versuche
2226 1 2 3 4 5 6 7 8
2227 0 280 240 200 160 120 80 40 0
2228 2 233,33 200 166,67 133,33 100 66,67 33,33 0
2229 4 200 171,43 142,86 114,29 85,71 57,14 28,57 0
2230 6 175 150 125 100 75 50 25 0
2231 8 155,56 133,33 111,11 88,89 66,67 44,44 22,22 0
2232 10 140 120 100 80 60 40 20 0
2233 12 127,27 109,09 90,91 72,73 54,55 36,36 18,18 0
2234 14 116,67 100 83,33 66,67 50 33,33 16,67 0
2235 16 107,69 92,31 76,92 61,54 46,15 30,77 15,38 0
2236 18 100 85,71 71,43 57,14 42,86 28,57 14,29 0
2237 20 93,33 80 66,67 53,33 40 26,67 13,33 0
2238 22 87,5 75 62,5 50 37,5 25 12,5 0
2239 24 82,35 70,59 58,82 47,06 35,29 23,53 11,76 0
2240 26 77,78 66,67 55,56 44,44 33,33 22,22 11,11 0
2241 28 73,68 63,16 52,63 42,11 31,58 21,05 10,53 0
2242 30 70 60 50 40 30 20 10 0
2243 32 66,67 57,14 47,62 38,1 28,57 19,05 9,52 0
2244 34 63,64 54,55 45,45 36,36 27,27 18,18 9,09 0
2245 35 62,22 53,33 44,44 35,56 26,67 17,78 8,89 0
2246 36 60,87 52,17 43,48 34,78 26,09 17,39 8,7 0
2247 38 58,33 50 41,67 33,33 25 16,67 8,33 0
2248 40 56 48 40 32 24 16 8 0
2249 42 53,85 46,15 38,46 30,77 23,08 15,38 7,69 0
2250 44 51,85 44,44 37,04 29,63 22,22 14,81 7,41 0
2251 46 50 42,86 35,71 28,57 21,43 14,29 7,14 0
2252 48 48,28 41,38 34,48 27,59 20,69 13,79 6,9 0
2253 50 46,67 40 33,33 26,67 20 13,33 6,67 0
2254 52 45,16 38,71 32,26 25,81 19,35 12,9 6,45 0
2255 54 43,75 37,5 31,25 25 18,75 12,5 6,25 0
2256 56 42,42 36,36 30,3 24,24 18,18 12,12 6,06 0
2257 58 41,18 35,29 29,41 23,53 17,65 11,76 5,88 0
2258 60 40 34,29 28,57 22,86 17,14 11,43 5,71 0
2259 62 38,89 33,33 27,78 22,22 16,67 11,11 5,56 0
2260 64 37,84 32,43 27,03 21,62 16,22 10,81 5,41 0
2261 66 36,84 31,58 26,32 21,05 15,79 10,53 5,26 0
2262 68 35,9 30,77 25,64 20,51 15,38 10,26 5,13 0
2263 70 35 30 25 20 15 10 5 0
2264 72 34,15 29,27 24,39 19,51 14,63 9,76 4,88 0
2265 74 33,33 28,57 23,81 19,05 14,29 9,52 4,76 0
2266 76 32,56 27,91 23,26 18,6 13,95 9,3 4,65 0
2267 78 31,82 27,27 22,73 18,18 13,64 9,09 4,55 0
2268 80 31,11 26,67 22,22 17,78 13,33 8,89 4,44 0
2269 82 30,43 26,09 21,74 17,39 13,04 8,7 4,35 0
2270 84 29,79 25,53 21,28 17,02 12,77 8,51 4,26 0
2271 86 29,17 25 20,83 16,67 12,5 8,33 4,17 0
2272 88 28,57 24,49 20,41 16,33 12,24 8,16 4,08 0
2273 90 28 24 20 16 12 8 4 0
2274 92 27,45 23,53 19,61 15,69 11,76 7,84 3,92 0
2275 94 26,92 23,08 19,23 15,38 11,54 7,69 3,85 0
2276 96 26,42 22,64 18,87 15,09 11,32 7,55 3,77 0
2277 98 25,93 22,22 18,52 14,81 11,11 7,41 3,7 0
2278 100 25,45 21,82 18,18 14,55 10,91 7,27 3,64 0
2279 * @return 1, falls Befrieden erlaubt ist, 0 sonst.
2280 * @param[in] caster Derjenige, der den Spruch ausfuehrt.
2281 * @attention Wenn acify() 1 zurueckliefert, zaehlt dies als
2282 * erfolgreicher Befriedungsversuch und in diesem Lebewesen wurde
2283 * StopHuntingMode(1) aufgerufen.
2284*/
2285public int Pacify(object caster) {
2286
2287 // wenn das Viech keine Gegner hat, dann witzlos. ;-) Ohne Caster gehts auch
2288 // direkt raus.
2289 if (!mappingp(enemies) || !sizeof(enemies)
2290 || !objectp(caster)) {
2291 return 0;
2292 }
2293
2294 // Wenn P_ACCEPT_PEACE gesetzt ist, altes Verhalten wiederspiegeln
2295 // -> der NPC ist einfach immer befriedbar. Gleiches gilt fuer den Caster
2296 // selber, der wird sich ja nicht gegen das eigene Befriede wehren. Und auch
2297 // im team wehrt man sich nicht gegen das Befriede eines Teamkollegen
2298 if (QueryProp(P_ACCEPT_PEACE)==1 || caster==ME
2299 || member(TeamMembers(), caster) > -1) {
2300 StopHuntingMode(1); // Caster/Gilde sollte eigene Meldung ausgeben
2301 return 1;
2302 }
2303
2304 string gilde = caster->QueryProp(P_GUILD) || "ANY";
2305
2306 // ggf. P_PEACE_HISTORY initialisieren
Zesstrac4052902019-12-29 16:45:21 +01002307 <int|mapping>* ph = QueryProp(P_PEACE_HISTORY);
MG Mud User88f12472016-06-24 23:31:02 +02002308 if (!pointerp(ph))
2309 SetProp(P_PEACE_HISTORY, ph=({time(), ([]) }) );
2310
2311 // ggf. die Zaehler reduzieren.
2312 if ( ph[0] + __RESET_TIME__ * 75/100 < time()) {
2313 _decay_peace_history(&ph);
2314 }
2315
2316 float w = (caster->QueryAttribute(A_INT) + 10 - ph[1][gilde] * 4.0) /
2317 (QueryAttribute(A_INT) + 10);
2318 // auf [0,1] begrenzen.
2319 if (w<0) w=0.0;
2320 else if (w>1) w=1.0;
2321 // w * n ist eine Zahl zwischen 0 und n, wenn w * n > random(n)+1,
2322 // darf befriedet werden. Da das Random fuer grosse Zahlen
2323 // besser verteilt ist, nehm ich n = __INT_MAX__ und vernachlaessige
2324 // ausserdem die +1 beim random().
2325 if (ceil(w * __INT_MAX__) > random(__INT_MAX__) ) {
2326 ph[1][gilde]++;
2327 StopHuntingMode(1);
2328 return 1;
2329 }
2330 // ein SetProp(P_PEACE_HISTORY) kann entfallen, da das Mapping direkt
2331 // geaendert wird. Sollte die Prop allerdings mal ne Querymethode haben,
2332 // welche eine Kopie davon liefert, muss das hier geaendert oder die
2333 // Prop per Query() abgefragt werden.
2334 return 0;
2335}
2336