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