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