/*
// Program: /std/living/attack.c
// Written mostly by Buddha@TMI
// Originally based on the attack.c found in the mudlib.n,
// many structural similarites may remain.
// It is is a part of the TMI distribution mudlib.
// Please retain this header if you use this file.
// Much work was done in December of 1991.
// revised 2-16-92 by Buddha
// revised again 3-1-92 by Buddha
// Rewritten to remove the LOC, and to update it for mudlib 0.9, by
// Mobydick@TMI-2, 9-21-92
// Added some stuff to make monsters remember and attack old opponents.
// Mobydick, 2-3-93.
// Added receive_damage() and receive_healing() systems, Watcher 3-27-93
*/
// Basic structure:
// The function kill_ob is used to initiate combat. The kill command calls
// it in any monster or player entering combat, as do the combat spells and
// other nastiness. It also turns on a monster's heartbeat if that is
// necessary.
// The heart_beat calls the continue_attack function. This function checks
// to see if the monster is dead and if so, starts the death sequence. It
// then checks the list of current opponents and removes any that are dead
// or otherwise out of the MUD. Opponents that have left the room are moved
// onto the "will_kill" list. It then checks to see if spellcasting or
// other activity has blocked the attack. If not, it calls execute_attack
// which does the actual attack against the first attacker on the list.
// The functions continue_attack and execute_attack are defined seperately
// in each body.
#include <config.h>
#include <stats.h>
#include <mudlib.h>
inherit SECURE_OBJECT ;
// Prototypes for things that come in through the body object.
varargs mixed set(string prop, mixed arg, mixed security) ;
static object *attackers, target, *will_attack; // wombled by buddha
static int any_attack ;
mixed link_data(string what);
static string qs ;
private static int heal_time ;
// These variables are used in execute_attack() but are declared here to get
// around the limit on number of variables declared in the function. Kludge.
static string *vb, *vb2 ;
static int dmin, dmax, damage ;
static string weapontype ;
static int skill_improve_prob ;
void run_away();
int kill_ob (object victim) ;
void set_verbs(string *verbs) ;
void set_verbs2 (string *verbs) ;
string *get_verb() ;
void cease_all_attacks();
void continue_attack() ;
// This function handles the damage calls from battle. It only accepts
// positive damage calls. receive_healing() should be used for healing.
int receive_damage(int damage) {
int dam;
set ("last_attacker", previous_object()) ;
if(damage <= 0 || link_data("dead") || query("hit_points") < 0 ||
query("linkdead")) return 0;
/*
This stuff is commented out because damage may come from non-living
objects or from spell commands - it's hard to know for sure what
object is the one that you want to attack.
if(!attackers || member_array(this_player(), attackers) == -1)
kill_ob(this_player());
*/
dam = query("hit_points") - damage;
if(dam < 0) dam = -1;
set("hit_points", dam);
return 1; }
// This function handles the healing calls from spells. It only accepts
// positive healing calls. receive_damage() should be used for damage.
int receive_healing(int healing) {
int heal;
if(healing <= 0 || link_data("dead") || query("hit_points") < 0)
return 0;
heal = query("hit_points") + healing ;
if(heal > query("max_hp")) heal = query("max_hp");
set("hit_points", heal);
return 1; }
// This function updates the object's AC. It got moved out of the
// player object so monsters could have it too.
int calc_armor_class() {
mapping armor ;
int ac ;
string *types ;
int i ;
armor = query("armor") ;
if (!armor) {
// The dude is butt-naked.
set ("armor_class", 0) ;
return 0 ;
}
ac = 0 ;
types = keys(armor) ;
for (i=0;i<sizeof(types);i++) {
ac = ac + armor[types[i]] ;
}
set ("armor_class", ac) ;
return ac ;
}
// This function is used to prevent a player from attacking for a time.
// Called by the wield command, the various spells, other places. Anything
// a players does that should interfere with his combat should call this.
void block_attack (int t) {
int i ;
// If we're not in combat, then don't bother.
if (!any_attack) return ;
#ifdef BLOCK_ATTACK
i = query("stop_attack") ;
set ("stop_attack",i+t/2) ;
#endif
}
// This is called in the heart_beat to decrease the blocking time.
void unblock_attack() {
int i ;
i = query("stop_attack")-1 ;
if (i>-1) set ("stop_attack",i) ;
}
// This function returns 1 if the player has stop_attack set and 0 if not.
// It's probably a pretty stupid thing to use, since you should just query
// stop_attack directly, but it's here if you want it.
int player_busy() {
return query("stop_attack") ;
}
// This function is used to initialize various values.
void init_attack() {
attackers = ({});
}
// This function returns the object at the top of the attackers list.
object query_attacker() {
if(!attackers || !sizeof(attackers)) return 0;
return attackers[0] ;
}
// Remove dead or nonexisting objects out of the list of attackers.
// Return true if there is still an attack going.
/*
// This is the 'kill' command. If the victim is not attacked yet,
// then it is entered first into the list of victims. If it is already
// in the list, then it is moved to the first entry.
*/
int kill_ob (object victim) {
int i;
if(query("linkdead")) return 0;
#ifdef NO_PKILL
if (userp(this_object()) && userp(victim)) {
if (query("no_attack") || victim->query("no_attack"))
return 2;
}
// no_attack for users added by Blue, 941022.
#endif
if (!attackers) {
init_attack() ;
attackers = ({ victim }) + attackers;
any_attack = 1;
set_heart_beat(1) ;
victim->kill_ob(this_object());
return 1;
}
i = member_array(victim, attackers);
if (i > -1) {
return 0;
}
attackers = ({ victim }) + attackers;
any_attack = 1;
victim->kill_ob(this_object());
set_heart_beat(1) ;
return 1;
}
// This is a default heart_beat for living objects. There's a player heart_beat
// in user.c and a monster heart_beat in monster.c, both of which should
// override this one.
void heart_beat() {
unblock_attack() ;
continue_attack() ;
}
// Continue_attack is called from heart_beat in monster.c and user.c.
// here is where we can try to see if we're dead or in combat.
// Call this to erase the list of opponents to attack. This will NOT stop
// them from attacking you again... ;)
void cease_all_attacks() {
attackers = ({});
any_attack = 0;
}
// Call this function to remove a specific user from the attack array.
int remove_attacker(object obj) {
if(member_array(obj, attackers) != -1) attackers -= ({ obj });
return 1; }
// This function returns the current target if there is one. The "current
// target" could be in another room, or dead, and not cleaned out of the
// list yet, so be reasonably careful with this.
object query_current_attacker() {
if(sizeof(attackers) == 0)
return 0;
else
return attackers[0];
}
// This function returns the whole attackers list. Same caveat as above.
object *query_attackers() {
if (sizeof(attackers)==0) {
return 0 ;
} else {
return attackers ;
}
}
// This lets a wimpy player or monster run away. Buddha got this part
// working for the case of rooms with no exits. (Bad sign for the wimp.)
void run_away() {
int wimpy ;
string direction ;
mapping womble ;
womble = environment(this_object())->query("exits") ;
if (!womble) return;
direction = this_player()->query("wimpydir") ;
if (!direction || !womble[direction]) {
direction = keys(womble)[random(sizeof(womble))] ;
}
write ("You flee to the "+direction+"!\n") ;
this_player()->force_me("go "+direction) ;
return ;
}
// This function restores spell points and hit points to the player and
// deals with his blood-alcohol level.
// Drunkenness is handled as follows: 0 indicates sobriety. Positive
// numbers indicate drunkenness, and diminish over time. When the player
// reaches drunkenness of 1, we then set his drunkenness to -6, where
// the negative number means hangover rather than drunkenness, and we
// begin counting up until we reach 0, at which time he's sober again.
void heal_up() {
int hp, sp, maxh, maxs ;
int time, dr, rate ;
// If he's a ghost we want to skip this entirely.
if (query("ghost")) return ;
heal_time = heal_time + 1 ;
time = query("time_to_heal") ;
// If the healing rate wasn't set, then it's one hp/sp each 10 heartbeats.
if (time==0) time = 10 ;
// Check to see if it's time to improve hits and spells.
if (heal_time>time) {
hp = query("hit_points") ;
sp = query("spell_points") ;
maxh = query("max_hp") ;
maxs = query("max_sp") ;
heal_time = 0 ;
// The drunkenness comes commented out. If you add the drunkenness stuff,
// put this back in.
/*
dr = query("drunk") ;
switch (dr) {
case -1 : { write ("Your headache clears up.\n") ; break; }
case 1 : { write ("Suddenly you have a massive headache.\n") ; break ; }
case 6 : { write ("Your head is clearing.\n") ; break ;}
case 11 : { write ("You feel drunk.\n") ; break ; }
case 16 : { write ("The room stops spinning.\n") ; break ; }
case 21 : { write ("You can see straight again.\n") ; break ; }
}
// If the player's drunk is 1, we send him into hangover land.
if (dr==1) dr=-6 ;
// Dry him out, or fix his hangover, as needed.
if (dr>0) set ("drunk", dr-1) ;
if (dr<0) set ("drunk", dr+1) ;
*/
if (hp==maxh && sp==maxs) return ;
// If drunkenness, put this line back in, and have sp and hp increase by
// rate instead of by 1.
// You could also put rate in but have it affected by things other than
// alcohol, perhaps by food, by rest, or by magic spells.
// Eodon noticed that being drunken won't do any good
// since you'll het dr = -6 and get -6/5+1 = 0 for healing
// Leto 02/27/95
// rate = dr/5+1 ;
if (hp<maxh) {
set ("hit_points", hp+1) ;
} else {
set("hit_points", maxh) ;
}
if (sp<maxs) {
set ("spell_points", sp+1) ;
} else {
set ("spell_points", maxs) ;
}
}
}
// The set_verbs function stores the list of verbs to be used when a player
// or monster makes an attack, and conjugates the verbs by adding an "s" to
// the end of each. You can use set_verbs2, below, to override this
// conjugation if you need to.
void set_verbs(string *verbs) {
int i ;
string w1, w2 ;
vb = verbs ;
vb2 = allocate(sizeof(vb)) ;
for (i=0;i<sizeof(verbs);i++) {
if (sscanf(verbs[i],"%s %s",w1,w2)==2) {
vb2[i]=w1+"s "+w2 ;
} else {
vb2[i]=verbs[i]+"s" ;
}
}
}
// This function is used to set the conjugated forms of the verbs. Use
// this when one of the attack verbs is irregular: there are some monster
// examples that do it.
void set_verbs2 (string *verbs) {
vb2 = verbs ;
}
// The get_verb function returns a random verb from the list of verbs.
// It returns bboth forms of the verb in a 2-element array.
string *get_verb() {
int i ;
i = random(sizeof(vb)) ;
return ({ vb[i], vb2[i] }) ;
}