blob: dd4cf722f11c977a46859ba09226ca267b681ba7 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// npc/combat.c -- NPC-spezifische Kampffunktionen
4//
5// $Id: combat.c 9488 2016-02-19 21:24:04Z Arathorn $
6#pragma strong_types
7#pragma save_types
8#pragma range_check
9#pragma no_clone
10#pragma pedantic
11
12inherit "std/living/combat";
13
14#include <combat.h>
15#include <language.h>
16#include <properties.h>
17#include <wizlevels.h>
18#include <health.h>
19#include <new_skills.h>
20
21#define NEED_PROTOTYPES 1
22#include <living/life.h>
23#undef NEED_PROTOTYPES
24
25#define HB_CHECK 7
26#define ME this_object()
27#define STATMASTER "/p/service/rochus/guildstat/master"
28
29nosave int heartbeat, beatcount;
30
31private void catch_up_hbs();
32
33protected void create() {
34 ::create();
35 beatcount=1;
36 heartbeat=1;
37}
38
39protected void create_super() {
40 set_next_reset(-1);
41}
42
43// aktuelles Lebewesen, fuer das dieser NPC gerade taetig ist. Default:
44// Spieler, bei dem er als Helfer-NPC registriert ist.
45public object QueryUser()
46{
47 mixed helperdata = QueryProp(P_HELPER_NPC);
48 if (pointerp(helperdata) && objectp(helperdata[0]))
49 return helperdata[0];
50 return 0;
51}
52
53// ggf. Feinde expiren. Soll das Problem verringern, dass Spieler nach Tagen
54// erst die Meldung kriegen, dass der NPC sie nicht mehr jagt, wenn der HB
55// reaktivert wird.
56void reset() {
57 // ggf. die abgeschalteten HBs nachholen.
58 if (!heartbeat)
59 catch_up_hbs();
60 // ggf. P_ENEMY_DAMAGE zuruecksetzen
61 ResetEnemyDamage();
62}
63
64static void _set_max_hp(int i) {
65 Set(P_MAX_HP,i);
66 SetProp(P_HP,i);
67}
68
69static void _set_max_sp(int i) {
70 Set(P_MAX_SP,i);
71 SetProp(P_SP,i);
72}
73
74
75// Check-Funktion fuer die P_NO_ATTACK-QueryMethod
76static mixed _check_immortality()
77{
78 int t;
79
80 if ( !(t = Query("time_to_mortality")) || time() > t ){
81 // Zeit ist abgelaufen - wieder angreifbar machen
82 Set( P_NO_ATTACK, 0, F_QUERY_METHOD );
83 heartbeat = 1;
84 beatcount = 1;
85 set_heart_beat(1);
86
87 return 0;
88 }
89
90 // der NPC ist noch unangreifbar
91 return break_string( capitalize(name( WER, 1 )) + " versteckt sich hinter "
92 "einem Fehler im Raum-Zeit-Gefuege und entgeht so "
93 "voruebergehend allen Angriffen.", 78 );
94}
95
96
97// wenn der HeartBeat buggt, wird diese Funktion vom Master aufgerufen
98public void make_immortal()
99{
100 // fuer 5 Minuten unangreifbar machen
101 Set( P_NO_ATTACK, #'_check_immortality, F_QUERY_METHOD );
102
103 Set( "time_to_mortality", time() + 300, F_VALUE );
104
105 // damit die Spieler keinen Vorteil durch den Bug haben, heilen
106 heal_self(10000);
107
108 // da nun der Heartbeat abgeschaltet ist und normalerweise erst
109 // reaktiviert wird, sobald jemand nach 5min P_NO_ATTACK abfragt, muss man
110 // aber auf Viecher achten, die immer nen Heartbeat haben wollen. In dem
111 // fall per call_out selber die Prop abfragen.
112 if (QueryProp(P_HB))
113 call_out(#'QueryProp, 301, P_NO_ATTACK);
114}
115
116
117// Damit NPCs gegeneinander weiterkaempfen, auch wenn kein Spieler
118// in der Naehe ist:
119static int _query_hb()
120{
121 // TODO: return InFight() || Query(P_HB, F_VALUE), sobald InFight()
122 // geaendert.
123 return (InFight() || Query(P_HB,F_VALUE)) ? 1 : 0;
124}
125
126
127#define SPELL_TOTALRATE 0
128#define SPELL_DAMAGE 1
129#define SPELL_TEXT_FOR_ENEMY 2
130#define SPELL_TEXT_FOR_OTHERS 3
131#define SPELL_DAMTYPE 4
132#define SPELL_FUNC 5
133#define SPELL_ARG 6
134
135varargs int AddSpell(int rate, int damage, string TextForEnemy,
Bugfix7d66c1d2016-11-20 17:27:15 +0100136 string TextForOthers, string|string* dam_type,
137 string|closure func, int|mapping spellarg)
138{
MG Mud User88f12472016-06-24 23:31:02 +0200139 mixed *spells;
140 int total_rates;
Bugfix7d66c1d2016-11-20 17:27:15 +0100141 closure cl;
MG Mud User88f12472016-06-24 23:31:02 +0200142
143 if (rate<0 || damage<=0 || !stringp(TextForEnemy) ||
144 !stringp(TextForOthers))
145 return 0;
146
147 if (stringp(dam_type))
148 dam_type = ({dam_type});
149 else if (!pointerp(dam_type))
150 dam_type = ({DT_MAGIC});
151
152 // Tatsaechlich ist es immer ein nicht-physischer Angriff, wenn spellarg ein
153 // int ist, weil wenn spellarg==0 ist der Default nicht-physischer Angriff
154 // und bei spellarg!=0 auch. Nur mit einem mapping kann man einen phys.
155 // Angriff erzeugen.
156 if (intp(spellarg))
157 spellarg = ([SP_PHYSICAL_ATTACK: 0]);
158
Bugfix7d66c1d2016-11-20 17:27:15 +0100159 // Falls func ein String ist eine Closure erstellen und diese speichern.
Bugfix9cba67c2017-01-01 14:55:42 +0100160 if(stringp(func) && sizeof(func))
Bugfix7d66c1d2016-11-20 17:27:15 +0100161 {
162 cl=symbol_function(func,this_object());
Bugfix49681542016-12-30 21:36:08 +0100163 if(!closurep(cl))
Bugfix7d66c1d2016-11-20 17:27:15 +0100164 {
165 catch(raise_error(
166 "AddSpell(): Es konnte keine Closure fuer "+func+" erstellt werden.");
167 publish);
168 }
169 }
170 else
171 {
172 cl=func;
173 }
174
MG Mud User88f12472016-06-24 23:31:02 +0200175 // Falls vorhanden, alte Syntax auf die von replace_personal() anpassen,
176 // die im heart_beat() beim Ausgeben der Meldung verwendet wird.
177 if ( strstr(TextForOthers, "@", 0) != -1 )
178 {
179 // Zeichen nach @WER & Co in runde Klammern einschliessen, damit es als
180 // Sub-Pattern im Ausgabestring wiederholt werden kann. Ansonsten wuerde
181 // es mit ersetzt.
182 TextForOthers = regreplace(TextForOthers, "@WER([^1-9])", "@WER1\\1", 1);
183 TextForOthers = regreplace(TextForOthers, "@WESSEN([^1-9])",
184 "@WESSEN1\\1", 1);
185 TextForOthers = regreplace(TextForOthers, "@WEM([^1-9])", "@WEM1\\1", 1);
186 TextForOthers = regreplace(TextForOthers, "@WEN([^1-9])", "@WEN1\\1", 1);
187 }
188 total_rates=Query("npc:total_rates")+rate;
189 spells=Query(P_SPELLS);
190 if (!pointerp(spells))
191 spells=({});
192 spells+=({({total_rates, damage, TextForEnemy, TextForOthers,
Bugfix7d66c1d2016-11-20 17:27:15 +0100193 dam_type, cl, spellarg})});
MG Mud User88f12472016-06-24 23:31:02 +0200194 Set(P_SPELLS,spells);
195 Set("npc:total_rates",total_rates);
196 return 1;
197}
198
199int AutoAttack(object ob) {
200 mixed m;
201
202 if (!query_once_interactive(ob))
203 return 0;
204 if (mappingp(m=QueryProp(P_AGGRESSIVE))) {
205 mixed *ind,x,z;
206 float f;
207 int i,n;
208
209 ind=m_indices(m)-({0});n=0;f=0.0;
210 for (i=sizeof(ind)-1;i>=0;i--) {
211 x=ind[i];
212 if ((z=m[x][ob->QueryProp(x)]) || (z=m[x][0])) {
213 f=f+(float)z;
214 n++;
215 }
216 }
217 if (n)
218 m=f/((float)n);
219 else
220 m=m[0];
221 }
222 if (((int)(100*(m+ob->QueryProp(P_AGGRESSIVE))))<=random(100))
223 return 0;
224 if (IS_LEARNER(ob)
225 && (ob->QueryProp(P_INVIS)
226 || ob->QueryProp(P_WANTS_TO_LEARN)))
227 return 0;
228 return 1;
229}
230
231void SpellAttack(object enemy) {
232}
233
234#if 0
235TJ(string s) {
236 object o;
237 if (o=find_player("jof"))
238 tell_object(o,sprintf("%O: %s\n",this_object(),s));
239}
240#else
241#define TJ(x)
242#endif
243
244protected void heart_beat() {
245 int r,i;
246 mixed env,*spells, sinfo;
247 object enemy;
248
249 if ( --beatcount < 0 )
250 beatcount = 0;
251
252 if (!beatcount && !Query(P_HB)) {
253 if (!environment()) {
254 set_heart_beat(0);
255 heartbeat = 0;
256 if( clonep(this_object()) ) remove();
257 return;
258 }
259 if (!QueryProp(P_POISON)) {
260 // Spieler anwesend?
261 env = filter(all_inventory(environment()), #'query_once_interactive);
262 if (!sizeof(env)) {
263 // Nein, HBs abschalten.
264 set_heart_beat(0);
265 heartbeat=0;
266 TJ("OFF\n");
267 beatcount=HB_CHECK;
268 Set("npc:beat_off_num",absolute_hb_count());
269 return;
270 }
271 }
272 }
273 ::heart_beat();
274 if (!ME)
275 return;
276 enemy=SelectEnemy();
277 if (QueryProp(P_AGGRESSIVE)
278 && (!enemy || environment()!=environment(enemy))
279 && !beatcount) {
280 beatcount=HB_CHECK;
281 env=filter(all_inventory(environment()),#'AutoAttack);
282 if (!sizeof(env))
283 return;
284 i=random(sizeof(env));
285 Kill(env[i]);
286 }
287 else if (!beatcount)
288 beatcount=HB_CHECK;
289 if (!objectp(enemy) ||QueryProp(P_DISABLE_ATTACK)>0)
290 return;
291 SpellAttack(enemy);
292
293 if (!pointerp(spells=Query(P_SPELLS))
294 || !sizeof(spells)
295 || !objectp(enemy=SelectEnemy())
296 || environment(enemy)!=environment()
297 || (QueryProp(P_DISABLE_ATTACK)>0)
298 || random(100)>Query(P_SPELLRATE))
299 return;
300 r=random(Query("npc:total_rates"));
301 for (i=sizeof(spells)-1;(i>0 && spells[i-1][SPELL_TOTALRATE]>r);i--)
302 ;
303 string akt_spell_mess = spells[i][SPELL_TEXT_FOR_ENEMY];
304 if ( sizeof(akt_spell_mess) )
305 tell_object(enemy, break_string(akt_spell_mess, 78));
306 akt_spell_mess = spells[i][SPELL_TEXT_FOR_OTHERS];
307 // Nur, wenn ueberhaupt eine Meldung gesetzt wurde, muss diese verarbeitet
308 // werden.
309 if (stringp(akt_spell_mess) && sizeof(akt_spell_mess))
310 {
311 akt_spell_mess = replace_personal(akt_spell_mess, ({enemy}), 1);
312 say(break_string(akt_spell_mess, 78),({enemy, this_object()}));
313 }
314 sinfo = deep_copy(spells[i][SPELL_ARG]);
315 if(!mappingp(sinfo))
316 sinfo=([ SI_MAGIC_TYPE :({ MT_ANGRIFF }) ]);
317 else if(!sinfo[SI_MAGIC_TYPE])
318 sinfo[ SI_MAGIC_TYPE]=({ MT_ANGRIFF });
319 if(!sinfo[SP_PHYSICAL_ATTACK] &&
320 (enemy->SpellDefend(this_object(),sinfo) >
321 random(MAX_ABILITY+QueryProp(P_LEVEL)*50))){
322 tell_object(enemy,"Du wehrst den Spruch ab.\n");
323 say(enemy->Name(WER,1)+" wehrt den Spruch ab.\n",
324 ({ enemy, this_object()}));
325 return ;
326 }
327 int damage = random(spells[i][SPELL_DAMAGE])+1;
328 enemy->Defend(damage, spells[i][SPELL_DAMTYPE],
329 spells[i][SPELL_ARG],
330 this_object());
331
332 // Falls der Gegner (oder wir) im Defend stirbt, hier abbrechen
333 if ( !objectp(ME) || !objectp(enemy)
334 || enemy->QueryProp(P_GHOST) ) return;
335
Bugfix7d66c1d2016-11-20 17:27:15 +0100336 closure cl = spells[i][SPELL_FUNC];
337 if (cl)
338 {
339 if (closurep(cl))
340 catch(funcall(cl, enemy, damage, spells[i][SPELL_DAMTYPE]);publish);
341 else
342 raise_error(sprintf("P_SPELL defekt: SPELL_FUNC in Spell %i ist keine "
343 "Closure.\n", i));
344 }
MG Mud User88f12472016-06-24 23:31:02 +0200345}
346
347// Heartbeats nachholen.
348private void catch_up_hbs() {
349 // gibt es HBs zum nachholen?
350 int beat_off_num = Query("npc:beat_off_num");
351 if (!beat_off_num)
352 return; // nein.
353 // wieviele HBs nachholen?
354 beat_off_num = absolute_hb_count() - beat_off_num;
355
356 if (beat_off_num>0) {
357 // Nicht ausgefuehrtes HEILEN nachholen
358 int rlock=QueryProp(P_NO_REGENERATION);
359 int hp=QueryProp(P_HP);
360 int sp=QueryProp(P_SP);
361 int alc=QueryProp(P_ALCOHOL);
362 if (!(rlock & NO_REG_HP)) {
363 hp+=beat_off_num/HEAL_DELAY+alc/ALCOHOL_DELAY;
364 SetProp(P_HP,hp);
365 }
366 if (!(rlock & NO_REG_SP)) {
367 sp+=beat_off_num/HEAL_DELAY+alc/ALCOHOL_DELAY;
368 SetProp(P_SP,sp);
369 }
370 alc-=beat_off_num/ALCOHOL_DELAY;
371 if ( alc < 0 )
372 alc = 0;
373 SetProp(P_ALCOHOL,alc);
374 int da = QueryProp(P_DISABLE_ATTACK);
375 // Paralysen abbauen
376 if ( da > 0 ) {
377 da -= beat_off_num;
378 if ( da < 0 )
379 da = 0;
380 SetProp( P_DISABLE_ATTACK, da );
381 }
382 // Hunttimes aktualisieren, Feinde expiren
383 update_hunt_times(beat_off_num);
384 if (!heartbeat)
385 // HBs immer noch abgeschaltet, naechstes Mal HBs seit jetzt nachholen.
386 Set("npc:beat_off_num",absolute_hb_count());
387 else
388 // HB laeuft wieder, nix mehr nachholen, bis zur naechsten Abschaltung.
389 Set("npc:beat_off_num",0);
390 }
391}
392
393void init() {
394
395 // ggf. Heartbeats nachholen und wieder einschalten.
396 if (!heartbeat) {
397 set_heart_beat(1);
398 heartbeat=1;
399 catch_up_hbs();
400 }
401
402 if (AutoAttack(this_player()))
403 Kill(this_player());
404}
405
406private nosave closure mod_att_stat;
407
408int Defend(int dam, mixed dam_type, mixed spell, object enemy) {
409 if (objectp(enemy=(enemy||this_player()))
410 && query_once_interactive(enemy)
411 && !IS_LEARNER(enemy)) {
412 if (!objectp(get_type_info(mod_att_stat,2))) {
413 object ma;
414 if (!objectp(ma=find_object(STATMASTER)))
415 return ::Defend(dam,dam_type,spell,enemy);
416 // Keine Statistik wenn Master nicht geladen ist.
417 mod_att_stat=symbol_function("ModifyAttackStat",ma);
418 }
419 funcall(mod_att_stat,
420 enemy->QueryProp(P_GUILD),
421 enemy->QueryProp(P_GUILD_LEVEL),
422 dam,
423 dam_type,
424 spell);
425 }
426
427 return ::Defend(dam,dam_type,spell,enemy);
428}