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