blob: b34a6485505166e36668973f5e7941a425364873 [file] [log] [blame]
MG Mud User88f12472016-06-24 23:31:02 +02001// MorgenGrauen MUDlib
2//
3// living/life.c -- life variables
4//
5// $Id: life.c 9426 2016-01-03 10:02:57Z Zesstra $
6
7// living object life variables
8//
9// P_ALIGN -- alignment value
10// P_NPC -- if living is an NPC
11// P_HP -- HitPoints
12// P_SP -- SpellPoints
13// P_ALCOHOL -- value of intoxication
14// P_DRINK -- value of soakness
15// P_FOOD -- value of stuffness
16// P_XP -- experience
17// P_POISON -- level of poison
18// P_CORPSE -- corpse-object
19// P_DEAF -- if living is deaf
Vanion50652322020-03-10 21:13:25 +010020#pragma strict_types,save_types,rtt_checks
MG Mud User88f12472016-06-24 23:31:02 +020021#pragma range_check
22#pragma no_clone
MG Mud User88f12472016-06-24 23:31:02 +020023
24#define NEED_PROTOTYPES
25#include <hook.h>
26#include <living/skills.h>
27#include <thing/properties.h>
28#include <living/life.h>
29#include <living/moving.h>
30#include <living/combat.h>
31#include <living/attributes.h>
32#include <thing/description.h>
33#include <thing/language.h>
34#undef NEED_PROTOTYPES
35#include <health.h>
36#include <defines.h>
37#include <new_skills.h>
38#include <scoremaster.h>
39#include <defuel.h>
40#include <properties.h>
41#include <events.h>
42#include <wizlevels.h>
43
44#define ALCOHOL_VALUE(n) n
45
46#include <debug_info.h> //voruebergehend
47
48
49// 'private'-Prototypen
50private void DistributeExp(object enemy, int exp_to_give);
51
52// Variablen
53nosave int delay_alcohol; /* time until next alcohol effect */
54nosave int delay_drink; /* time until next drink effect */
55nosave int delay_food; /* time until next food effect */
56nosave int delay_heal; /* time until next heal effect */
57nosave int delay_sp; /* time until next sp regeneration */
58nosave int delay_poison; /* time until next poison effect */
59nosave int drop_poison;
60nosave mapping enemy_damage;
61nosave mapping hp_buffer;
62nosave mapping sp_buffer;
63nosave int remove_me;
64int nextdefueltimefood;
65int nextdefueltimedrink;
66
67
68protected void create()
69{
70 Set(P_GHOST, SAVE, F_MODE);
71 Set(P_FROG, SAVE, F_MODE);
72 Set(P_ALIGN, SAVE, F_MODE);
73 Set(P_HP, SAVE, F_MODE);
74 Set(P_SP, SAVE, F_MODE);
75 Set(P_XP, SAVE, F_MODE);
76 Set( P_LAST_XP, ({ "", 0 }) );
77 Set( P_LAST_XP, PROTECTED, F_MODE_AS );
78
79 Set(P_ALCOHOL, SAVE, F_MODE);
80 Set(P_DRINK, SAVE, F_MODE);
81 Set(P_FOOD, SAVE, F_MODE);
82 Set(P_POISON, SAVE, F_MODE);
83 Set(P_DEAF, SAVE, F_MODE);
84
85 SetProp(P_FOOD_DELAY, FOOD_DELAY);
86 SetProp(P_DRINK_DELAY, DRINK_DELAY);
87 SetProp(P_ALCOHOL_DELAY, ALCOHOL_DELAY);
88 SetProp(P_HP_DELAY,HEAL_DELAY);
89 SetProp(P_SP_DELAY,HEAL_DELAY);
90 SetProp(P_POISON_DELAY,POISON_DELAY);
91 // default fuer alle Lebewesen (NPC + Spieler):
92 SetProp(P_MAX_POISON, 10);
93 SetProp(P_CORPSE, "/std/corpse");
94
95 nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
96 nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
97
98 enemy_damage=([:2 ]);
99 hp_buffer=([]);
100 sp_buffer=([]);
101
102 SetProp(P_DEFUEL_LIMIT_FOOD,1);
103 SetProp(P_DEFUEL_LIMIT_DRINK,1);
104 SetProp(P_DEFUEL_TIME_FOOD,1);
105 SetProp(P_DEFUEL_TIME_DRINK,1);
106 SetProp(P_DEFUEL_AMOUNT_FOOD,1);
107 SetProp(P_DEFUEL_AMOUNT_DRINK,1);
108
109 offerHook(H_HOOK_DIE,1);
110
111 offerHook(H_HOOK_FOOD,1);
112 offerHook(H_HOOK_DRINK,1);
113 offerHook(H_HOOK_ALCOHOL,1);
114 offerHook(H_HOOK_POISON,1);
115 offerHook(H_HOOK_CONSUME,1);
116}
117
118// Wenn der letzte Kampf lang her ist und das Lebewesen wieder vollgeheilt
119// ist, wird P_ENEMY_DAMAGE zurueckgesetzt.
120protected void ResetEnemyDamage() {
121 if (time() > QueryProp(P_LAST_COMBAT_TIME) + __RESET_TIME__ * 4
122 && QueryProp(P_HP) == QueryProp(P_MAX_HP))
123 enemy_damage=([:2 ]);
124}
125
126private void DistributeExp(object enemy, int exp_to_give) {
127 int total_damage, tmp, ex;
128 mapping present_enemies;
129
130 if ( exp_to_give<=0 )
131 return;
132
133 mapping endmg=deep_copy(enemy_damage);
134
135 // Mitglieder im Team des Killers bekommen:
136 //
137 // Gesamtanteil des Teams
138 // Eigenen Anteil + ----------------------
139 // Anzahl Teammitglieder
140 // ---------------------------------------
141 // 2
142 //
bugfixd94d0932020-04-08 11:27:13 +0200143 object *inv = ({object*})enemy->TeamMembers();
MG Mud User88f12472016-06-24 23:31:02 +0200144 if ( pointerp(inv) )
145 {
146 present_enemies=m_allocate(sizeof(inv), 1);
147 foreach(object ob: inv)
148 {
149 if ( objectp(ob) && (environment(ob)==environment()) )
150 {
151 tmp=endmg[object_name(ob)];
152 total_damage+=tmp;
153 present_enemies[ob] = tmp/2;
154 m_delete(endmg,object_name(ob)); //s.u.
155 }
156 }
157 int mitglieder = sizeof(present_enemies);
158 if ( mitglieder )
159 {
160 tmp=total_damage/(2*mitglieder);
161 foreach(object ob, int punkte: &present_enemies)
162 punkte += tmp;
163 }
164 }
165 else {
166 // ohne Team wird trotzdem ein Mapping gebraucht. Da Groessenveraenderung
167 // rel. teuer sind, kann einfach mal fuer 3 Eintraege Platz reservieren.
168 present_enemies=m_allocate(3, 1);
169 }
170 // Und noch die Lebewesen im Raum ohne Team.
171 foreach(object ob: all_inventory(environment()))
172 {
173 if ( tmp=endmg[object_name(ob)] )
174 {
175 total_damage += tmp;
176 present_enemies[ob] = tmp;
177 m_delete(endmg,object_name(ob)); // Nur einmal pro Leben Punkte :)
178 }
179 }
180 if ( !total_damage )
181 {
bugfixd94d0932020-04-08 11:27:13 +0200182 ({int})enemy->AddExp(exp_to_give);
MG Mud User88f12472016-06-24 23:31:02 +0200183 }
184 else
185 {
186 foreach(object ob, int damage: present_enemies)
187 {
188 if ( !objectp(ob) )
189 continue;
190 if ( query_once_interactive(ob) && ( !interactive(ob)
191 || (query_idle(ob)>600) ) )
192 continue;
193 //exp_to_give*present_enemies[i][1]/total_damage gibt bei viel Schaden
194 //einen numerical overflow. Daher muessen wir hier wohl doch
195 //zwischenzeitlich mit floats rechnen, auch wenn das 0-1 XP Verlust
196 //durch float->int-Konversion gibt. (ceil() lohnt sich IMHO nicht.)
197 ex = (int)(exp_to_give*((float)damage/(float)total_damage));
198 ob->AddExp(ex);
199 }
200 }
201}
202
203/*
204 * This function is called from other players when they want to make
205 * damage to us. But it will only be called indirectly.
206 * We return how much damage we received, which will
207 * change the attackers score. This routine is probably called from
208 * heart_beat() from another player.
209 * Compare this function to reduce_hit_points(dam).
210 */
211public int do_damage(int dam, object enemy)
Zesstra4736ba92021-05-07 09:31:24 +0200212{ int hit_point,al;
MG Mud User88f12472016-06-24 23:31:02 +0200213
214 if ( extern_call()
215 && objectp(enemy)
216 && living(enemy)
217 && !QueryProp(P_ENABLE_IN_ATTACK_OUT))
218 {
bugfixd94d0932020-04-08 11:27:13 +0200219 al=time()-({int})enemy->QueryProp(P_LAST_MOVE);
MG Mud User88f12472016-06-24 23:31:02 +0200220 if (al<3) // Erste Kampfrunde nach Betreten des Raumes?
221 dam/=(4-al); // Gegen Rein-Feuerball-Raus-Taktik
222 }
223
224 if ( QueryProp(P_GHOST) || QueryProp(P_NO_ATTACK) || (dam<=0)
225 || ( objectp(enemy)
bugfixd94d0932020-04-08 11:27:13 +0200226 && ( ({int})enemy->QueryProp(P_GHOST)
227 || ({int|string})enemy->QueryProp(P_NO_ATTACK) ) ) )
MG Mud User88f12472016-06-24 23:31:02 +0200228 return 0;
229
230 hit_point = QueryProp(P_HP)-dam;
231
232 if ( QueryProp(P_XP) && objectp(enemy) )
233 {
234 if ( !QueryProp(P_NO_XP) )
bugfixd94d0932020-04-08 11:27:13 +0200235 ({int})enemy->AddExp(dam*({int})QueryProp(P_TOTAL_WC)/10);
MG Mud User88f12472016-06-24 23:31:02 +0200236 }
237
238 if (living(enemy)) {
239 string enname = object_name(enemy);
240 // Hmpf. Blueprints sind doof. Die Chance ist zwar gering, aber koennte
241 // sein, dass ein Unique-NPC mit zwei verschiedenen Spielern am gleichen
242 // NPC metzelt.
243 // TODO: MHmm. wie gross ist das Risiko wirklich?
244 //if (!clonep(enemy))
245 // enname = enname + "_" + to_string(object_time(enemy));
246 // nur wenn gegner NPC ist und noch nicht drinsteht: Daten aus
247 // P_HELPER_NPC auswerten
248 if (!member(enemy_damage,enemy) && !query_once_interactive(enemy)) {
bugfixd94d0932020-04-08 11:27:13 +0200249 mixed helper = ({mixed})enemy->QueryProp(P_HELPER_NPC);
MG Mud User88f12472016-06-24 23:31:02 +0200250 if (pointerp(helper) && objectp(helper[0]))
251 enemy_damage[enname,1] = helper[0];
252 }
253 enemy_damage[enname,0]+=dam;
254 }
255
256 SetProp(P_HP, hit_point);
257
258 if ( hit_point<0 )
259 {
260 //TODO: Warum nicht das ganze Zeug ins die() verlegen?
261 if ( enemy )
262 {
bugfixd94d0932020-04-08 11:27:13 +0200263 ({int})enemy->StopHuntFor(ME,1);
MG Mud User88f12472016-06-24 23:31:02 +0200264 if ( !QueryProp(P_NO_XP) )
265 DistributeExp(enemy,QueryProp(P_XP)/100);
266 if ( !query_once_interactive(ME) )
267 log_file ("NPC_XP", sprintf(
268 "[%s] %s, XP: %d, HP*WC: %d, Killer: %s\n",
269 dtime(time()), object_name(ME), (QueryProp(P_XP)/100),
270 QueryProp(P_TOTAL_WC)*QueryProp(P_MAX_HP)/10,
bugfixd94d0932020-04-08 11:27:13 +0200271 ({string})enemy->name()||"NoName" ));
272 al = QueryProp(P_ALIGN)/50 + ({int})enemy->QueryProp(P_ALIGN)/200;
MG Mud User88f12472016-06-24 23:31:02 +0200273 if (al>20)
274 al=20;
275 else if(al<-20)
276 al=-20;
bugfixd94d0932020-04-08 11:27:13 +0200277 ({int})enemy->SetProp(P_ALIGN,({int})enemy->QueryProp(P_ALIGN)-al);
MG Mud User88f12472016-06-24 23:31:02 +0200278 }
279 SetProp(P_KILLER, enemy);
280
281 die();
282 }
283 return dam;
284}
285
286
287private void _transfer( object *obs, string|object dest, int flag )
288{ int i;
289
290 i = sizeof(obs);
291
292 // Eine Schleife ist zwar langsamer als filter() o.ae., aber
293 // selbst mit einer noch so schnellen Loesung kann leider nicht
294 // ausgeschlossen werden, dass irgendwo ein too-long-eval-Bug dazwischen
295 // kommt. Dazu sind die Kaempfe mit Gilden-NPCs etc. einfach zu teuer ...
296 // Pruefung auf zerstoerte Objekte, da einige sich evtl. im NotifyPlayerDeath()
297 // zerstoeren.
298 while ( i && get_eval_cost() > 300000 )
bugfixd94d0932020-04-08 11:27:13 +0200299 if ( objectp(obs[--i]) && !({int})obs[i]->QueryProp(P_NEVERDROP) )
MG Mud User88f12472016-06-24 23:31:02 +0200300 // Jetzt wird's noch etwas teurer mit catch() - aber manche Sachen
301 // duerfen einfach nicht buggen
bugfixd94d0932020-04-08 11:27:13 +0200302 catch( ({int})obs[i]->move( dest, flag );publish );
MG Mud User88f12472016-06-24 23:31:02 +0200303
304 if ( i > 0 )
305 // Zuviel Rechenzeit verbraten, es muessen noch Objekte bewegt werden
306 call_out( #'_transfer, 0, obs[0..i-1], dest, flag );
307 else {
308 if ( remove_me )
309 remove();
310 }
311}
312
313
314public varargs void transfer_all_to( string|object dest, int isnpc ) {
315 int flags;
316 object *obs;
317
318 if ( !objectp(ME) )
319 return;
320
321 // Das Flag "isnpc" ist fuer NPCs gedacht. Deren Ausruestung darf nicht
322 // mit M_NOCHECK bewegt werden, da Spieler das bei Nicht-Standard-Leichen
323 // sonst u.U. ausnutzen koennten.
324 if ( isnpc )
325 flags = M_SILENT;
326 else
327 flags = M_SILENT|M_NOCHECK;
328
329 obs = all_inventory(ME) || ({});
330
331 // unnoetig, weil _transfer() auch auf P_NEVERDROP prueft. Zesstra
332 //obs -= filter_objects( obs, "QueryProp", P_NEVERDROP );
333
334 _transfer( obs, dest, flags );
335}
336
337
338protected varargs void create_kill_log_entry(string killer, object enemy) {
339 int level,lost_exp;
340
341 if ( (level=QueryProp(P_LEVEL))<20 || !IS_SEER(ME) )
342 lost_exp = QueryProp(P_XP)/3;
343 else
344 lost_exp = QueryProp(P_XP)/(level-17);
345
346 log_file("KILLS",sprintf("%s %s (%d,%d) %s\n", strftime("%e %b %H:%M"),
347 capitalize(REAL_UID(ME)), level, lost_exp/1000, killer));
348}
349
350// Liefert im Tod (nach dem toetenden do_damage()) das Spielerobjekt, was den
351// Tod wohl zu verantworten hat, falls es ermittelt werden kann. Es werden vor
352// allem registrierte Helfer-NPC und einige Sonderobjekte beruecksichtigt.
353protected object get_killing_player()
354{
355 object killer=QueryProp(P_KILLER);
356 // koennte sein, wenn ausserhalb des Todes gerufen oder eine Vergiftung uns
357 // umgebracht hat.
358 if (!objectp(killer))
359 return 0;
360
361 while (killer && !query_once_interactive(killer))
bugfixd94d0932020-04-08 11:27:13 +0200362 killer = ({object})killer->QueryUser();
MG Mud User88f12472016-06-24 23:31:02 +0200363
364 return killer;
365}
366
367protected object GiveKillScore(object pl, int npcnum)
368{
369 // Stufenpunkt fuer den Kill vergeben.
370 // Falls der Killer den Punkt schon hat, wird
371 // zufaellig ein Mitglied seines Teams ausgewaehlt
372 // und diesem der Punkt gegeben.
373 object *obs,ob;
374 mixed *fr;
375 int i,j,sz;
376
bugfixd94d0932020-04-08 11:27:13 +0200377 if ( pointerp(obs=({object*})pl->TeamMembers()) && (member(obs,pl)>=0) )
MG Mud User88f12472016-06-24 23:31:02 +0200378 {
bugfixd94d0932020-04-08 11:27:13 +0200379 if ( !pointerp(fr=({mixed})pl->PresentTeamRows())
MG Mud User88f12472016-06-24 23:31:02 +0200380 || !sizeof(fr)
381 || !pointerp(fr=fr[0])) // Erste Reihe des Teams
382 fr=({});
383 fr-=({pl,0});
384 obs-=({pl,0});
385 obs-=fr;
386 i=sz=sizeof(obs); // restliche Teammitglieder in zufaelliger Reihenfolge:
387 for ( --i ; i>=0 ; i-- )
388 {
389 j=random(sz);
390 ob=obs[j];
391 obs[j]=obs[0];
392 obs[0]=ob;
393 }
394 i=sz=sizeof(fr); // Erste Reihe in zufaelliger Reihenfolge:
395 for ( --i ; i>=0 ; i-- )
396 {
397 j=random(sz);
398 ob=fr[j];
399 fr[j]=fr[0];
400 fr[0]=ob;
401 }
402
403 obs+=fr; // Erste Reihe wird vor Rest getestet
404 obs+=({pl}); // Killer wird als erstes getestet
405 }
406 else
407 {
408 obs=({pl});
409 }
410 for ( i=sizeof(obs)-1 ; i>=0 ; i-- )
411 if ( objectp(ob=obs[i] )
412 && interactive(ob) // Nur netztot dabei stehen gilt nicht :)
413 && query_idle(ob)<600 // gegen Leute die sich nur mitschleppen lassen
414 && environment(ob)==environment(pl) // Nur anwesende Teammitglieder
415 && !IS_LEARNER(ob)
bugfixd94d0932020-04-08 11:27:13 +0200416// && !({int|string})ob->QueryProp(P_TESTPLAYER)
417 && !(({int})SCOREMASTER->HasKill(ob,ME)) )
418 return ({int})SCOREMASTER->GiveKill(ob,npcnum),ob;
MG Mud User88f12472016-06-24 23:31:02 +0200419
bugfixd94d0932020-04-08 11:27:13 +0200420 return ({int})SCOREMASTER->GiveKill(pl,npcnum),pl;
MG Mud User88f12472016-06-24 23:31:02 +0200421}
422
423// zum ueberschreiben in Spielern
424public int death_suffering() {
425 return 0; // NPC haben keine Todesfolgen
426}
427
428// kein 2. Leben fuer Nicht-Spieler. ;-)
429varargs protected int second_life( object corpse ) {
430 return 0;
431}
432
433public varargs void die( int poisondeath, int extern )
434{ object corpse;
435 string die_msg, tmp;
436 mixed res;
437 mixed hookData;
438 mixed hookRes;
439
440 if ( !objectp(this_object()) || QueryProp(P_GHOST) )
441 return; // Ghosts dont die ...
442
443 // direkt von extern aufgerufen und nicht ueber heart_beat() oder
444 // do_damage() hierher gelangt?
445 if (extern_call() && previous_object() != this_object()) {
446 extern=1;
447 SetProp(P_KILLER, previous_object());
448 }
449
450 if ( res = QueryProp(P_TMP_DIE_HOOK) ){
451 if ( pointerp(res) && sizeof(res)>=3
452 && intp(res[0]) && time()<res[0]
453 && objectp(res[1]) && stringp(res[2]) )
454 {
bugfixd94d0932020-04-08 11:27:13 +0200455 if ( res = ({mixed})call_other( res[1], res[2], poisondeath ) ) {
MG Mud User88f12472016-06-24 23:31:02 +0200456 SetProp(P_KILLER,0);
457 return;
458 }
459 }
460 else
461 SetProp(P_TMP_DIE_HOOK,0);
462 }
463
464 // trigger die hook
465 hookData=poisondeath;
466 hookRes=HookFlow(H_HOOK_DIE,hookData);
467 if (pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
468 if(hookRes[H_RETCODE]==H_CANCELLED) {
469 SetProp(P_KILLER,0);
470 return;
471 }
472 else if (hookRes[H_RETCODE]==H_ALTERED)
473 poisondeath = hookRes[H_RETDATA];
474 }
475
476 if ( IS_LEARNING(ME) && query_once_interactive(ME) ){
477 tell_object( ME, "Sei froh dass Du unsterblich bist, sonst waere es "
478 "eben Dein Ende gewesen.\n");
479 SetProp(P_KILLER,0);
480 return;
481 }
482
483 // Gegner befrieden.
484 map_objects( QueryEnemies()[0], "StopHuntFor", ME, 1 );
485 StopHuntingMode(1);
486
487 // Falls die() direkt aufgerufen wurde und dies ein Spieler ist, muss das
488 // die() noch Eintraege in /log/KILLS via create_kill_log_entry bzw. in
489 // /log/KILLER erstellen.
490 if ( query_once_interactive(ME) && extern )
491 {
492 object killer = QueryProp(P_KILLER)
493 || previous_object() || this_interactive() || this_player();
494 if ( killer && !query_once_interactive(killer) )
495 {
496 tmp = explode( object_name(killer), "#")[0] + " (direkt !)";
497
498 create_kill_log_entry( tmp + " (" + REAL_UID(killer) + ")", killer );
499 }
500 else if ( killer && !QueryProp(P_TESTPLAYER) && !IS_LEARNER(ME) )
501 {
502 log_file( "KILLER", sprintf( "%s %s (%d/%d) toetete %s (%d/%d)\n",
503 ctime(time()),
504 capitalize(getuid(killer)),
505 query_wiz_level(killer),
bugfixd94d0932020-04-08 11:27:13 +0200506 ({int})killer->QueryProp(P_LEVEL),
MG Mud User88f12472016-06-24 23:31:02 +0200507 capitalize(getuid(ME)),
508 query_wiz_level(ME),
509 QueryProp(P_LEVEL) ) );
510
bugfixd94d0932020-04-08 11:27:13 +0200511 ({int})killer->SetProp( P_KILLS, -1 );
MG Mud User88f12472016-06-24 23:31:02 +0200512 }
513 }
514
515 // Bei NPC EKs vergeben und ggf. in der Gilde des Killers und im Raum
516 // NPC_Killed_By() rufen.
517 if ( !query_once_interactive(ME) )
518 {
Vanion50652322020-03-10 21:13:25 +0100519 object killer = (({object}) QueryProp(P_KILLER)) || previous_object() ||
MG Mud User88f12472016-06-24 23:31:02 +0200520 this_interactive() || this_player();
521
522 if ( killer && query_once_interactive(killer) )
523 {
bugfixd94d0932020-04-08 11:27:13 +0200524 if (stringp(res=({string})killer->QueryProp(P_GUILD))
MG Mud User88f12472016-06-24 23:31:02 +0200525 && objectp(res=find_object("/gilden/"+res)))
bugfixd94d0932020-04-08 11:27:13 +0200526 ({void})res->NPC_Killed_By(killer);
MG Mud User88f12472016-06-24 23:31:02 +0200527
528 if (environment())
bugfixd94d0932020-04-08 11:27:13 +0200529 ({void})environment()->NPC_Killed_By(killer);
MG Mud User88f12472016-06-24 23:31:02 +0200530
531 res = QueryProp(P_XP);
532 res = (res < SCORE_LOW_MARK) ? 0 : ((res > SCORE_HIGH_MARK) ? 2 : 1);
533 if ( !QueryProp(P_NO_SCORE) && !IS_LEARNER(killer) &&
534 // !killer->QueryProp(P_TESTPLAYER) &&
bugfixd94d0932020-04-08 11:27:13 +0200535 pointerp( res = ({mixed})SCOREMASTER->QueryNPC(res)) )
MG Mud User88f12472016-06-24 23:31:02 +0200536 GiveKillScore( killer, res[0] );
537 }
538 }
539
540 if( !(die_msg = QueryProp(P_DIE_MSG)) )
541 if (QueryProp(P_PLURAL))
542 die_msg = " fallen tot zu Boden.\n";
543 else
544 die_msg = " faellt tot zu Boden.\n";
545
546 if ( poisondeath )
547 {
548 Set( P_LAST_DAMTYPES, ({ DT_POISON }) );
549 Set( P_LAST_DAMTIME, time() );
550 Set( P_LAST_DAMAGE, 1 );
551 die_msg = " wird von Gift hinweggerafft und kippt um.\n";
552 }
553
554 say( capitalize(name(WER,1)) + die_msg );
555
556 // Wenn keine Leiche, dann Kram ins Env legen.
557 if ( QueryProp(P_NOCORPSE) || !(tmp = QueryProp(P_CORPSE))
558 || catch(corpse = clone_object(tmp);publish)
559 || !objectp(corpse) )
560 {
561 // Magier oder Testspieler behalten ihre Ausruestung.
562 // Sonst kaemen u.U. Spieler an Magiertools etc. heran
563 if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
564 (!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
565 transfer_all_to( environment(), 0 );
566 else
567 // Aber sie ziehen sich aus.
568 filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
569 }
570 else
571 // sonst in die Leiche legen.
572 {
bugfixd94d0932020-04-08 11:27:13 +0200573 ({void})corpse->Identify(ME);
574 ({int})corpse->move( environment(), M_NOCHECK|M_SILENT );
MG Mud User88f12472016-06-24 23:31:02 +0200575 // Magier oder Testspieler behalten ihre Ausruestung.
576 // Sonst kaemen u.U. Spieler an Magiertools etc. heran
577 if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
578 (!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
579 transfer_all_to( corpse, !query_once_interactive(ME) );
580 else
581 // Aber sie ziehen sich aus.
582 filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
583 }
584
585 if ( query_once_interactive(ME) ) {
586 Set( P_DEADS, Query(P_DEADS) + 1 );
587 // Spieler-Tod-event ausloesen
bugfixd94d0932020-04-08 11:27:13 +0200588 ({int})EVENTD->TriggerEvent(EVT_LIB_PLAYER_DEATH, ([
MG Mud User88f12472016-06-24 23:31:02 +0200589 E_OBJECT: ME, E_PLNAME: getuid(ME),
590 E_ENVIRONMENT: environment(), E_TIME: time(),
591 P_KILLER: QueryProp(P_KILLER),
592 P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
593 P_LAST_DAMTYPES: copy(QueryProp(P_LAST_DAMTYPES)),
594 E_EXTERNAL_DEATH: extern,
595 E_POISON_DEATH: poisondeath,
596 E_CORPSE: (objectp(corpse)?corpse:0) ]) );
597 }
598 else {
599 // NPC-Todes-Event ausloesen. Div. Mappings/Arrays werden nicht kopiert,
600 // weil der NPC ja jetzt eh zerstoert wird.
601 mapping data = ([
602 E_OBNAME: object_name(ME),
603 E_ENVIRONMENT: environment(), E_TIME: time(),
604 P_NAME: QueryProp(P_NAME),
605 P_KILLER: QueryProp(P_KILLER),
606 P_ENEMY_DAMAGE: QueryProp(P_ENEMY_DAMAGE),
607 P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
608 P_LAST_DAMTYPES: QueryProp(P_LAST_DAMTYPES),
609 E_EXTERNAL_DEATH: extern,
610 E_POISON_DEATH: poisondeath,
611 E_CORPSE: (objectp(corpse)?corpse:0),
612 P_XP: QueryProp(P_XP),
613 P_ATTRIBUTES: QueryProp(P_ATTRIBUTES),
614 P_MAX_HP: QueryProp(P_MAX_HP),
615 P_HANDS: QueryProp(P_HANDS),
616 P_ALIGN: QueryProp(P_ALIGN),
617 P_RACE: QueryProp(P_RACE),
618 P_CLASS: QueryProp(P_CLASS),
619 ]);
bugfixd94d0932020-04-08 11:27:13 +0200620 ({int})EVENTD->TriggerEvent(EVT_LIB_NPC_DEATH(""), data);
621 ({int})EVENTD->TriggerEvent(
MG Mud User88f12472016-06-24 23:31:02 +0200622 EVT_LIB_NPC_DEATH(load_name(ME)), data);
623 }
624
625 // transfer_all_to() ist evtl. (wenn zuviele Objekte bewegt werden mussten)
626 // noch nicht ganz fertig und wird per call_out() den Rest erledigen.
627 // Sollte die Leiche dann nicht mehr existieren, verbleiben die restlichen
628 // Objekte im Spieler.
629 // Es bleiben aber auf jeden Fall noch rund 300k Eval-Ticks ueber, damit
630 // kein Spieler dank "evalcost too high" ungeschoren davon kommt.
631 if ( !(second_life(corpse)) )
632 {
633 Set( P_GHOST, 1 ); // Fuer korrekte Ausgabe auf Teamkanal.
634
635 if ( find_call_out(#'_transfer) == -1 )
636 // Falls kein call_out() mehr laeuft, sofort destructen ...
637 remove();
638 else
639 // ... ansonsten vormerken
640 remove_me = 1;
641 }
Zesstraa9ba1b82021-02-13 20:00:55 +0100642 else
643 {
644 // In Spielern den Reg-Buffer fuer HP/SP loeschen
645 hp_buffer=([]);
646 sp_buffer=([]);
647 }
MG Mud User88f12472016-06-24 23:31:02 +0200648}
649
650public void heal_self(int h)
651{
652 if ( h<=0 )
653 return;
654 SetProp(P_HP, QueryProp(P_HP)+h);
655 SetProp(P_SP, QueryProp(P_SP)+h);
656}
657
658
659//--------------------------------------------------------------------------
660//
661// int defuel_food( /* void */ )
662//
663// Enttankt den Spieler um einen gewissen Essens-Wert.
664// Sollte nur von Toiletten aufgerufen werden.
665//
666//--------------------------------------------------------------------------
667public int defuel_food()
668{
669 int food;
670
671 food=QueryProp(P_FOOD);
672
673// wenn spieler kein food hat: return 0
674 if ( !food )
675 return NO_DEFUEL;
676
677// wenn spieler unter enttank-grenze: return -1
678 if ( food < QueryProp(P_DEFUEL_LIMIT_FOOD) )
679 return DEFUEL_TOO_LOW;
680
681// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
682 if ( time() < nextdefueltimefood )
683 return DEFUEL_TOO_SOON;
684
685 food=to_int(((food*QueryProp(P_DEFUEL_AMOUNT_FOOD))/2));
686 food+=random(food);
687
688// sicherheitshalber
689 if ( food > QueryProp(P_FOOD) )
690 food=QueryProp(P_FOOD);
691
692 SetProp(P_FOOD,(QueryProp(P_FOOD)-food));
693
694 nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
695
696 return food;
697}
698
699
700//--------------------------------------------------------------------------
701//
702// int defuel_drink( /* void */ )
703//
704// Enttankt den Spieler um einen gewissen Fluessigkeits-Wert.
705// Gleichzeitig wird eine gewisse Menge Alkohol reduziert.
706// Sollte nur von Toiletten aufgerufen werden.
707//
708//--------------------------------------------------------------------------
709public int defuel_drink()
710{
711 int alc, drink;
712
713 drink=QueryProp(P_DRINK);
714
715// wenn spieler kein drink hat: return 0
716 if ( !drink )
717 return NO_DEFUEL;
718
719// wenn spieler unter enttank-grenze: return -1
720 if ( drink < QueryProp(P_DEFUEL_LIMIT_DRINK) )
721 return DEFUEL_TOO_LOW;
722
723// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
724 if ( time() < nextdefueltimedrink )
725 return DEFUEL_TOO_SOON;
726
727 drink=to_int(((drink*QueryProp(P_DEFUEL_AMOUNT_DRINK))/2));
728 drink+=random(drink);
729
730// sicherheitshalber
731 if ( drink > QueryProp(P_DRINK) )
732 drink=QueryProp(P_DRINK);
733
734 SetProp(P_DRINK,(QueryProp(P_DRINK)-drink));
735
736// jedes fluessige Enttanken macht auch etwas nuechterner :^)
737// bei sehr kleinen Mengen enttankt man keinen Alkohol
738// ansonsten in Abhaengigkeit von enttankter Menge, P_ALCOHOL und P_WEIGHT
739
740 if ( drink > 9 && QueryProp(P_ALCOHOL) > 0 )
741 {
742 alc=(to_int(exp(log(1.1)*(drink)))*
743 to_int(exp(log(0.67)*(QueryProp(P_ALCOHOL)))))/
744 (QueryProp(P_MAX_DRINK)*QueryProp(P_MAX_ALCOHOL))*
745 (to_int(QueryProp(P_WEIGHT)/1000));
746
747 SetProp(P_ALCOHOL,QueryProp(P_ALCOHOL)-(alc+random(alc)));
748 }
749
750 nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
751
752 return drink;
753}
754
755
756public void reduce_spell_points(int h)
757{
758 SetProp(P_SP, QueryProp(P_SP)-h);
759}
760
761public void restore_spell_points(int h)
762{
763 SetProp(P_SP, QueryProp(P_SP)+h);
764}
765
766/* Reduce hitpoints. Log who is doing it. */
767public int reduce_hit_points(int dam)
Zesstra4736ba92021-05-07 09:31:24 +0200768{
MG Mud User88f12472016-06-24 23:31:02 +0200769 int i;
770
771#ifdef LOG_REDUCE_HP
772 if (this_player()!=ME)
773 {
774 log_file("REDUCE_HP", name()+" by ");
775 if(!this_player()) log_file("REDUCE_HP","?\n");
776 else {
bugfixd94d0932020-04-08 11:27:13 +0200777 log_file("REDUCE_HP",({string})this_player()->name());
MG Mud User88f12472016-06-24 23:31:02 +0200778 o=previous_object();
779 if (o)
780 log_file("REDUCE_HP", " " + object_name(o) + ", " +
bugfixd94d0932020-04-08 11:27:13 +0200781 ({string})o->name(WER,0) + " (" + creator(o) + ")\n");
MG Mud User88f12472016-06-24 23:31:02 +0200782 else
783 log_file("REDUCE_HP", " ??\n");
784 }
785 }
786#endif
787 if ((i=QueryProp(P_HP)) <= dam)
788 return SetProp(P_HP,1);
789 return SetProp(P_HP, i - dam);
790}
791
792public int restore_hit_points(int heal)
793{
794 return reduce_hit_points(-heal);
795}
796
797public varargs int drink_alcohol(int strength,int testonly, string mytext)
798{ int alc,add,res;
799
800 add=ALCOHOL_VALUE(strength);
801 res=UseSkill(SK_BOOZE,([
802 SI_SKILLARG : add,
803 SI_TESTFLAG : 1])); // Kann der Spieler gut saufen?
804 if (intp(res) && res>0) add=res;
805 alc=QueryProp(P_ALCOHOL)+add;
806 if ((alc >= QueryProp(P_MAX_ALCOHOL)) && !IS_LEARNING(this_object())){
807 if(!testonly)
808 tell_object(ME,mytext||"So ein Pech, Du hast alles verschuettet.\n");
809 return 0;
810 }
811 if(testonly)return 1;
812 UseSkill(SK_BOOZE,([ SI_SKILLARG : ALCOHOL_VALUE(strength) ]));
813 if(alc < 0) alc = 0;
814 if(!alc) tell_object(ME, "Du bist stocknuechtern.\n");
815 SetProp(P_ALCOHOL, alc);
816 return 1;
817}
818
819public varargs int drink_soft(int strength, int testonly, string mytext)
820{ int soaked;
821
822 soaked = QueryProp(P_DRINK);
823 if((soaked + strength > QueryProp(P_MAX_DRINK)) &&
824 !IS_LEARNING(this_object())){
825 if(!testonly)
826 tell_object(ME, mytext||
827 "Nee, so viel kannst Du momentan echt nicht trinken.\n" );
828 return 0;
829 }
830 if(testonly)return 1;
831 if((soaked += DRINK_VALUE(strength)) < 0) soaked = 0;
832 if(!soaked) tell_object(ME, "Dir klebt die Zunge am Gaumen.\n");
833 SetProp(P_DRINK, soaked);
834 return 1;
835}
836
837public varargs int eat_food(int strength, int testonly, string mytext)
838{ int stuffed;
839
840 stuffed = QueryProp(P_FOOD);
841 if ((stuffed + strength > QueryProp(P_MAX_FOOD)) &&
842 !IS_LEARNING(this_object()))
843 {
844 if(!testonly)
845 tell_object(ME,
846 mytext || "Das ist viel zu viel fuer Dich! Wie waers mit etwas "
847 "leichterem?\n");
848 return 0;
849 }
850 if(testonly)return 1;
851 stuffed += FOOD_VALUE(strength);
852 if(stuffed < 0) stuffed = 0;
853 if(!stuffed) tell_object(ME, "Was rumpelt denn da in Deinem Bauch?\n");
854 SetProp(P_FOOD, stuffed);
855 return 1;
856}
857
858public int buffer_hp(int val,int rate)
859{
860 int dif;
861
862 if(val<=0 || rate<=0)return 0;
863 if(val < rate)rate = val;
864 if(rate>20) rate=20;
865
866 /* Check for BufferOverflow */
867 if((dif=(hp_buffer[0]+val)-QueryProp(P_MAX_HP)) > 0)val-=dif;
868 if(val<=0)return 0;
869
870 hp_buffer[0] += val;
871 hp_buffer[1+rate] += val;
872 if(rate > hp_buffer[1])hp_buffer[1] = rate;
873
874 return hp_buffer[0];
875}
876
877public int buffer_sp(int val,int rate)
878{
879 int dif;
880
881 if(val<=0 || rate<=0)return 0;
882 if(val < rate)rate = val;
883 if(rate>20) rate=20;
884
885 /* Check for BufferOverflow */
886 if((dif=(sp_buffer[0]+val)-QueryProp(P_MAX_SP)) > 0)val-=dif;
887 if(val<=0)return 0;
888
889 sp_buffer[0] += val;
890 sp_buffer[1+rate] += val;
891 if(rate > sp_buffer[1])sp_buffer[1] = rate;
892
893 return sp_buffer[0];
894}
895
896protected void update_buffers()
897{ int i, rate, max;
898
899 rate=0;
900 max=0;
901 for(i=1;i<=20;i++){
902 if(member(hp_buffer, i+1))
903 if(hp_buffer[i+1]<=0)
904 hp_buffer = m_delete(hp_buffer,i+1);
905 else{
906 max+=hp_buffer[i+1];
907 rate=i;
908 }
909 }
910
911 hp_buffer[0]=max;
912 hp_buffer[1]=rate;
913 rate=0;
914 max=0;
915 for(i=1;i<=20;i++){
916 if(member(sp_buffer, i+1))
917 if(sp_buffer[i+1]<=0)
918 sp_buffer = m_delete(sp_buffer,i+1);
919 else{
920 max+=sp_buffer[i+1];
921 rate=i;
922 }
923 }
924 sp_buffer[0]=max;
925 sp_buffer[1]=rate;
926}
927
928public int check_timed_key(string key) {
929
930 // keine 0 als key (Typ wird per RTTC geprueft)
931 if (!key)
932 return 0;
933 mapping tmap=Query(P_TIMING_MAP, F_VALUE);
934 if (!mappingp(tmap)) {
935 tmap=([]);
936 Set(P_TIMING_MAP, tmap, F_VALUE);
937 }
938 // Wenn key noch nicht abgelaufen, Ablaufzeitpunkt zurueckgeben.
939 // Sonst 0 (key frei)
940 return (time() < tmap[key]) && tmap[key];
941}
942
943public int check_and_update_timed_key(int duration,string key) {
944
945 // wenn key noch gesperrt, die zeit der naechsten Verfuegbarkeit
946 // zurueckgeben.
947 int res = check_timed_key(key);
948 if (res) {
949 return res;
950 }
951
952 // duration <= 0 ist unsinnig. Aber key ist nicht mehr gesperrt, d.h. time()
953 // ist ein sinnvoller Rueckgabewert.
954 if (duration <= 0)
955 return time();
956
957 mapping tmap = Query(P_TIMING_MAP,F_VALUE);
958 tmap[key]=time()+duration;
959
960 // speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
961 // keine Kopie.
962 //SetProp(P_TIMING_MAP, tmap);
963
964 return -1; // Erfolg.
965}
966
967protected void expire_timing_map() {
968
969 mapping tmap = Query(P_TIMING_MAP, F_VALUE);
970 if (!mappingp(tmap) || !sizeof(tmap))
971 return;
972 foreach(string key, int endtime: tmap) {
973 if (endtime < time())
974 m_delete(tmap, key);
975 }
976 // speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
977 // keine Kopie.
978}
979
980protected void heart_beat()
981{
982 if ( !this_object() )
983 return;
984
985 attribute_hb();
986
987 // Als Geist leidet man nicht unter so weltlichen Dingen wie
988 // Alkohol, Gift&Co ...
989 if ( QueryProp(P_GHOST) )
990 return;
991
992 int hpoison = QueryProp(P_POISON);
993 int rlock = QueryProp(P_NO_REGENERATION);
994 int hp = QueryProp(P_HP);
995 int sp = QueryProp(P_SP);
996 int alc;
997
998 // Wenn Alkohol getrunken: Alkoholauswirkungen?
999 if ( (alc = QueryProp(P_ALCOHOL)) && !random(40) ){
1000 int n;
1001 string gilde;
1002 object ob;
1003
1004 n = random( 5 * (alc - 1)/QueryProp(P_MAX_ALCOHOL) );
1005
1006 switch (n){
1007 case ALC_EFFECT_HICK:
1008 say( capitalize(name( WER, 1 )) + " sagt: <Hick>!\n" );
1009 write( "<Hick>! Oh, Tschuldigung.\n" );
1010 break;
1011
1012 case ALC_EFFECT_STUMBLE:
1013 say( capitalize(name( WER, 1 )) + " stolpert ueber " +
1014 QueryPossPronoun( FEMALE, WEN ) + " Fuesse.\n" );
1015 write( "Du stolperst.\n" );
1016 break;
1017
1018 case ALC_EFFECT_LOOKDRUNK:
1019 say( capitalize(name( WER, 1 )) + " sieht betrunken aus.\n" );
1020 write( "Du fuehlst Dich benommen.\n" );
1021 break;
1022
1023 case ALC_EFFECT_RUELPS:
1024 say( capitalize(name( WER, 1 )) + " ruelpst.\n" );
1025 write( "Du ruelpst.\n" );
1026 break;
1027 }
1028
1029 // Gilde und Environment informieren ueber Alkoholauswirkung.
1030 if ( stringp(gilde = QueryProp(P_GUILD))
1031 && objectp(ob = find_object( "/gilden/" + gilde )) )
bugfixd94d0932020-04-08 11:27:13 +02001032 ({void})ob->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_GUILD );
MG Mud User88f12472016-06-24 23:31:02 +02001033
1034 if ( environment() )
bugfixd94d0932020-04-08 11:27:13 +02001035 ({void})environment()->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_ENV );
MG Mud User88f12472016-06-24 23:31:02 +02001036 }
1037
1038 // Alkohol abbauen und etwas extra heilen, falls erlaubt.
1039 if ( alc && (--delay_alcohol < 0)
1040 && !(rlock & NO_REG_ALCOHOL) ){
1041
1042 SetProp( P_ALCOHOL, alc - 1 );
1043
1044 if ( !hpoison ){
1045 hp++;
1046 sp++;
1047 }
1048
1049 delay_alcohol = QueryProp(P_ALCOHOL_DELAY);
1050 }
1051
1052 // P_DRINK reduzieren, falls erlaubt.
1053 if ( (--delay_drink < 0) && !(rlock & NO_REG_DRINK) ){
1054 delay_drink = QueryProp(P_DRINK_DELAY);
1055 SetProp( P_DRINK, QueryProp(P_DRINK) - 1 );
1056 }
1057
1058 // P_FOOD reduzieren, falls erlaubt.
1059 if ( (--delay_food < 0) && !(rlock & NO_REG_FOOD) ){
1060 delay_food = QueryProp(P_FOOD_DELAY);
1061 SetProp( P_FOOD, QueryProp(P_FOOD) - 1 );
1062 }
1063
1064 // Regeneration aus dem HP-Puffer
1065 // Hierbei wird zwar nur geheilt, wenn das erlaubt ist, aber der Puffer
1066 // muss trotzdem abgearbeitet/reduziert werden, da Delfen sonst eine
1067 // mobile Tanke kriegen. (Keine Heilung im Hellen, Puffer bleibt sonst
1068 // konstant und kann bei Bedarf ueber dunkelmachende Items abgerufen
1069 // werden.)
1070 int val;
1071 if (hp_buffer[0]) {
1072 int rate = hp_buffer[1];
1073 val = hp_buffer[rate + 1];
1074
1075 if ( val > rate )
1076 val = rate;
1077 hp_buffer[0] -= val;
1078 hp_buffer[rate + 1] -= val;
1079 if ( hp_buffer[rate + 1] <= 0 )
1080 update_buffers();
1081 }
1082 // Jetzt Regeneration aus dem Puffer durchfuehren, aber nur wenn erlaubt.
1083 if ( val && !(rlock & NO_REG_BUFFER_HP) )
1084 hp += val;
1085 // normales Heilen, falls keine Regeneration aus dem Puffer erfolgte und
1086 // es erlaubt ist.
1087 else if ( (--delay_heal < 0) && !(rlock & NO_REG_HP) ){
1088 delay_heal = QueryProp(P_HP_DELAY);
1089 if ( !hpoison )
1090 hp++;
1091 }
1092
1093 // Gleiches Spiel jetzt fuer den SP-Puffer (s.o.)
1094 val=0;
1095 if ( sp_buffer[0] ) {
1096 int rate = sp_buffer[1];
1097 val = sp_buffer[rate + 1];
1098
1099 if ( val > rate )
1100 val = rate;
1101
1102 sp_buffer[0] -= val;
1103 sp_buffer[rate + 1] -= val;
1104
1105 if ( sp_buffer[rate + 1] <= 0 )
1106 update_buffers();
1107 }
1108 // Regeneration erlaubt?
1109 if ( val && !(rlock & NO_REG_BUFFER_SP) )
1110 sp += val;
1111 // Wenn nicht, normales Hochideln versuchen.
1112 else if ( (--delay_sp < 0) && !(rlock & NO_REG_SP) ){
1113 delay_sp = QueryProp(P_SP_DELAY);
1114 if ( !hpoison )
1115 sp++;
1116 }
1117
1118 if ( hpoison && (interactive(ME) || !query_once_interactive(ME)) ){
1119 // Vanion, 26.10.03
1120 // Wenn _set_poison() per SET_METHOD ueberschrieben wird, kann
1121 // nicht sichergestellt werden, dass poison immer groesser 0 ist
1122 // Daher muss hier ein Test rein, so teuer das auch ist :(
1123 if (--hpoison < 0)
1124 hpoison=0;
1125
1126 if ( --delay_poison < 0 ){
1127 delay_poison = QueryProp(P_POISON_DELAY)
1128 + random(POISON_MERCY_DELAY);
1129 hp -= hpoison;
1130
1131 if ( hp < 0 ){
1132 tell_object( ME, "Oh weh - das Gift war zuviel fuer Dich!\n"
1133 + "Du stirbst.\n" );
1134
1135 if ( query_once_interactive(ME) ){
1136 create_kill_log_entry( "Vergiftung", 0 );
1137
1138 // Beim Gifttod gibt es keinen Killer. Aber auf diese Art
1139 // erkennt der Todesraum die Ursache korrekt und gibt die
1140 // richtige Meldung aus.
1141 SetProp( P_KILLER, "gift" );
1142 }
1143
1144 die(1);
1145 return;
1146 }
1147
1148 if ( (hpoison < 3 || !query_once_interactive(ME) )
1149 && --drop_poison < 0)
1150 {
1151 // Giftlevel eins reduzieren. hpoison wurde oben schon
1152 // reduziert, d.h. einfach hpoison in P_POISON schreiben.
1153 // dabei wird dann auch ggf. drop_poison richtig gesetzt.
1154 SetProp( P_POISON, hpoison );
1155 if ( !hpoison )
1156 tell_object( ME, "Du scheinst die Vergiftung "
1157 "ueberwunden zu haben.\n" );
1158 }
1159 }
1160
1161 if ( hpoison && !random(15) )
1162 switch ( hp*100/QueryProp(P_MAX_HP) ){
1163 case 71..100 :
1164 write( "Du fuehlst Dich nicht gut.\n" );
1165 say( capitalize(name(WER)) +
1166 " sieht etwas benommen aus.\n" );
1167 break;
1168
1169 case 46..70 :
1170 write( "Dir ist schwindlig und Dein Magen revoltiert.\n" );
1171 say( capitalize(name(WER)) + " taumelt ein wenig.\n" );
1172 break;
1173
1174 case 26..45 :
1175 write( "Dir ist heiss. Du fuehlst Dich schwach. Kopfweh "
1176 "hast Du auch.\n" );
1177 say( capitalize(name(WER)) + " glueht direkt und scheint "
1178 "grosse Schwierigkeiten zu haben.\n" );
1179 break;
1180
1181 case 11..25 :
1182 write( "Du fuehlst Dich beschissen. Alles tut weh, und Du "
1183 "siehst nur noch unscharf.\n" );
1184 say( capitalize(name(WER)) + " taumelt und stoehnt und "
Arathornd7ddef72018-08-29 22:21:30 +02001185 "kann gerade noch vermeiden hinzufallen.\n" );
MG Mud User88f12472016-06-24 23:31:02 +02001186 break;
1187
1188 case 0..10 :
1189 write( break_string( "Du siehst fast nichts mehr und kannst "
1190 "Dich nur noch unter groessten Schmerzen "
1191 "bewegen. Aber bald tut nichts mehr weh"
1192 "...", 78 ) );
1193 say( break_string( capitalize(name(WER)) + " glueht wie "
1194 "im Fieber, kann sich kaum noch ruehren "
1195 "und hat ein schmerzverzerrtes Gesicht.\n",
1196 78 ) );
1197 break;
1198 }
1199 }
1200
1201 SetProp( P_HP, hp );
1202 SetProp( P_SP, sp );
1203}
1204
1205public int AddExp( int e )
1206{
1207 int experience;
1208 string fn;
1209 mixed last;
1210
1211 experience = QueryProp(P_XP);
1212
1213 if ( QueryProp(P_KILLS) > 1 && e > 0 )
1214 return experience;
1215
1216 fn = implode( explode( object_name( environment() || this_object() ),
1217 "/" )[0..<2], "/" );
1218
1219 if ( pointerp(last = Query(P_LAST_XP)) && sizeof(last) == 2 && last[0] == fn )
1220 Set( P_LAST_XP, ({ fn, last[1]+e }) );
1221 else
1222 Set( P_LAST_XP, ({ fn, e }) );
1223
1224 if ( (experience += e) < 0 )
1225 experience = 0;
1226
1227 return SetProp( P_XP, experience );
1228}
1229
1230static <string|int>* _set_last_xp( <int|string>* last )
1231{
1232 if ( !pointerp(last) || sizeof(last) != 2 || !stringp(last[0]) ||
1233 !intp(last[1]) )
1234 return Query(P_LAST_XP);
1235 else
1236 return Set( P_LAST_XP, last );
1237}
1238
1239
1240static int _set_align(int a)
1241{
1242 if (a<-1000) a = -1000;
1243 if (a>1000) a = 1000;
1244 return Set(P_ALIGN, a);
1245}
1246
1247
1248static int _set_hp( int hp )
1249{
1250 if ( QueryProp(P_GHOST) )
1251 return QueryProp(P_HP);
1252
1253 if ( hp < 0 )
1254 return Set( P_HP, 0 );
1255
1256 if ( hp > QueryProp(P_MAX_HP) )
1257 return Set( P_HP, QueryProp(P_MAX_HP), F_VALUE );
1258
1259 return Set( P_HP, hp, F_VALUE );
1260}
1261
1262static int _set_sp( int sp )
1263{
MG Mud User88f12472016-06-24 23:31:02 +02001264 if ( QueryProp(P_GHOST) )
1265 QueryProp(P_SP);
1266
1267 if ( sp < 0 )
1268 return Set( P_SP, 0 );
1269
1270 if ( sp > QueryProp(P_MAX_SP) )
1271 return Set( P_SP, QueryProp(P_MAX_SP), F_VALUE );
1272
1273 return Set( P_SP, sp, F_VALUE );
1274}
1275
1276static int _set_alcohol(int n)
1277{
MG Mud User88f12472016-06-24 23:31:02 +02001278 if (QueryProp(P_GHOST))
1279 return Query(P_ALCOHOL, F_VALUE);
1280
Zesstra9ad254c2019-09-27 00:30:41 +02001281 // nur Aenderungen und Werte >=0 werden gesetzt...
MG Mud User88f12472016-06-24 23:31:02 +02001282 n = n < 0 ? 0 : n;
1283 int old = Query(P_ALCOHOL, F_VALUE);
1284 if ( old == n)
1285 return old;
1286
1287 // Hooks aufrufen
1288 int *ret = HookFlow(H_HOOK_ALCOHOL, n);
1289 // Bei Abbruch alten Wert zurueckgeben
1290 switch (ret[H_RETCODE]) {
1291 case H_CANCELLED:
1292 return old;
1293 case H_ALTERED:
1294 // sonst neuen Wert setzen
1295 if(!intp(ret[H_RETDATA]))
1296 raise_error(sprintf(
1297 "_set_alcohol(): data from HookFlow() != <int>: %.50O\n",
1298 ret[H_RETDATA]));
1299 n = ret[H_RETDATA];
1300 n = n < 0 ? 0 : n;
1301
1302 // H_NO_MOD is fallthrough
1303 }
1304
1305 return Set(P_ALCOHOL, n, F_VALUE);
1306}
1307
1308static int _set_drink(int n)
1309{
MG Mud User88f12472016-06-24 23:31:02 +02001310 if (QueryProp(P_GHOST))
1311 return Query(P_DRINK, F_VALUE);
1312
Zesstra9ad254c2019-09-27 00:30:41 +02001313 // nur Aenderungen und Werte >=0 werden gesetzt...
MG Mud User88f12472016-06-24 23:31:02 +02001314 n = n < 0 ? 0 : n;
1315 int old = Query(P_DRINK, F_VALUE);
1316 if ( old == n)
1317 return old;
1318
1319 // Hooks aufrufen
1320 int *ret = HookFlow(H_HOOK_DRINK, n);
1321 // Bei Abbruch alten Wert zurueckgeben
1322 switch (ret[H_RETCODE]) {
1323 case H_CANCELLED:
1324 return old;
1325 case H_ALTERED:
1326 // sonst neuen Wert setzen
1327 if(!intp(ret[H_RETDATA]))
1328 raise_error(sprintf(
1329 "_set_drink(): data from HookFlow() != <int>: %.50O\n",
1330 ret[H_RETDATA]));
1331 n = ret[H_RETDATA];
1332 n = n < 0 ? 0 : n;
1333
1334 // H_NO_MOD is fallthrough
1335 }
1336
1337 return Set(P_DRINK, n, F_VALUE);
1338}
1339
1340static int _set_food(int n)
1341{
MG Mud User88f12472016-06-24 23:31:02 +02001342 if (QueryProp(P_GHOST))
1343 return Query(P_FOOD, F_VALUE);
1344
Zesstra9ad254c2019-09-27 00:30:41 +02001345 // nur Aenderungen und Werte >=0 werden gesetzt...
MG Mud User88f12472016-06-24 23:31:02 +02001346 n = n < 0 ? 0 : n;
1347 int old = Query(P_FOOD, F_VALUE);
1348 if ( old == n)
1349 return old;
1350
1351 // Hooks aufrufen
1352 int *ret = HookFlow(H_HOOK_FOOD, n);
1353 // Bei Abbruch alten Wert zurueckgeben
1354 switch (ret[H_RETCODE]) {
1355 case H_CANCELLED:
1356 return old;
1357 case H_ALTERED:
1358 // sonst neuen Wert setzen
1359 if(!intp(ret[H_RETDATA]))
1360 raise_error(sprintf(
1361 "_set_food(): data from HookFlow() != <int>: %.50O\n",
1362 ret[H_RETDATA]));
1363 n = ret[H_RETDATA];
1364 n = n < 0 ? 0 : n;
1365
1366 // H_NO_MOD is fallthrough
1367 }
1368
1369 return Set(P_FOOD, n, F_VALUE);
1370}
1371
1372static int _set_poison(int n)
1373{
MG Mud User88f12472016-06-24 23:31:02 +02001374 if (QueryProp(P_GHOST))
1375 return Query(P_POISON, F_VALUE);
1376
1377 int mp = QueryProp(P_MAX_POISON);
1378 n = (n<0 ? 0 : (n>mp ? mp : n));
1379
1380 // nur >=0 zulassen.
1381 n = n < 0 ? 0 : n;
1382
1383 int old = Query(P_POISON, F_VALUE);
1384 if ( old == 0 && n == 0)
1385 return old;
1386
1387 // Hooks aufrufen
1388 int *ret = HookFlow(H_HOOK_POISON, n);
1389 // Bei Abbruch alten Wert zurueckgeben
1390 switch (ret[H_RETCODE]) {
1391 case H_CANCELLED:
1392 return old;
1393 case H_ALTERED:
1394 // sonst neuen Wert setzen
1395 if(!intp(ret[H_RETDATA]))
1396 raise_error(sprintf(
1397 "_set_poison(): data from HookFlow() != <int>: %.50O\n",
1398 ret[H_RETDATA]));
1399 n = ret[H_RETDATA];
1400 n = n < 0 ? 0 : n;
1401
1402 // H_NO_MOD is fallthrough
1403 }
1404
1405 // Fuer die Selbstheilung.
1406 switch(n) {
1407 case 1:
1408 drop_poison = 40+random(16);
1409 break;
1410 case 2:
1411 drop_poison = 25+random(8);
1412 break;
1413 case 3:
1414 drop_poison = 18+random(4);
1415 break;
1416 default:
1417 // nur relevant fuer NPC, da Spieler bei >3 Gift nicht mehr regegenieren.
1418 drop_poison = 22 - 2*n + random(43 - 3*n);
1419 break;
1420 }
1421
1422 // fuer Setzen der Prop von aussen ein Log schreiben
1423 if (previous_object(1) != ME)
1424 log_file("POISON", sprintf("%s - %s: %d von %O (%s)\n",
1425 dtime(time())[5..],
1426 (query_once_interactive(this_object()) ?
1427 capitalize(geteuid(this_object())) :
1428 capitalize(name(WER))),
1429 n,
1430 (previous_object(2) ? previous_object(2) : previous_object(1)),
1431 (this_player() ? capitalize(geteuid(this_player())) : "???")));
1432
1433 return Set(P_POISON, n, F_VALUE);
1434}
1435
1436static int _set_xp(int xp) { return Set(P_XP, xp < 0 ? 0 : xp, F_VALUE); }
1437
1438static mixed _set_die_hook(mixed hook)
1439{
1440 if(hook && query_once_interactive(this_object()))
1441 log_file("DIE_HOOK",
1442 sprintf("%s : DIE_HOOK gesetzt von %O in %O (%s)\n",
1443 dtime(time())[5..],
1444 (previous_object(2) ? previous_object(2):previous_object(1)),
1445 this_object(),getuid(this_object())));
1446 return Set(P_TMP_DIE_HOOK,hook, F_VALUE);
1447}
1448
1449static mapping _query_enemy_damage()
1450{
1451 return copy(enemy_damage);
1452}
1453
1454// nur ne Kopie liefern, sonst kann das jeder von aussen aendern.
1455static mapping _query_timing_map() {
1456 return copy(Query(P_TIMING_MAP));
1457}
1458
1459/****************************************************************************
1460 * Consume-Funktion, um zentral durch konsumierbare Dinge ausgeloeste
1461 * Aenderungen des Gesundheitszustandes herbeizufuehren.
1462 ***************************************************************************/
1463
1464/* Konsumiert etwas
1465 *
1466 * Rueckgabewert
1467 * 1 erfolgreich konsumiert
1468 * 0 fehlende oder falsche Parameter
1469 * <0 Bedingung fuer konsumieren nicht erfuellt, Bitset aus:
1470 * 1 Kann nichts mehr essen
1471 * 2 Kann nichts mehr trinken
1472 * 4 Kann nichts mehr saufen
1473 * 8 Abgebrochen durch Hook H_HOOK_CONSUME
1474 */
1475public varargs int consume(mapping cinfo, int testonly)
1476{
1477 int retval = 0;
1478 // nur was tun, wenn auch Infos reinkommen
1479 if (mappingp(cinfo) && sizeof(cinfo)) {
1480 // Hooks aufrufen, sie aendern ggf. noch was in cinfo.
1481 mixed *hret = HookFlow(H_HOOK_CONSUME, ({cinfo, testonly}) );
1482 switch(hret[H_RETCODE])
1483 {
1484 case H_CANCELLED:
1485 return -HC_HOOK_CANCELLATION;
1486 case H_ALTERED:
1487 // testonly kann nicht geaendert werden.
1488 cinfo = hret[H_RETDATA][0];
1489 }
1490 // Legacy-Mappings (flache) neben strukturierten Mappings zulassen
1491 // flache Kopien erzeugen (TODO?: und fuer Teilmappings nicht relevante
1492 // Eintraege loeschen)
1493 mapping conditions;
1494 if (mappingp(cinfo[H_CONDITIONS])) {
1495 conditions = copy(cinfo[H_CONDITIONS]);
1496 } else {
1497 conditions = copy(cinfo);
1498 }
1499 mapping effects;
1500 if (mappingp(cinfo[H_EFFECTS])) {
1501 effects = filter(cinfo[H_EFFECTS], (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
1502 } else {
1503 effects = filter(cinfo, (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
1504 }
1505
1506 // Bedingungen pruefen
1507 if (mappingp(conditions) && sizeof(conditions)) {
1508 // Bedingungen fuer Konsum auswerten
1509 if (conditions[P_FOOD] && !eat_food(conditions[P_FOOD], 1))
1510 retval |= HC_MAX_FOOD_REACHED;
1511 else if (conditions[P_DRINK] && !drink_soft(conditions[P_DRINK], 1))
1512 retval |= HC_MAX_DRINK_REACHED;
1513 else if (conditions[P_ALCOHOL] && !drink_alcohol(conditions[P_ALCOHOL], 1))
1514 retval |= HC_MAX_ALCOHOL_REACHED;
1515 // retval negativ machen, damit Fehler leicht erkennbar ist
1516 retval = -retval;
1517 }
1518 // Bedingungen wurden abgearbeitet, jetzt die Heilung durchfuehren
1519 if (!retval) {
1520 if (!testonly) {
1521 // Bedingungen erfuellen, wenn alles passt und kein Test
1522 if (conditions[P_ALCOHOL])
1523 drink_alcohol(conditions[P_ALCOHOL]);
1524 if (conditions[P_DRINK])
1525 drink_soft(conditions[P_DRINK]);
1526 if (conditions[P_FOOD])
1527 eat_food(conditions[P_FOOD]);
1528 // Und jetzt die Wirkungen
1529 if (effects[P_POISON])
1530 SetProp(P_POISON, QueryProp(P_POISON) + effects[P_POISON]);
1531 // Und nun wirklich heilen
1532 switch (cinfo[H_DISTRIBUTION]) {
1533 case HD_INSTANT:
1534 map(effects, (: SetProp($1, QueryProp($1) + $2) :));
1535 break;
1536 case 1..50:
1537 buffer_hp(effects[P_HP], cinfo[H_DISTRIBUTION]);
1538 buffer_sp(effects[P_SP], cinfo[H_DISTRIBUTION]);
1539 break;
1540 default:
1541 buffer_hp(effects[P_HP], HD_STANDARD);
1542 buffer_sp(effects[P_SP], HD_STANDARD);
1543 break;
1544 }
1545 }
1546 retval = 1;
1547 }
1548 }
1549 return retval;
1550}