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