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