| // 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; |
| } |