blob: f3a72f5f537e8c5420aa714d7ff037cd23823230 [file] [log] [blame]
// MorgenGrauen MUDlib
//
// living/life.c -- life variables
//
// $Id: life.c 9426 2016-01-03 10:02:57Z Zesstra $
// living object life variables
//
// P_ALIGN -- alignment value
// P_NPC -- if living is an NPC
// P_HP -- HitPoints
// P_SP -- SpellPoints
// P_ALCOHOL -- value of intoxication
// P_DRINK -- value of soakness
// P_FOOD -- value of stuffness
// P_XP -- experience
// P_POISON -- level of poison
// P_CORPSE -- corpse-object
// P_DEAF -- if living is deaf
#pragma strong_types,save_types,rtt_checks
#pragma range_check
#pragma no_clone
#pragma pedantic
#define NEED_PROTOTYPES
#include <hook.h>
#include <living/skills.h>
#include <thing/properties.h>
#include <living/life.h>
#include <living/moving.h>
#include <living/combat.h>
#include <living/attributes.h>
#include <thing/description.h>
#include <thing/language.h>
#undef NEED_PROTOTYPES
#include <health.h>
#include <defines.h>
#include <new_skills.h>
#include <scoremaster.h>
#include <defuel.h>
#include <properties.h>
#include <events.h>
#include <wizlevels.h>
#define ALCOHOL_VALUE(n) n
#include <debug_info.h> //voruebergehend
// 'private'-Prototypen
private void DistributeExp(object enemy, int exp_to_give);
// Variablen
nosave int delay_alcohol; /* time until next alcohol effect */
nosave int delay_drink; /* time until next drink effect */
nosave int delay_food; /* time until next food effect */
nosave int delay_heal; /* time until next heal effect */
nosave int delay_sp; /* time until next sp regeneration */
nosave int delay_poison; /* time until next poison effect */
nosave int drop_poison;
nosave mapping enemy_damage;
nosave mapping hp_buffer;
nosave mapping sp_buffer;
nosave int remove_me;
int nextdefueltimefood;
int nextdefueltimedrink;
protected void create()
{
Set(P_GHOST, SAVE, F_MODE);
Set(P_FROG, SAVE, F_MODE);
Set(P_ALIGN, SAVE, F_MODE);
Set(P_HP, SAVE, F_MODE);
Set(P_SP, SAVE, F_MODE);
Set(P_XP, SAVE, F_MODE);
Set( P_LAST_XP, ({ "", 0 }) );
Set( P_LAST_XP, PROTECTED, F_MODE_AS );
Set(P_ALCOHOL, SAVE, F_MODE);
Set(P_DRINK, SAVE, F_MODE);
Set(P_FOOD, SAVE, F_MODE);
Set(P_POISON, SAVE, F_MODE);
Set(P_DEAF, SAVE, F_MODE);
SetProp(P_FOOD_DELAY, FOOD_DELAY);
SetProp(P_DRINK_DELAY, DRINK_DELAY);
SetProp(P_ALCOHOL_DELAY, ALCOHOL_DELAY);
SetProp(P_HP_DELAY,HEAL_DELAY);
SetProp(P_SP_DELAY,HEAL_DELAY);
SetProp(P_POISON_DELAY,POISON_DELAY);
// default fuer alle Lebewesen (NPC + Spieler):
SetProp(P_MAX_POISON, 10);
SetProp(P_CORPSE, "/std/corpse");
nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
enemy_damage=([:2 ]);
hp_buffer=([]);
sp_buffer=([]);
SetProp(P_DEFUEL_LIMIT_FOOD,1);
SetProp(P_DEFUEL_LIMIT_DRINK,1);
SetProp(P_DEFUEL_TIME_FOOD,1);
SetProp(P_DEFUEL_TIME_DRINK,1);
SetProp(P_DEFUEL_AMOUNT_FOOD,1);
SetProp(P_DEFUEL_AMOUNT_DRINK,1);
offerHook(H_HOOK_DIE,1);
offerHook(H_HOOK_FOOD,1);
offerHook(H_HOOK_DRINK,1);
offerHook(H_HOOK_ALCOHOL,1);
offerHook(H_HOOK_POISON,1);
offerHook(H_HOOK_CONSUME,1);
}
// Wenn der letzte Kampf lang her ist und das Lebewesen wieder vollgeheilt
// ist, wird P_ENEMY_DAMAGE zurueckgesetzt.
protected void ResetEnemyDamage() {
if (time() > QueryProp(P_LAST_COMBAT_TIME) + __RESET_TIME__ * 4
&& QueryProp(P_HP) == QueryProp(P_MAX_HP))
enemy_damage=([:2 ]);
}
private void DistributeExp(object enemy, int exp_to_give) {
int total_damage, tmp, ex;
mapping present_enemies;
if ( exp_to_give<=0 )
return;
mapping endmg=deep_copy(enemy_damage);
// Mitglieder im Team des Killers bekommen:
//
// Gesamtanteil des Teams
// Eigenen Anteil + ----------------------
// Anzahl Teammitglieder
// ---------------------------------------
// 2
//
object *inv = enemy->TeamMembers();
if ( pointerp(inv) )
{
present_enemies=m_allocate(sizeof(inv), 1);
foreach(object ob: inv)
{
if ( objectp(ob) && (environment(ob)==environment()) )
{
tmp=endmg[object_name(ob)];
total_damage+=tmp;
present_enemies[ob] = tmp/2;
m_delete(endmg,object_name(ob)); //s.u.
}
}
int mitglieder = sizeof(present_enemies);
if ( mitglieder )
{
tmp=total_damage/(2*mitglieder);
foreach(object ob, int punkte: &present_enemies)
punkte += tmp;
}
}
else {
// ohne Team wird trotzdem ein Mapping gebraucht. Da Groessenveraenderung
// rel. teuer sind, kann einfach mal fuer 3 Eintraege Platz reservieren.
present_enemies=m_allocate(3, 1);
}
// Und noch die Lebewesen im Raum ohne Team.
foreach(object ob: all_inventory(environment()))
{
if ( tmp=endmg[object_name(ob)] )
{
total_damage += tmp;
present_enemies[ob] = tmp;
m_delete(endmg,object_name(ob)); // Nur einmal pro Leben Punkte :)
}
}
if ( !total_damage )
{
enemy->AddExp(exp_to_give);
}
else
{
foreach(object ob, int damage: present_enemies)
{
if ( !objectp(ob) )
continue;
if ( query_once_interactive(ob) && ( !interactive(ob)
|| (query_idle(ob)>600) ) )
continue;
//exp_to_give*present_enemies[i][1]/total_damage gibt bei viel Schaden
//einen numerical overflow. Daher muessen wir hier wohl doch
//zwischenzeitlich mit floats rechnen, auch wenn das 0-1 XP Verlust
//durch float->int-Konversion gibt. (ceil() lohnt sich IMHO nicht.)
ex = (int)(exp_to_give*((float)damage/(float)total_damage));
ob->AddExp(ex);
}
}
}
/*
* This function is called from other players when they want to make
* damage to us. But it will only be called indirectly.
* We return how much damage we received, which will
* change the attackers score. This routine is probably called from
* heart_beat() from another player.
* Compare this function to reduce_hit_points(dam).
*/
public int do_damage(int dam, object enemy)
{ int hit_point,al,al2;
if ( extern_call()
&& objectp(enemy)
&& living(enemy)
&& !QueryProp(P_ENABLE_IN_ATTACK_OUT))
{
al=time()-enemy->QueryProp(P_LAST_MOVE);
if (al<3) // Erste Kampfrunde nach Betreten des Raumes?
dam/=(4-al); // Gegen Rein-Feuerball-Raus-Taktik
}
if ( QueryProp(P_GHOST) || QueryProp(P_NO_ATTACK) || (dam<=0)
|| ( objectp(enemy)
&& ( enemy->QueryProp(P_GHOST)
|| enemy->QueryProp(P_NO_ATTACK) ) ) )
return 0;
hit_point = QueryProp(P_HP)-dam;
if ( QueryProp(P_XP) && objectp(enemy) )
{
if ( !QueryProp(P_NO_XP) )
enemy->AddExp(dam*(int)QueryProp(P_TOTAL_WC)/10);
}
if (living(enemy)) {
string enname = object_name(enemy);
// Hmpf. Blueprints sind doof. Die Chance ist zwar gering, aber koennte
// sein, dass ein Unique-NPC mit zwei verschiedenen Spielern am gleichen
// NPC metzelt.
// TODO: MHmm. wie gross ist das Risiko wirklich?
//if (!clonep(enemy))
// enname = enname + "_" + to_string(object_time(enemy));
// nur wenn gegner NPC ist und noch nicht drinsteht: Daten aus
// P_HELPER_NPC auswerten
if (!member(enemy_damage,enemy) && !query_once_interactive(enemy)) {
mixed helper = enemy->QueryProp(P_HELPER_NPC);
if (pointerp(helper) && objectp(helper[0]))
enemy_damage[enname,1] = helper[0];
}
enemy_damage[enname,0]+=dam;
}
SetProp(P_HP, hit_point);
if ( hit_point<0 )
{
//TODO: Warum nicht das ganze Zeug ins die() verlegen?
if ( enemy )
{
enemy->StopHuntFor(ME,1);
if ( !QueryProp(P_NO_XP) )
DistributeExp(enemy,QueryProp(P_XP)/100);
if ( !query_once_interactive(ME) )
log_file ("NPC_XP", sprintf(
"[%s] %s, XP: %d, HP*WC: %d, Killer: %s\n",
dtime(time()), object_name(ME), (QueryProp(P_XP)/100),
QueryProp(P_TOTAL_WC)*QueryProp(P_MAX_HP)/10,
enemy->name()||"NoName" ));
al = QueryProp(P_ALIGN)/50 + enemy->QueryProp(P_ALIGN)/200;
if (al>20)
al=20;
else if(al<-20)
al=-20;
enemy->SetProp(P_ALIGN,enemy->QueryProp(P_ALIGN)-al);
}
SetProp(P_KILLER, enemy);
die();
}
return dam;
}
private void _transfer( object *obs, string|object dest, int flag )
{ int i;
i = sizeof(obs);
// Eine Schleife ist zwar langsamer als filter() o.ae., aber
// selbst mit einer noch so schnellen Loesung kann leider nicht
// ausgeschlossen werden, dass irgendwo ein too-long-eval-Bug dazwischen
// kommt. Dazu sind die Kaempfe mit Gilden-NPCs etc. einfach zu teuer ...
// Pruefung auf zerstoerte Objekte, da einige sich evtl. im NotifyPlayerDeath()
// zerstoeren.
while ( i && get_eval_cost() > 300000 )
if ( objectp(obs[--i]) && !obs[i]->QueryProp(P_NEVERDROP) )
// Jetzt wird's noch etwas teurer mit catch() - aber manche Sachen
// duerfen einfach nicht buggen
catch( obs[i]->move( dest, flag );publish );
if ( i > 0 )
// Zuviel Rechenzeit verbraten, es muessen noch Objekte bewegt werden
call_out( #'_transfer, 0, obs[0..i-1], dest, flag );
else {
if ( remove_me )
remove();
}
}
public varargs void transfer_all_to( string|object dest, int isnpc ) {
int flags;
object *obs;
if ( !objectp(ME) )
return;
// Das Flag "isnpc" ist fuer NPCs gedacht. Deren Ausruestung darf nicht
// mit M_NOCHECK bewegt werden, da Spieler das bei Nicht-Standard-Leichen
// sonst u.U. ausnutzen koennten.
if ( isnpc )
flags = M_SILENT;
else
flags = M_SILENT|M_NOCHECK;
obs = all_inventory(ME) || ({});
// unnoetig, weil _transfer() auch auf P_NEVERDROP prueft. Zesstra
//obs -= filter_objects( obs, "QueryProp", P_NEVERDROP );
_transfer( obs, dest, flags );
}
protected varargs void create_kill_log_entry(string killer, object enemy) {
int level,lost_exp;
if ( (level=QueryProp(P_LEVEL))<20 || !IS_SEER(ME) )
lost_exp = QueryProp(P_XP)/3;
else
lost_exp = QueryProp(P_XP)/(level-17);
log_file("KILLS",sprintf("%s %s (%d,%d) %s\n", strftime("%e %b %H:%M"),
capitalize(REAL_UID(ME)), level, lost_exp/1000, killer));
}
// Liefert im Tod (nach dem toetenden do_damage()) das Spielerobjekt, was den
// Tod wohl zu verantworten hat, falls es ermittelt werden kann. Es werden vor
// allem registrierte Helfer-NPC und einige Sonderobjekte beruecksichtigt.
protected object get_killing_player()
{
object killer=QueryProp(P_KILLER);
// koennte sein, wenn ausserhalb des Todes gerufen oder eine Vergiftung uns
// umgebracht hat.
if (!objectp(killer))
return 0;
while (killer && !query_once_interactive(killer))
killer = killer->QueryUser();
return killer;
}
protected object GiveKillScore(object pl, int npcnum)
{
// Stufenpunkt fuer den Kill vergeben.
// Falls der Killer den Punkt schon hat, wird
// zufaellig ein Mitglied seines Teams ausgewaehlt
// und diesem der Punkt gegeben.
object *obs,ob;
mixed *fr;
int i,j,sz;
if ( pointerp(obs=pl->TeamMembers()) && (member(obs,pl)>=0) )
{
if ( !pointerp(fr=pl->PresentTeamRows())
|| !sizeof(fr)
|| !pointerp(fr=fr[0])) // Erste Reihe des Teams
fr=({});
fr-=({pl,0});
obs-=({pl,0});
obs-=fr;
i=sz=sizeof(obs); // restliche Teammitglieder in zufaelliger Reihenfolge:
for ( --i ; i>=0 ; i-- )
{
j=random(sz);
ob=obs[j];
obs[j]=obs[0];
obs[0]=ob;
}
i=sz=sizeof(fr); // Erste Reihe in zufaelliger Reihenfolge:
for ( --i ; i>=0 ; i-- )
{
j=random(sz);
ob=fr[j];
fr[j]=fr[0];
fr[0]=ob;
}
obs+=fr; // Erste Reihe wird vor Rest getestet
obs+=({pl}); // Killer wird als erstes getestet
}
else
{
obs=({pl});
}
for ( i=sizeof(obs)-1 ; i>=0 ; i-- )
if ( objectp(ob=obs[i] )
&& interactive(ob) // Nur netztot dabei stehen gilt nicht :)
&& query_idle(ob)<600 // gegen Leute die sich nur mitschleppen lassen
&& environment(ob)==environment(pl) // Nur anwesende Teammitglieder
&& !IS_LEARNER(ob)
// && !ob->QueryProp(P_TESTPLAYER)
&& !(SCOREMASTER->HasKill(ob,ME)) )
return SCOREMASTER->GiveKill(ob,npcnum),ob;
return SCOREMASTER->GiveKill(pl,npcnum),pl;
}
// zum ueberschreiben in Spielern
public int death_suffering() {
return 0; // NPC haben keine Todesfolgen
}
// kein 2. Leben fuer Nicht-Spieler. ;-)
varargs protected int second_life( object corpse ) {
return 0;
}
public varargs void die( int poisondeath, int extern )
{ object corpse;
string die_msg, tmp;
mixed res;
mixed hookData;
mixed hookRes;
if ( !objectp(this_object()) || QueryProp(P_GHOST) )
return; // Ghosts dont die ...
// direkt von extern aufgerufen und nicht ueber heart_beat() oder
// do_damage() hierher gelangt?
if (extern_call() && previous_object() != this_object()) {
extern=1;
SetProp(P_KILLER, previous_object());
}
if ( res = QueryProp(P_TMP_DIE_HOOK) ){
if ( pointerp(res) && sizeof(res)>=3
&& intp(res[0]) && time()<res[0]
&& objectp(res[1]) && stringp(res[2]) )
{
if ( res = call_other( res[1], res[2], poisondeath ) ) {
SetProp(P_KILLER,0);
return;
}
}
else
SetProp(P_TMP_DIE_HOOK,0);
}
// trigger die hook
hookData=poisondeath;
hookRes=HookFlow(H_HOOK_DIE,hookData);
if (pointerp(hookRes) && sizeof(hookRes)>H_RETDATA){
if(hookRes[H_RETCODE]==H_CANCELLED) {
SetProp(P_KILLER,0);
return;
}
else if (hookRes[H_RETCODE]==H_ALTERED)
poisondeath = hookRes[H_RETDATA];
}
if ( IS_LEARNING(ME) && query_once_interactive(ME) ){
tell_object( ME, "Sei froh dass Du unsterblich bist, sonst waere es "
"eben Dein Ende gewesen.\n");
SetProp(P_KILLER,0);
return;
}
// Gegner befrieden.
map_objects( QueryEnemies()[0], "StopHuntFor", ME, 1 );
StopHuntingMode(1);
// Falls die() direkt aufgerufen wurde und dies ein Spieler ist, muss das
// die() noch Eintraege in /log/KILLS via create_kill_log_entry bzw. in
// /log/KILLER erstellen.
if ( query_once_interactive(ME) && extern )
{
object killer = QueryProp(P_KILLER)
|| previous_object() || this_interactive() || this_player();
if ( killer && !query_once_interactive(killer) )
{
tmp = explode( object_name(killer), "#")[0] + " (direkt !)";
create_kill_log_entry( tmp + " (" + REAL_UID(killer) + ")", killer );
}
else if ( killer && !QueryProp(P_TESTPLAYER) && !IS_LEARNER(ME) )
{
log_file( "KILLER", sprintf( "%s %s (%d/%d) toetete %s (%d/%d)\n",
ctime(time()),
capitalize(getuid(killer)),
query_wiz_level(killer),
killer->QueryProp(P_LEVEL),
capitalize(getuid(ME)),
query_wiz_level(ME),
QueryProp(P_LEVEL) ) );
killer->SetProp( P_KILLS, -1 );
}
}
// Bei NPC EKs vergeben und ggf. in der Gilde des Killers und im Raum
// NPC_Killed_By() rufen.
if ( !query_once_interactive(ME) )
{
object killer = ((object) QueryProp(P_KILLER)) || previous_object() ||
this_interactive() || this_player();
if ( killer && query_once_interactive(killer) )
{
if (stringp(res=killer->QueryProp(P_GUILD))
&& objectp(res=find_object("/gilden/"+res)))
res->NPC_Killed_By(killer);
if (environment())
environment()->NPC_Killed_By(killer);
res = QueryProp(P_XP);
res = (res < SCORE_LOW_MARK) ? 0 : ((res > SCORE_HIGH_MARK) ? 2 : 1);
if ( !QueryProp(P_NO_SCORE) && !IS_LEARNER(killer) &&
// !killer->QueryProp(P_TESTPLAYER) &&
pointerp( res = SCOREMASTER->QueryNPC(res)) )
GiveKillScore( killer, res[0] );
}
}
if( !(die_msg = QueryProp(P_DIE_MSG)) )
if (QueryProp(P_PLURAL))
die_msg = " fallen tot zu Boden.\n";
else
die_msg = " faellt tot zu Boden.\n";
if ( poisondeath )
{
Set( P_LAST_DAMTYPES, ({ DT_POISON }) );
Set( P_LAST_DAMTIME, time() );
Set( P_LAST_DAMAGE, 1 );
die_msg = " wird von Gift hinweggerafft und kippt um.\n";
}
say( capitalize(name(WER,1)) + die_msg );
// Wenn keine Leiche, dann Kram ins Env legen.
if ( QueryProp(P_NOCORPSE) || !(tmp = QueryProp(P_CORPSE))
|| catch(corpse = clone_object(tmp);publish)
|| !objectp(corpse) )
{
// Magier oder Testspieler behalten ihre Ausruestung.
// Sonst kaemen u.U. Spieler an Magiertools etc. heran
if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
(!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
transfer_all_to( environment(), 0 );
else
// Aber sie ziehen sich aus.
filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
}
else
// sonst in die Leiche legen.
{
corpse->Identify(ME);
corpse->move( environment(), M_NOCHECK|M_SILENT );
// Magier oder Testspieler behalten ihre Ausruestung.
// Sonst kaemen u.U. Spieler an Magiertools etc. heran
if ( !(IS_LEARNER(ME) || (tmp = Query(P_TESTPLAYER)) &&
(!stringp(tmp) || IS_LEARNER( lower_case(tmp) ))) )
transfer_all_to( corpse, !query_once_interactive(ME) );
else
// Aber sie ziehen sich aus.
filter_objects(QueryProp(P_ARMOURS),"DoUnwear",M_NOCHECK,0);
}
if ( query_once_interactive(ME) ) {
Set( P_DEADS, Query(P_DEADS) + 1 );
// Spieler-Tod-event ausloesen
EVENTD->TriggerEvent(EVT_LIB_PLAYER_DEATH, ([
E_OBJECT: ME, E_PLNAME: getuid(ME),
E_ENVIRONMENT: environment(), E_TIME: time(),
P_KILLER: QueryProp(P_KILLER),
P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
P_LAST_DAMTYPES: copy(QueryProp(P_LAST_DAMTYPES)),
E_EXTERNAL_DEATH: extern,
E_POISON_DEATH: poisondeath,
E_CORPSE: (objectp(corpse)?corpse:0) ]) );
}
else {
// NPC-Todes-Event ausloesen. Div. Mappings/Arrays werden nicht kopiert,
// weil der NPC ja jetzt eh zerstoert wird.
mapping data = ([
E_OBNAME: object_name(ME),
E_ENVIRONMENT: environment(), E_TIME: time(),
P_NAME: QueryProp(P_NAME),
P_KILLER: QueryProp(P_KILLER),
P_ENEMY_DAMAGE: QueryProp(P_ENEMY_DAMAGE),
P_LAST_DAMAGE: QueryProp(P_LAST_DAMAGE),
P_LAST_DAMTYPES: QueryProp(P_LAST_DAMTYPES),
E_EXTERNAL_DEATH: extern,
E_POISON_DEATH: poisondeath,
E_CORPSE: (objectp(corpse)?corpse:0),
P_XP: QueryProp(P_XP),
P_ATTRIBUTES: QueryProp(P_ATTRIBUTES),
P_MAX_HP: QueryProp(P_MAX_HP),
P_HANDS: QueryProp(P_HANDS),
P_ALIGN: QueryProp(P_ALIGN),
P_RACE: QueryProp(P_RACE),
P_CLASS: QueryProp(P_CLASS),
]);
EVENTD->TriggerEvent(EVT_LIB_NPC_DEATH(""), data);
EVENTD->TriggerEvent(
EVT_LIB_NPC_DEATH(load_name(ME)), data);
}
// transfer_all_to() ist evtl. (wenn zuviele Objekte bewegt werden mussten)
// noch nicht ganz fertig und wird per call_out() den Rest erledigen.
// Sollte die Leiche dann nicht mehr existieren, verbleiben die restlichen
// Objekte im Spieler.
// Es bleiben aber auf jeden Fall noch rund 300k Eval-Ticks ueber, damit
// kein Spieler dank "evalcost too high" ungeschoren davon kommt.
if ( !(second_life(corpse)) )
{
Set( P_GHOST, 1 ); // Fuer korrekte Ausgabe auf Teamkanal.
if ( find_call_out(#'_transfer) == -1 )
// Falls kein call_out() mehr laeuft, sofort destructen ...
remove();
else
// ... ansonsten vormerken
remove_me = 1;
}
}
public void heal_self(int h)
{
if ( h<=0 )
return;
SetProp(P_HP, QueryProp(P_HP)+h);
SetProp(P_SP, QueryProp(P_SP)+h);
}
//--------------------------------------------------------------------------
//
// int defuel_food( /* void */ )
//
// Enttankt den Spieler um einen gewissen Essens-Wert.
// Sollte nur von Toiletten aufgerufen werden.
//
//--------------------------------------------------------------------------
public int defuel_food()
{
int food;
food=QueryProp(P_FOOD);
// wenn spieler kein food hat: return 0
if ( !food )
return NO_DEFUEL;
// wenn spieler unter enttank-grenze: return -1
if ( food < QueryProp(P_DEFUEL_LIMIT_FOOD) )
return DEFUEL_TOO_LOW;
// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
if ( time() < nextdefueltimefood )
return DEFUEL_TOO_SOON;
food=to_int(((food*QueryProp(P_DEFUEL_AMOUNT_FOOD))/2));
food+=random(food);
// sicherheitshalber
if ( food > QueryProp(P_FOOD) )
food=QueryProp(P_FOOD);
SetProp(P_FOOD,(QueryProp(P_FOOD)-food));
nextdefueltimefood=time()+QueryProp(P_DEFUEL_TIME_FOOD);
return food;
}
//--------------------------------------------------------------------------
//
// int defuel_drink( /* void */ )
//
// Enttankt den Spieler um einen gewissen Fluessigkeits-Wert.
// Gleichzeitig wird eine gewisse Menge Alkohol reduziert.
// Sollte nur von Toiletten aufgerufen werden.
//
//--------------------------------------------------------------------------
public int defuel_drink()
{
int alc, drink;
drink=QueryProp(P_DRINK);
// wenn spieler kein drink hat: return 0
if ( !drink )
return NO_DEFUEL;
// wenn spieler unter enttank-grenze: return -1
if ( drink < QueryProp(P_DEFUEL_LIMIT_DRINK) )
return DEFUEL_TOO_LOW;
// wenn letztes enttanken nicht lange genug zurueckliegt: return -2
if ( time() < nextdefueltimedrink )
return DEFUEL_TOO_SOON;
drink=to_int(((drink*QueryProp(P_DEFUEL_AMOUNT_DRINK))/2));
drink+=random(drink);
// sicherheitshalber
if ( drink > QueryProp(P_DRINK) )
drink=QueryProp(P_DRINK);
SetProp(P_DRINK,(QueryProp(P_DRINK)-drink));
// jedes fluessige Enttanken macht auch etwas nuechterner :^)
// bei sehr kleinen Mengen enttankt man keinen Alkohol
// ansonsten in Abhaengigkeit von enttankter Menge, P_ALCOHOL und P_WEIGHT
if ( drink > 9 && QueryProp(P_ALCOHOL) > 0 )
{
alc=(to_int(exp(log(1.1)*(drink)))*
to_int(exp(log(0.67)*(QueryProp(P_ALCOHOL)))))/
(QueryProp(P_MAX_DRINK)*QueryProp(P_MAX_ALCOHOL))*
(to_int(QueryProp(P_WEIGHT)/1000));
SetProp(P_ALCOHOL,QueryProp(P_ALCOHOL)-(alc+random(alc)));
}
nextdefueltimedrink=time()+QueryProp(P_DEFUEL_TIME_DRINK);
return drink;
}
public void reduce_spell_points(int h)
{
SetProp(P_SP, QueryProp(P_SP)-h);
}
public void restore_spell_points(int h)
{
SetProp(P_SP, QueryProp(P_SP)+h);
}
/* Reduce hitpoints. Log who is doing it. */
public int reduce_hit_points(int dam)
{ object o;
int i;
#ifdef LOG_REDUCE_HP
if (this_player()!=ME)
{
log_file("REDUCE_HP", name()+" by ");
if(!this_player()) log_file("REDUCE_HP","?\n");
else {
log_file("REDUCE_HP",this_player()->name());
o=previous_object();
if (o)
log_file("REDUCE_HP", " " + object_name(o) + ", " +
o->name(WER,0) + " (" + creator(o) + ")\n");
else
log_file("REDUCE_HP", " ??\n");
}
}
#endif
if ((i=QueryProp(P_HP)) <= dam)
return SetProp(P_HP,1);
return SetProp(P_HP, i - dam);
}
public int restore_hit_points(int heal)
{
return reduce_hit_points(-heal);
}
public varargs int drink_alcohol(int strength,int testonly, string mytext)
{ int alc,add,res;
add=ALCOHOL_VALUE(strength);
res=UseSkill(SK_BOOZE,([
SI_SKILLARG : add,
SI_TESTFLAG : 1])); // Kann der Spieler gut saufen?
if (intp(res) && res>0) add=res;
alc=QueryProp(P_ALCOHOL)+add;
if ((alc >= QueryProp(P_MAX_ALCOHOL)) && !IS_LEARNING(this_object())){
if(!testonly)
tell_object(ME,mytext||"So ein Pech, Du hast alles verschuettet.\n");
return 0;
}
if(testonly)return 1;
UseSkill(SK_BOOZE,([ SI_SKILLARG : ALCOHOL_VALUE(strength) ]));
if(alc < 0) alc = 0;
if(!alc) tell_object(ME, "Du bist stocknuechtern.\n");
SetProp(P_ALCOHOL, alc);
return 1;
}
public varargs int drink_soft(int strength, int testonly, string mytext)
{ int soaked;
soaked = QueryProp(P_DRINK);
if((soaked + strength > QueryProp(P_MAX_DRINK)) &&
!IS_LEARNING(this_object())){
if(!testonly)
tell_object(ME, mytext||
"Nee, so viel kannst Du momentan echt nicht trinken.\n" );
return 0;
}
if(testonly)return 1;
if((soaked += DRINK_VALUE(strength)) < 0) soaked = 0;
if(!soaked) tell_object(ME, "Dir klebt die Zunge am Gaumen.\n");
SetProp(P_DRINK, soaked);
return 1;
}
public varargs int eat_food(int strength, int testonly, string mytext)
{ int stuffed;
stuffed = QueryProp(P_FOOD);
if ((stuffed + strength > QueryProp(P_MAX_FOOD)) &&
!IS_LEARNING(this_object()))
{
if(!testonly)
tell_object(ME,
mytext || "Das ist viel zu viel fuer Dich! Wie waers mit etwas "
"leichterem?\n");
return 0;
}
if(testonly)return 1;
stuffed += FOOD_VALUE(strength);
if(stuffed < 0) stuffed = 0;
if(!stuffed) tell_object(ME, "Was rumpelt denn da in Deinem Bauch?\n");
SetProp(P_FOOD, stuffed);
return 1;
}
public int buffer_hp(int val,int rate)
{
int dif;
if(val<=0 || rate<=0)return 0;
if(val < rate)rate = val;
if(rate>20) rate=20;
/* Check for BufferOverflow */
if((dif=(hp_buffer[0]+val)-QueryProp(P_MAX_HP)) > 0)val-=dif;
if(val<=0)return 0;
hp_buffer[0] += val;
hp_buffer[1+rate] += val;
if(rate > hp_buffer[1])hp_buffer[1] = rate;
return hp_buffer[0];
}
public int buffer_sp(int val,int rate)
{
int dif;
if(val<=0 || rate<=0)return 0;
if(val < rate)rate = val;
if(rate>20) rate=20;
/* Check for BufferOverflow */
if((dif=(sp_buffer[0]+val)-QueryProp(P_MAX_SP)) > 0)val-=dif;
if(val<=0)return 0;
sp_buffer[0] += val;
sp_buffer[1+rate] += val;
if(rate > sp_buffer[1])sp_buffer[1] = rate;
return sp_buffer[0];
}
protected void update_buffers()
{ int i, rate, max;
rate=0;
max=0;
for(i=1;i<=20;i++){
if(member(hp_buffer, i+1))
if(hp_buffer[i+1]<=0)
hp_buffer = m_delete(hp_buffer,i+1);
else{
max+=hp_buffer[i+1];
rate=i;
}
}
hp_buffer[0]=max;
hp_buffer[1]=rate;
rate=0;
max=0;
for(i=1;i<=20;i++){
if(member(sp_buffer, i+1))
if(sp_buffer[i+1]<=0)
sp_buffer = m_delete(sp_buffer,i+1);
else{
max+=sp_buffer[i+1];
rate=i;
}
}
sp_buffer[0]=max;
sp_buffer[1]=rate;
}
public int check_timed_key(string key) {
// keine 0 als key (Typ wird per RTTC geprueft)
if (!key)
return 0;
mapping tmap=Query(P_TIMING_MAP, F_VALUE);
if (!mappingp(tmap)) {
tmap=([]);
Set(P_TIMING_MAP, tmap, F_VALUE);
}
// Wenn key noch nicht abgelaufen, Ablaufzeitpunkt zurueckgeben.
// Sonst 0 (key frei)
return (time() < tmap[key]) && tmap[key];
}
public int check_and_update_timed_key(int duration,string key) {
// wenn key noch gesperrt, die zeit der naechsten Verfuegbarkeit
// zurueckgeben.
int res = check_timed_key(key);
if (res) {
return res;
}
// duration <= 0 ist unsinnig. Aber key ist nicht mehr gesperrt, d.h. time()
// ist ein sinnvoller Rueckgabewert.
if (duration <= 0)
return time();
mapping tmap = Query(P_TIMING_MAP,F_VALUE);
tmap[key]=time()+duration;
// speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
// keine Kopie.
//SetProp(P_TIMING_MAP, tmap);
return -1; // Erfolg.
}
protected void expire_timing_map() {
mapping tmap = Query(P_TIMING_MAP, F_VALUE);
if (!mappingp(tmap) || !sizeof(tmap))
return;
foreach(string key, int endtime: tmap) {
if (endtime < time())
m_delete(tmap, key);
}
// speichern per SetProp() unnoetig, da man das Mapping direkt aendert,
// keine Kopie.
}
protected void heart_beat()
{
if ( !this_object() )
return;
attribute_hb();
// Als Geist leidet man nicht unter so weltlichen Dingen wie
// Alkohol, Gift&Co ...
if ( QueryProp(P_GHOST) )
return;
int hpoison = QueryProp(P_POISON);
int rlock = QueryProp(P_NO_REGENERATION);
int hp = QueryProp(P_HP);
int sp = QueryProp(P_SP);
int alc;
// Wenn Alkohol getrunken: Alkoholauswirkungen?
if ( (alc = QueryProp(P_ALCOHOL)) && !random(40) ){
int n;
string gilde;
object ob;
n = random( 5 * (alc - 1)/QueryProp(P_MAX_ALCOHOL) );
switch (n){
case ALC_EFFECT_HICK:
say( capitalize(name( WER, 1 )) + " sagt: <Hick>!\n" );
write( "<Hick>! Oh, Tschuldigung.\n" );
break;
case ALC_EFFECT_STUMBLE:
say( capitalize(name( WER, 1 )) + " stolpert ueber " +
QueryPossPronoun( FEMALE, WEN ) + " Fuesse.\n" );
write( "Du stolperst.\n" );
break;
case ALC_EFFECT_LOOKDRUNK:
say( capitalize(name( WER, 1 )) + " sieht betrunken aus.\n" );
write( "Du fuehlst Dich benommen.\n" );
break;
case ALC_EFFECT_RUELPS:
say( capitalize(name( WER, 1 )) + " ruelpst.\n" );
write( "Du ruelpst.\n" );
break;
}
// Gilde und Environment informieren ueber Alkoholauswirkung.
if ( stringp(gilde = QueryProp(P_GUILD))
&& objectp(ob = find_object( "/gilden/" + gilde )) )
ob->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_GUILD );
if ( environment() )
environment()->InformAlcoholEffect( ME, n, ALC_EFFECT_AREA_ENV );
}
// Alkohol abbauen und etwas extra heilen, falls erlaubt.
if ( alc && (--delay_alcohol < 0)
&& !(rlock & NO_REG_ALCOHOL) ){
SetProp( P_ALCOHOL, alc - 1 );
if ( !hpoison ){
hp++;
sp++;
}
delay_alcohol = QueryProp(P_ALCOHOL_DELAY);
}
// P_DRINK reduzieren, falls erlaubt.
if ( (--delay_drink < 0) && !(rlock & NO_REG_DRINK) ){
delay_drink = QueryProp(P_DRINK_DELAY);
SetProp( P_DRINK, QueryProp(P_DRINK) - 1 );
}
// P_FOOD reduzieren, falls erlaubt.
if ( (--delay_food < 0) && !(rlock & NO_REG_FOOD) ){
delay_food = QueryProp(P_FOOD_DELAY);
SetProp( P_FOOD, QueryProp(P_FOOD) - 1 );
}
// Regeneration aus dem HP-Puffer
// Hierbei wird zwar nur geheilt, wenn das erlaubt ist, aber der Puffer
// muss trotzdem abgearbeitet/reduziert werden, da Delfen sonst eine
// mobile Tanke kriegen. (Keine Heilung im Hellen, Puffer bleibt sonst
// konstant und kann bei Bedarf ueber dunkelmachende Items abgerufen
// werden.)
int val;
if (hp_buffer[0]) {
int rate = hp_buffer[1];
val = hp_buffer[rate + 1];
if ( val > rate )
val = rate;
hp_buffer[0] -= val;
hp_buffer[rate + 1] -= val;
if ( hp_buffer[rate + 1] <= 0 )
update_buffers();
}
// Jetzt Regeneration aus dem Puffer durchfuehren, aber nur wenn erlaubt.
if ( val && !(rlock & NO_REG_BUFFER_HP) )
hp += val;
// normales Heilen, falls keine Regeneration aus dem Puffer erfolgte und
// es erlaubt ist.
else if ( (--delay_heal < 0) && !(rlock & NO_REG_HP) ){
delay_heal = QueryProp(P_HP_DELAY);
if ( !hpoison )
hp++;
}
// Gleiches Spiel jetzt fuer den SP-Puffer (s.o.)
val=0;
if ( sp_buffer[0] ) {
int rate = sp_buffer[1];
val = sp_buffer[rate + 1];
if ( val > rate )
val = rate;
sp_buffer[0] -= val;
sp_buffer[rate + 1] -= val;
if ( sp_buffer[rate + 1] <= 0 )
update_buffers();
}
// Regeneration erlaubt?
if ( val && !(rlock & NO_REG_BUFFER_SP) )
sp += val;
// Wenn nicht, normales Hochideln versuchen.
else if ( (--delay_sp < 0) && !(rlock & NO_REG_SP) ){
delay_sp = QueryProp(P_SP_DELAY);
if ( !hpoison )
sp++;
}
if ( hpoison && (interactive(ME) || !query_once_interactive(ME)) ){
// Vanion, 26.10.03
// Wenn _set_poison() per SET_METHOD ueberschrieben wird, kann
// nicht sichergestellt werden, dass poison immer groesser 0 ist
// Daher muss hier ein Test rein, so teuer das auch ist :(
if (--hpoison < 0)
hpoison=0;
if ( --delay_poison < 0 ){
delay_poison = QueryProp(P_POISON_DELAY)
+ random(POISON_MERCY_DELAY);
hp -= hpoison;
if ( hp < 0 ){
tell_object( ME, "Oh weh - das Gift war zuviel fuer Dich!\n"
+ "Du stirbst.\n" );
if ( query_once_interactive(ME) ){
create_kill_log_entry( "Vergiftung", 0 );
// Beim Gifttod gibt es keinen Killer. Aber auf diese Art
// erkennt der Todesraum die Ursache korrekt und gibt die
// richtige Meldung aus.
SetProp( P_KILLER, "gift" );
}
die(1);
return;
}
if ( (hpoison < 3 || !query_once_interactive(ME) )
&& --drop_poison < 0)
{
// Giftlevel eins reduzieren. hpoison wurde oben schon
// reduziert, d.h. einfach hpoison in P_POISON schreiben.
// dabei wird dann auch ggf. drop_poison richtig gesetzt.
SetProp( P_POISON, hpoison );
if ( !hpoison )
tell_object( ME, "Du scheinst die Vergiftung "
"ueberwunden zu haben.\n" );
}
}
if ( hpoison && !random(15) )
switch ( hp*100/QueryProp(P_MAX_HP) ){
case 71..100 :
write( "Du fuehlst Dich nicht gut.\n" );
say( capitalize(name(WER)) +
" sieht etwas benommen aus.\n" );
break;
case 46..70 :
write( "Dir ist schwindlig und Dein Magen revoltiert.\n" );
say( capitalize(name(WER)) + " taumelt ein wenig.\n" );
break;
case 26..45 :
write( "Dir ist heiss. Du fuehlst Dich schwach. Kopfweh "
"hast Du auch.\n" );
say( capitalize(name(WER)) + " glueht direkt und scheint "
"grosse Schwierigkeiten zu haben.\n" );
break;
case 11..25 :
write( "Du fuehlst Dich beschissen. Alles tut weh, und Du "
"siehst nur noch unscharf.\n" );
say( capitalize(name(WER)) + " taumelt und stoehnt und "
"kann gerade noch vermeiden, hinzufallen.\n" );
break;
case 0..10 :
write( break_string( "Du siehst fast nichts mehr und kannst "
"Dich nur noch unter groessten Schmerzen "
"bewegen. Aber bald tut nichts mehr weh"
"...", 78 ) );
say( break_string( capitalize(name(WER)) + " glueht wie "
"im Fieber, kann sich kaum noch ruehren "
"und hat ein schmerzverzerrtes Gesicht.\n",
78 ) );
break;
}
}
SetProp( P_HP, hp );
SetProp( P_SP, sp );
}
public int AddExp( int e )
{
int experience;
string fn;
mixed last;
experience = QueryProp(P_XP);
if ( QueryProp(P_KILLS) > 1 && e > 0 )
return experience;
fn = implode( explode( object_name( environment() || this_object() ),
"/" )[0..<2], "/" );
if ( pointerp(last = Query(P_LAST_XP)) && sizeof(last) == 2 && last[0] == fn )
Set( P_LAST_XP, ({ fn, last[1]+e }) );
else
Set( P_LAST_XP, ({ fn, e }) );
if ( (experience += e) < 0 )
experience = 0;
return SetProp( P_XP, experience );
}
static <string|int>* _set_last_xp( <int|string>* last )
{
if ( !pointerp(last) || sizeof(last) != 2 || !stringp(last[0]) ||
!intp(last[1]) )
return Query(P_LAST_XP);
else
return Set( P_LAST_XP, last );
}
static int _set_align(int a)
{
if (a<-1000) a = -1000;
if (a>1000) a = 1000;
return Set(P_ALIGN, a);
}
static int _set_hp( int hp )
{
if ( QueryProp(P_GHOST) )
return QueryProp(P_HP);
if ( hp < 0 )
return Set( P_HP, 0 );
if ( hp > QueryProp(P_MAX_HP) )
return Set( P_HP, QueryProp(P_MAX_HP), F_VALUE );
return Set( P_HP, hp, F_VALUE );
}
static int _set_sp( int sp )
{
//einige Leute schreiben floats in die P_HP. :-(
if (!intp(sp)) {
sp=to_int(sp);
//ja, es ist teuer. Aber ich will wissen, wers ist. Kann vor
//naechstem Reboot wieder raus.
log_file("ILLEGAL_TYPE.log",sprintf(
"Versuch, einen nicht-int in P_SP in %O zu schreiben: \n%O\n",
this_object(),
debug_info(DINFO_TRACE,DIT_STR_CURRENT)));
}
if ( QueryProp(P_GHOST) )
QueryProp(P_SP);
if ( sp < 0 )
return Set( P_SP, 0 );
if ( sp > QueryProp(P_MAX_SP) )
return Set( P_SP, QueryProp(P_MAX_SP), F_VALUE );
return Set( P_SP, sp, F_VALUE );
}
static int _set_alcohol(int n)
{
if(!intp(n))
raise_error(sprintf(
"_set_alcohol(): expected <int>, got %.50O\n", n));
if (QueryProp(P_GHOST))
return Query(P_ALCOHOL, F_VALUE);
// nur Änderungen und Werte >=0 werden gesetzt...
n = n < 0 ? 0 : n;
int old = Query(P_ALCOHOL, F_VALUE);
if ( old == n)
return old;
// Hooks aufrufen
int *ret = HookFlow(H_HOOK_ALCOHOL, n);
// Bei Abbruch alten Wert zurueckgeben
switch (ret[H_RETCODE]) {
case H_CANCELLED:
return old;
case H_ALTERED:
// sonst neuen Wert setzen
if(!intp(ret[H_RETDATA]))
raise_error(sprintf(
"_set_alcohol(): data from HookFlow() != <int>: %.50O\n",
ret[H_RETDATA]));
n = ret[H_RETDATA];
n = n < 0 ? 0 : n;
// H_NO_MOD is fallthrough
}
return Set(P_ALCOHOL, n, F_VALUE);
}
static int _set_drink(int n)
{
if(!intp(n))
raise_error(sprintf(
"_set_drink(): expected <int>, got %.50O\n", n));
if (QueryProp(P_GHOST))
return Query(P_DRINK, F_VALUE);
// nur Änderungen und Werte >=0 werden gesetzt...
n = n < 0 ? 0 : n;
int old = Query(P_DRINK, F_VALUE);
if ( old == n)
return old;
// Hooks aufrufen
int *ret = HookFlow(H_HOOK_DRINK, n);
// Bei Abbruch alten Wert zurueckgeben
switch (ret[H_RETCODE]) {
case H_CANCELLED:
return old;
case H_ALTERED:
// sonst neuen Wert setzen
if(!intp(ret[H_RETDATA]))
raise_error(sprintf(
"_set_drink(): data from HookFlow() != <int>: %.50O\n",
ret[H_RETDATA]));
n = ret[H_RETDATA];
n = n < 0 ? 0 : n;
// H_NO_MOD is fallthrough
}
return Set(P_DRINK, n, F_VALUE);
}
static int _set_food(int n)
{
if(!intp(n))
raise_error(sprintf(
"_set_food(): expected <int>, got %.50O\n", n));
if (QueryProp(P_GHOST))
return Query(P_FOOD, F_VALUE);
// nur Änderungen und Werte >=0 werden gesetzt...
n = n < 0 ? 0 : n;
int old = Query(P_FOOD, F_VALUE);
if ( old == n)
return old;
// Hooks aufrufen
int *ret = HookFlow(H_HOOK_FOOD, n);
// Bei Abbruch alten Wert zurueckgeben
switch (ret[H_RETCODE]) {
case H_CANCELLED:
return old;
case H_ALTERED:
// sonst neuen Wert setzen
if(!intp(ret[H_RETDATA]))
raise_error(sprintf(
"_set_food(): data from HookFlow() != <int>: %.50O\n",
ret[H_RETDATA]));
n = ret[H_RETDATA];
n = n < 0 ? 0 : n;
// H_NO_MOD is fallthrough
}
return Set(P_FOOD, n, F_VALUE);
}
static int _set_poison(int n)
{
if(!intp(n))
raise_error(sprintf(
"_set_poison(): expected <int>, got %.50O\n", n));
if (QueryProp(P_GHOST))
return Query(P_POISON, F_VALUE);
int mp = QueryProp(P_MAX_POISON);
n = (n<0 ? 0 : (n>mp ? mp : n));
// nur >=0 zulassen.
n = n < 0 ? 0 : n;
int old = Query(P_POISON, F_VALUE);
if ( old == 0 && n == 0)
return old;
// Hooks aufrufen
int *ret = HookFlow(H_HOOK_POISON, n);
// Bei Abbruch alten Wert zurueckgeben
switch (ret[H_RETCODE]) {
case H_CANCELLED:
return old;
case H_ALTERED:
// sonst neuen Wert setzen
if(!intp(ret[H_RETDATA]))
raise_error(sprintf(
"_set_poison(): data from HookFlow() != <int>: %.50O\n",
ret[H_RETDATA]));
n = ret[H_RETDATA];
n = n < 0 ? 0 : n;
// H_NO_MOD is fallthrough
}
// Fuer die Selbstheilung.
switch(n) {
case 1:
drop_poison = 40+random(16);
break;
case 2:
drop_poison = 25+random(8);
break;
case 3:
drop_poison = 18+random(4);
break;
default:
// nur relevant fuer NPC, da Spieler bei >3 Gift nicht mehr regegenieren.
drop_poison = 22 - 2*n + random(43 - 3*n);
break;
}
// fuer Setzen der Prop von aussen ein Log schreiben
if (previous_object(1) != ME)
log_file("POISON", sprintf("%s - %s: %d von %O (%s)\n",
dtime(time())[5..],
(query_once_interactive(this_object()) ?
capitalize(geteuid(this_object())) :
capitalize(name(WER))),
n,
(previous_object(2) ? previous_object(2) : previous_object(1)),
(this_player() ? capitalize(geteuid(this_player())) : "???")));
return Set(P_POISON, n, F_VALUE);
}
static int _set_xp(int xp) { return Set(P_XP, xp < 0 ? 0 : xp, F_VALUE); }
static mixed _set_die_hook(mixed hook)
{
if(hook && query_once_interactive(this_object()))
log_file("DIE_HOOK",
sprintf("%s : DIE_HOOK gesetzt von %O in %O (%s)\n",
dtime(time())[5..],
(previous_object(2) ? previous_object(2):previous_object(1)),
this_object(),getuid(this_object())));
return Set(P_TMP_DIE_HOOK,hook, F_VALUE);
}
static mapping _query_enemy_damage()
{
return copy(enemy_damage);
}
// nur ne Kopie liefern, sonst kann das jeder von aussen aendern.
static mapping _query_timing_map() {
return copy(Query(P_TIMING_MAP));
}
/****************************************************************************
* Consume-Funktion, um zentral durch konsumierbare Dinge ausgeloeste
* Aenderungen des Gesundheitszustandes herbeizufuehren.
***************************************************************************/
/* Konsumiert etwas
*
* Rueckgabewert
* 1 erfolgreich konsumiert
* 0 fehlende oder falsche Parameter
* <0 Bedingung fuer konsumieren nicht erfuellt, Bitset aus:
* 1 Kann nichts mehr essen
* 2 Kann nichts mehr trinken
* 4 Kann nichts mehr saufen
* 8 Abgebrochen durch Hook H_HOOK_CONSUME
*/
public varargs int consume(mapping cinfo, int testonly)
{
int retval = 0;
// nur was tun, wenn auch Infos reinkommen
if (mappingp(cinfo) && sizeof(cinfo)) {
// Hooks aufrufen, sie aendern ggf. noch was in cinfo.
mixed *hret = HookFlow(H_HOOK_CONSUME, ({cinfo, testonly}) );
switch(hret[H_RETCODE])
{
case H_CANCELLED:
return -HC_HOOK_CANCELLATION;
case H_ALTERED:
// testonly kann nicht geaendert werden.
cinfo = hret[H_RETDATA][0];
}
// Legacy-Mappings (flache) neben strukturierten Mappings zulassen
// flache Kopien erzeugen (TODO?: und fuer Teilmappings nicht relevante
// Eintraege loeschen)
mapping conditions;
if (mappingp(cinfo[H_CONDITIONS])) {
conditions = copy(cinfo[H_CONDITIONS]);
} else {
conditions = copy(cinfo);
}
mapping effects;
if (mappingp(cinfo[H_EFFECTS])) {
effects = filter(cinfo[H_EFFECTS], (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
} else {
effects = filter(cinfo, (: member(H_ALLOWED_EFFECTS, $1) > -1 :));
}
// Bedingungen pruefen
if (mappingp(conditions) && sizeof(conditions)) {
// Bedingungen fuer Konsum auswerten
if (conditions[P_FOOD] && !eat_food(conditions[P_FOOD], 1))
retval |= HC_MAX_FOOD_REACHED;
else if (conditions[P_DRINK] && !drink_soft(conditions[P_DRINK], 1))
retval |= HC_MAX_DRINK_REACHED;
else if (conditions[P_ALCOHOL] && !drink_alcohol(conditions[P_ALCOHOL], 1))
retval |= HC_MAX_ALCOHOL_REACHED;
// retval negativ machen, damit Fehler leicht erkennbar ist
retval = -retval;
}
// Bedingungen wurden abgearbeitet, jetzt die Heilung durchfuehren
if (!retval) {
if (!testonly) {
// Bedingungen erfuellen, wenn alles passt und kein Test
if (conditions[P_ALCOHOL])
drink_alcohol(conditions[P_ALCOHOL]);
if (conditions[P_DRINK])
drink_soft(conditions[P_DRINK]);
if (conditions[P_FOOD])
eat_food(conditions[P_FOOD]);
// Und jetzt die Wirkungen
if (effects[P_POISON])
SetProp(P_POISON, QueryProp(P_POISON) + effects[P_POISON]);
// Und nun wirklich heilen
switch (cinfo[H_DISTRIBUTION]) {
case HD_INSTANT:
map(effects, (: SetProp($1, QueryProp($1) + $2) :));
break;
case 1..50:
buffer_hp(effects[P_HP], cinfo[H_DISTRIBUTION]);
buffer_sp(effects[P_SP], cinfo[H_DISTRIBUTION]);
break;
default:
buffer_hp(effects[P_HP], HD_STANDARD);
buffer_sp(effects[P_SP], HD_STANDARD);
break;
}
}
retval = 1;
}
}
return retval;
}